You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 10:18:23 UTC
[sling-org-apache-sling-testing-osgi-mock] 01/23: SLING-4042 Donate
sling-mock, jcr-mock, osgi-mock implementation
This is an automated email from the ASF dual-hosted git repository.
rombert pushed a commit to annotated tag org.apache.sling.testing.osgi-mock-1.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-osgi-mock.git
commit e5ba840d79da4de23084251e3d5ce06e04c5973a
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Mon Oct 13 11:54:39 2014 +0000
SLING-4042 Donate sling-mock, jcr-mock, osgi-mock implementation
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/testing/osgi-mock@1631356 13f79535-47bb-0310-9956-ffa450edef68
---
pom.xml | 119 ++++++++
.../apache/sling/testing/mock/osgi/MockBundle.java | 182 +++++++++++++
.../sling/testing/mock/osgi/MockBundleContext.java | 220 +++++++++++++++
.../testing/mock/osgi/MockComponentContext.java | 98 +++++++
.../apache/sling/testing/mock/osgi/MockFilter.java | 46 ++++
.../sling/testing/mock/osgi/MockLogService.java | 87 ++++++
.../apache/sling/testing/mock/osgi/MockOsgi.java | 250 +++++++++++++++++
.../testing/mock/osgi/MockServiceReference.java | 131 +++++++++
.../testing/mock/osgi/MockServiceRegistration.java | 105 +++++++
.../sling/testing/mock/osgi/OsgiMetadataUtil.java | 278 +++++++++++++++++++
.../testing/mock/osgi/ReflectionServiceUtil.java | 301 +++++++++++++++++++++
src/main/resources/simplelogger.properties | 18 ++
src/site/markdown/index.md | 35 +++
src/site/markdown/usage.md | 59 ++++
.../testing/mock/osgi/MockBundleContextTest.java | 159 +++++++++++
.../sling/testing/mock/osgi/MockBundleTest.java | 60 ++++
.../mock/osgi/MockComponentContextTest.java | 84 ++++++
.../sling/testing/mock/osgi/MockFilterTest.java | 46 ++++
.../testing/mock/osgi/MockLogServiceTest.java | 70 +++++
.../mock/osgi/MockServiceReferenceTest.java | 81 ++++++
.../testing/mock/osgi/OsgiMetadataUtilTest.java | 93 +++++++
.../mock/osgi/ReflectionServiceUtilTest.java | 226 ++++++++++++++++
.../sling/testing/mock/osgi/package-info.java | 23 ++
src/test/resources/META-INF/test.txt | 1 +
...ling.testing.mock.osgi.OsgiMetadataUtilTest.xml | 14 +
...testing.mock.osgi.ReflectionServiceUtilTest.xml | 32 +++
src/test/resources/simplelogger.properties | 18 ++
27 files changed, 2836 insertions(+)
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..708d885
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>22</version>
+ <relativePath>../../parent/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>org.apache.sling.testing.osgi-mock</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <name>Apache Sling Testing OSGi Mock</name>
+ <description>Mock implementation of selected OSGi APIs.</description>
+
+ <properties>
+ <sling.java.version>6</sling.java.version>
+ </properties>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ <scope>compile</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <scope>compile</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>15.0</version>
+ <scope>compile</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.0.1</version>
+ <scope>compile</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>compile</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <version>1.9.5</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-scr-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>generate-scr-scrdescriptor</id>
+ <goals>
+ <goal>scr</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+ </build>
+
+</project>
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockBundle.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockBundle.java
new file mode 100644
index 0000000..6c1f22e
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockBundle.java
@@ -0,0 +1,182 @@
+/*
+ * 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.InputStream;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+
+/**
+ * Mock {@link Bundle} implementation.
+ */
+class MockBundle implements Bundle {
+
+ private static volatile long bundleCounter;
+
+ private final long bundleId;
+ private final BundleContext bundleContext;
+
+ /**
+ * Constructor
+ */
+ public MockBundle(BundleContext bundleContext) {
+ this.bundleId = ++bundleCounter;
+ this.bundleContext = bundleContext;
+ }
+
+ @Override
+ public long getBundleId() {
+ return this.bundleId;
+ }
+
+ @Override
+ public BundleContext getBundleContext() {
+ return this.bundleContext;
+ }
+
+ @Override
+ public URL getEntry(final String name) {
+ // try to load resource from classpath
+ return getClass().getResource(name);
+ }
+
+ @Override
+ public int getState() {
+ return Bundle.ACTIVE;
+ }
+
+ // --- unsupported operations ---
+ @Override
+ public Enumeration<?> findEntries(final String path, final String filePattern, final boolean recurse) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Enumeration<?> getEntryPaths(final String path) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Dictionary<?, ?> getHeaders() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Dictionary<?, ?> getHeaders(final String locale) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getLastModified() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getLocation() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ServiceReference[] getRegisteredServices() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public URL getResource(final String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Enumeration<?> getResources(final String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ServiceReference[] getServicesInUse() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getSymbolicName() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean hasPermission(final Object permission) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Class<?> loadClass(final String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void start() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void stop() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void uninstall() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void update() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void update(final InputStream inputStream) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void start(final int options) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void stop(final int options) {
+ throw new UnsupportedOperationException();
+ }
+
+ // this is part of org.osgi 4.2.0
+ public Map getSignerCertificates(final int signersType) {
+ throw new UnsupportedOperationException();
+ }
+
+ // this is part of org.osgi 4.2.0
+ public Version getVersion() {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockBundleContext.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockBundleContext.java
new file mode 100644
index 0000000..cbaa13d
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockBundleContext.java
@@ -0,0 +1,220 @@
+/*
+ * 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.File;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.commons.lang3.StringUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * Mock {@link BundleContext} implementation.
+ */
+class MockBundleContext implements BundleContext {
+
+ private final MockBundle bundle;
+ private final List<MockServiceRegistration> registeredServices = new ArrayList<MockServiceRegistration>();
+ private final List<ServiceListener> serviceListeners = new ArrayList<ServiceListener>();
+ private final List<BundleListener> bundleListeners = new ArrayList<BundleListener>();
+
+ public MockBundleContext() {
+ this.bundle = new MockBundle(this);
+ }
+
+ @Override
+ public Bundle getBundle() {
+ return this.bundle;
+ }
+
+ @Override
+ public Filter createFilter(final String s) {
+ // return filter that denies all
+ return new MockFilter();
+ }
+
+ @Override
+ public ServiceRegistration registerService(final String clazz, final Object service, final Dictionary properties) {
+ String[] clazzes;
+ if (StringUtils.isBlank(clazz)) {
+ clazzes = new String[0];
+ } else {
+ clazzes = new String[] { clazz };
+ }
+ return registerService(clazzes, service, properties);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public ServiceRegistration registerService(final String[] clazzes, final Object service, final Dictionary properties) {
+ MockServiceRegistration registration = new MockServiceRegistration(this.bundle, clazzes, service, properties);
+ this.registeredServices.add(registration);
+ notifyServiceListeners(ServiceEvent.REGISTERED, registration.getReference());
+ return registration;
+ }
+
+ @Override
+ public ServiceReference getServiceReference(final String clazz) {
+ ServiceReference[] serviceRefs = getServiceReferences(clazz, null);
+ if (serviceRefs != null && serviceRefs.length > 0) {
+ return serviceRefs[0];
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public ServiceReference[] getServiceReferences(final String clazz, final String filter) {
+ Set<ServiceReference> result = new TreeSet<ServiceReference>();
+ for (MockServiceRegistration serviceRegistration : this.registeredServices) {
+ if (serviceRegistration.matches(clazz, filter)) {
+ result.add(serviceRegistration.getReference());
+ }
+ }
+ if (result.isEmpty()) {
+ return null;
+ } else {
+ return result.toArray(new ServiceReference[result.size()]);
+ }
+ }
+
+ @Override
+ public ServiceReference[] getAllServiceReferences(final String clazz, final String filter) {
+ // for now just do the same as getServiceReferences
+ return getServiceReferences(clazz, filter);
+ }
+
+ @Override
+ public Object getService(final ServiceReference serviceReference) {
+ return ((MockServiceReference) serviceReference).getService();
+ }
+
+ @Override
+ public boolean ungetService(final ServiceReference serviceReference) {
+ // do nothing for now
+ return false;
+ }
+
+ @Override
+ public void addServiceListener(final ServiceListener serviceListener) {
+ addServiceListener(serviceListener, null);
+ }
+
+ @Override
+ public void addServiceListener(final ServiceListener serviceListener, final String s) {
+ if (!serviceListeners.contains(serviceListener)) {
+ serviceListeners.add(serviceListener);
+ }
+ }
+
+ @Override
+ public void removeServiceListener(final ServiceListener serviceListener) {
+ serviceListeners.remove(serviceListener);
+ }
+
+ private void notifyServiceListeners(int eventType, ServiceReference serviceReference) {
+ final ServiceEvent event = new ServiceEvent(eventType, serviceReference);
+ for (ServiceListener serviceListener : serviceListeners) {
+ serviceListener.serviceChanged(event);
+ }
+ }
+
+ @Override
+ public void addBundleListener(final BundleListener bundleListener) {
+ if (!bundleListeners.contains(bundleListener)) {
+ bundleListeners.add(bundleListener);
+ }
+ }
+
+ @Override
+ public void removeBundleListener(final BundleListener bundleListener) {
+ bundleListeners.remove(bundleListener);
+ }
+
+ void sendBundleEvent(BundleEvent bundleEvent) {
+ for (BundleListener bundleListener : bundleListeners) {
+ bundleListener.bundleChanged(bundleEvent);
+ }
+ }
+
+ @Override
+ public void addFrameworkListener(final FrameworkListener frameworkListener) {
+ // accept method, but ignore it
+ }
+
+ @Override
+ public void removeFrameworkListener(final FrameworkListener frameworkListener) {
+ // accept method, but ignore it
+ }
+
+ Object locateService(final String name, final ServiceReference reference) {
+ for (MockServiceRegistration serviceRegistration : this.registeredServices) {
+ if (serviceRegistration.getReference() == reference) {
+ return serviceRegistration.getService();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Bundle[] getBundles() {
+ return new Bundle[0];
+ }
+
+ // --- unsupported operations ---
+ @Override
+ public String getProperty(final String s) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Bundle installBundle(final String s) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Bundle installBundle(final String s, final InputStream inputStream) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Bundle getBundle(final long l) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public File getDataFile(final String s) {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockComponentContext.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockComponentContext.java
new file mode 100644
index 0000000..cf70b1d
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockComponentContext.java
@@ -0,0 +1,98 @@
+/*
+ * 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.util.Dictionary;
+import java.util.Hashtable;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.ComponentInstance;
+
+/**
+ * Mock {@link ComponentContext} implementation.
+ */
+class MockComponentContext implements ComponentContext {
+
+ private final MockBundleContext bundleContext;
+ private final Dictionary<String, Object> properties;
+
+ public MockComponentContext(final MockBundleContext mockBundleContext) {
+ this(mockBundleContext, new Hashtable<String, Object>());
+ }
+
+ public MockComponentContext(final MockBundleContext mockBundleContext, final Dictionary<String, Object> properties) {
+ this.bundleContext = mockBundleContext;
+ this.properties = properties;
+ }
+
+ @Override
+ public Dictionary<String, Object> getProperties() {
+ return this.properties;
+ }
+
+ @Override
+ public Object locateService(final String name, final ServiceReference reference) {
+ return this.bundleContext.locateService(name, reference);
+ }
+
+ @Override
+ public BundleContext getBundleContext() {
+ return this.bundleContext;
+ }
+
+ @Override
+ public void disableComponent(final String name) {
+ // allow calling, but ignore
+ }
+
+ @Override
+ public void enableComponent(final String name) {
+ // allow calling, but ignore
+ }
+
+ // --- unsupported operations ---
+ @Override
+ public ComponentInstance getComponentInstance() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ServiceReference getServiceReference() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Bundle getUsingBundle() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object locateService(final String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object[] locateServices(final String name) {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockFilter.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockFilter.java
new file mode 100644
index 0000000..b0f585c
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockFilter.java
@@ -0,0 +1,46 @@
+/*
+ * 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.util.Dictionary;
+
+import org.osgi.framework.Filter;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Mock {@link Filter} implementation.
+ */
+class MockFilter implements Filter {
+
+ @Override
+ public boolean match(final ServiceReference reference) {
+ return false;
+ }
+
+ @Override
+ public boolean match(final Dictionary dictionary) {
+ return false;
+ }
+
+ @Override
+ public boolean matchCase(final Dictionary dictionary) {
+ return false;
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockLogService.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockLogService.java
new file mode 100644
index 0000000..28d1433
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockLogService.java
@@ -0,0 +1,87 @@
+/*
+ * 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 org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Mock {@link LogService} implementation.
+ */
+class MockLogService implements LogService {
+
+ private final Logger log;
+
+ public MockLogService(final Class<?> loggerContext) {
+ this.log = LoggerFactory.getLogger(loggerContext);
+ }
+
+ @Override
+ public void log(final int level, final String message) {
+ switch (level) {
+ case LogService.LOG_ERROR:
+ this.log.error(message);
+ break;
+ case LogService.LOG_WARNING:
+ this.log.warn(message);
+ break;
+ case LogService.LOG_INFO:
+ this.log.info(message);
+ break;
+ case LogService.LOG_DEBUG:
+ this.log.debug(message);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid log level: " + level);
+ }
+ }
+
+ @Override
+ public void log(final int level, final String message, final Throwable exception) {
+ switch (level) {
+ case LogService.LOG_ERROR:
+ this.log.error(message, exception);
+ break;
+ case LogService.LOG_WARNING:
+ this.log.warn(message, exception);
+ break;
+ case LogService.LOG_INFO:
+ this.log.info(message, exception);
+ break;
+ case LogService.LOG_DEBUG:
+ this.log.debug(message, exception);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid log level: " + level);
+ }
+ }
+
+ @Override
+ public void log(final ServiceReference sr, final int level, final String message) {
+ log(level, message);
+ }
+
+ @Override
+ public void log(final ServiceReference sr, final int level, final String message, final Throwable exception) {
+ log(level, message, exception);
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockOsgi.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockOsgi.java
new file mode 100644
index 0000000..fd65fe1
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockOsgi.java
@@ -0,0 +1,250 @@
+/*
+ * 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.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.log.LogService;
+
+/**
+ * Factory for mock OSGi objects.
+ */
+public final class MockOsgi {
+
+ private MockOsgi() {
+ // static methods only
+ }
+
+ /**
+ * @return Mocked {@link BundleContext} instance
+ */
+ public static 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(BundleContext bundleContext, BundleEvent bundleEvent) {
+ ((MockBundleContext) bundleContext).sendBundleEvent(bundleEvent);
+ }
+
+ /**
+ * @return Mocked {@link ComponentContext} instance
+ */
+ public static ComponentContext newComponentContext() {
+ return new MockComponentContext((MockBundleContext) newBundleContext());
+ }
+
+ /**
+ * @param properties Properties
+ * @return Mocked {@link ComponentContext} instance
+ */
+ public static ComponentContext newComponentContext(Dictionary<String, Object> properties) {
+ return newComponentContext(newBundleContext(), properties);
+ }
+
+ /**
+ * @param properties Properties
+ * @return Mocked {@link ComponentContext} instance
+ */
+ public static ComponentContext newComponentContext(Map<String, Object> properties) {
+ return newComponentContext(toDictionary(properties));
+ }
+
+ /**
+ * @param bundleContext Bundle context
+ * @param properties Properties
+ * @return Mocked {@link ComponentContext} instance
+ */
+ public static ComponentContext newComponentContext(BundleContext bundleContext,
+ Dictionary<String, Object> properties) {
+ return new MockComponentContext((MockBundleContext) bundleContext, properties);
+ }
+
+ /**
+ * @param bundleContext Bundle context
+ * @param properties Properties
+ * @return Mocked {@link ComponentContext} instance
+ */
+ public static ComponentContext newComponentContext(BundleContext bundleContext, Map<String, Object> properties) {
+ return newComponentContext(bundleContext, toDictionary(properties));
+ }
+
+ /**
+ * @param loggerContext Context class for logging
+ * @return Mocked {@link LogService} instance
+ */
+ public static LogService newLogService(final Class<?> loggerContext) {
+ return new MockLogService(loggerContext);
+ }
+
+ /**
+ * Simulate OSGi service 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
+ */
+ public static boolean injectServices(Object target, BundleContext bundleContext) {
+ return ReflectionServiceUtil.injectServices(target, bundleContext);
+ }
+
+ /**
+ * Simulate activation of service instance. Invokes the @Activate annotated
+ * method.
+ * @param target Service instance.
+ * @return true if activation method was called. False if such a method did
+ * not exist.
+ */
+ public static boolean activate(Object target) {
+ ComponentContext componentContext = newComponentContext();
+ return ReflectionServiceUtil.activateDeactivate(target, componentContext, true);
+ }
+
+ /**
+ * Simulate activation of service instance. Invokes the @Activate annotated
+ * method.
+ * @param target Service instance.
+ * @param properties Properties
+ * @return true if activation method was called. False if such a method did
+ * not exist.
+ */
+ public static boolean activate(Object target, Dictionary<String, Object> properties) {
+ ComponentContext componentContext = newComponentContext(properties);
+ return ReflectionServiceUtil.activateDeactivate(target, componentContext, true);
+ }
+
+ /**
+ * Simulate activation of service instance. Invokes the @Activate annotated
+ * method.
+ * @param target Service instance.
+ * @param properties Properties
+ * @return true if activation method was called. False if such a method did
+ * not exist.
+ */
+ public static boolean activate(Object target, Map<String, Object> properties) {
+ return activate(target, toDictionary(properties));
+ }
+
+ /**
+ * Simulate activation of service instance. Invokes the @Activate annotated
+ * method.
+ * @param target Service instance.
+ * @param bundleContext Bundle context
+ * @param properties Properties
+ * @return true if activation method was called. False if such a method did
+ * not exist.
+ */
+ public static boolean activate(Object target, BundleContext bundleContext, Dictionary<String, Object> properties) {
+ ComponentContext componentContext = newComponentContext(bundleContext, properties);
+ return ReflectionServiceUtil.activateDeactivate(target, componentContext, true);
+ }
+
+ /**
+ * Simulate activation of service instance. Invokes the @Activate annotated
+ * method.
+ * @param target Service instance.
+ * @param bundleContext Bundle context
+ * @param properties Properties
+ * @return true if activation method was called. False if such a method did
+ * not exist.
+ */
+ public static boolean activate(Object target, BundleContext bundleContext, Map<String, Object> properties) {
+ return activate(target, bundleContext, toDictionary(properties));
+ }
+
+ /**
+ * Simulate deactivation of service instance. Invokes the @Activate
+ * annotated method.
+ * @param target Service instance.
+ * @return true if deactivation method was called. False if such a method
+ * did not exist.
+ */
+ public static boolean deactivate(Object target) {
+ ComponentContext componentContext = newComponentContext();
+ return ReflectionServiceUtil.activateDeactivate(target, componentContext, false);
+ }
+
+ /**
+ * Simulate deactivation of service instance. Invokes the @Activate
+ * annotated method.
+ * @param target Service instance.
+ * @param properties Properties
+ * @return true if deactivation method was called. False if such a method
+ * did not exist.
+ */
+ public static boolean deactivate(Object target, Dictionary<String, Object> properties) {
+ ComponentContext componentContext = newComponentContext(properties);
+ return ReflectionServiceUtil.activateDeactivate(target, componentContext, false);
+ }
+
+ /**
+ * Simulate deactivation of service instance. Invokes the @Activate
+ * annotated method.
+ * @param target Service instance.
+ * @param properties Properties
+ * @return true if deactivation method was called. False if such a method
+ * did not exist.
+ */
+ public static boolean deactivate(Object target, Map<String, Object> properties) {
+ return deactivate(target, toDictionary(properties));
+ }
+
+ /**
+ * Simulate deactivation of service instance. Invokes the @Activate
+ * annotated method.
+ * @param target Service instance.
+ * @param bundleContext Bundle context
+ * @param properties Properties
+ * @return true if deactivation method was called. False if such a method
+ * did not exist.
+ */
+ public static boolean deactivate(Object target, BundleContext bundleContext, Dictionary<String, Object> properties) {
+ ComponentContext componentContext = newComponentContext(bundleContext, properties);
+ return ReflectionServiceUtil.activateDeactivate(target, componentContext, false);
+ }
+
+ /**
+ * Simulate activation of service instance. Invokes the @Activate annotated
+ * method.
+ * @param target Service instance.
+ * @param bundleContext Bundle context
+ * @param properties Properties
+ * @return true if activation method was called. False if such a method did
+ * not exist.
+ */
+ public static boolean deactivate(Object target, BundleContext bundleContext, Map<String, Object> properties) {
+ return deactivate(target, bundleContext, toDictionary(properties));
+ }
+
+ private static Dictionary<String, Object> toDictionary(Map<String, Object> map) {
+ return new Hashtable<String, Object>(map);
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockServiceReference.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockServiceReference.java
new file mode 100644
index 0000000..c7568be
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockServiceReference.java
@@ -0,0 +1,131 @@
+/*
+ * 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.util.Collections;
+import java.util.Dictionary;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Mock {@link ServiceReference} implementation.
+ */
+class MockServiceReference implements ServiceReference {
+
+ private final Bundle bundle;
+ private final MockServiceRegistration serviceRegistration;
+
+ public MockServiceReference(final Bundle bundle, final MockServiceRegistration serviceRegistration) {
+ this.bundle = bundle;
+ this.serviceRegistration = serviceRegistration;
+ }
+
+ @Override
+ public Bundle getBundle() {
+ return this.bundle;
+ }
+
+ /**
+ * Set service reference property
+ * @param key Key
+ * @param value Value
+ */
+ public void setProperty(final String key, final Object value) {
+ this.serviceRegistration.getProperties().put(key, value);
+ }
+
+ @Override
+ public Object getProperty(final String key) {
+ return this.serviceRegistration.getProperties().get(key);
+ }
+
+ @Override
+ public String[] getPropertyKeys() {
+ Dictionary<String, Object> props = this.serviceRegistration.getProperties();
+ return Collections.list(props.keys()).toArray(new String[props.size()]);
+ }
+
+ @Override
+ public int hashCode() {
+ return ((Long) getServiceId()).hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof MockServiceReference)) {
+ return false;
+ }
+ return ((Long) getServiceId()).equals(((MockServiceReference) obj).getServiceId());
+ }
+
+ @Override
+ public int compareTo(final Object obj) {
+ if (!(obj instanceof MockServiceReference)) {
+ return 0;
+ }
+ // sort by decreasing by service ranking, and secondary increasing by
+ // service id
+ Integer serviceRanking = getServiceRanking();
+ Integer otherServiceRanking = ((MockServiceReference) obj).getServiceRanking();
+ int serviceRankingCompare = otherServiceRanking.compareTo(serviceRanking);
+ if (serviceRankingCompare == 0) {
+ Long serviceId = getServiceId();
+ Long otherServiceId = ((MockServiceReference) obj).getServiceId();
+ return serviceId.compareTo(otherServiceId);
+ } else {
+ return serviceRankingCompare;
+ }
+ }
+
+ long getServiceId() {
+ Number serviceID = (Number) getProperty(Constants.SERVICE_ID);
+ if (serviceID != null) {
+ return serviceID.longValue();
+ } else {
+ return 0L;
+ }
+ }
+
+ int getServiceRanking() {
+ Number serviceRanking = (Number) getProperty(Constants.SERVICE_RANKING);
+ if (serviceRanking != null) {
+ return serviceRanking.intValue();
+ } else {
+ return 0;
+ }
+ }
+
+ Object getService() {
+ return this.serviceRegistration.getService();
+ }
+
+ // --- unsupported operations ---
+ @Override
+ public Bundle[] getUsingBundles() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isAssignableTo(final Bundle otherBundle, final String className) {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockServiceRegistration.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockServiceRegistration.java
new file mode 100644
index 0000000..a3bdaf5
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockServiceRegistration.java
@@ -0,0 +1,105 @@
+/*
+ * 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.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.w3c.dom.Document;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Mock {@link ServiceRegistration} implementation.
+ */
+class MockServiceRegistration implements ServiceRegistration {
+
+ private static volatile long serviceCounter;
+
+ private final Set<String> clazzes;
+ private final Object service;
+ private Dictionary<String, Object> properties;
+ private final ServiceReference serviceReference;
+
+ @SuppressWarnings("unchecked")
+ public MockServiceRegistration(final Bundle bundle, final String[] clazzes, final Object service,
+ final Dictionary<String, Object> properties) {
+ this.clazzes = new HashSet<String>(ImmutableList.copyOf(clazzes));
+ this.service = service;
+ this.properties = properties != null ? properties : new Hashtable();
+ this.properties.put(Constants.SERVICE_ID, ++serviceCounter);
+ this.serviceReference = new MockServiceReference(bundle, this);
+ readOsgiMetadata();
+ }
+
+ @Override
+ public ServiceReference getReference() {
+ return this.serviceReference;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void setProperties(final Dictionary properties) {
+ this.properties = properties;
+ }
+
+ @Override
+ public void unregister() {
+ // do nothing for now
+ }
+
+ Dictionary<String, Object> getProperties() {
+ return this.properties;
+ }
+
+ boolean matches(final String clazz, final String filter) {
+ // ignore filter for now
+ return this.clazzes.contains(clazz);
+ }
+
+ Object getService() {
+ return this.service;
+ }
+
+ /**
+ * Try to read OSGI-metadata from /OSGI-INF and read all implemented
+ * interfaces and service properties
+ */
+ private void readOsgiMetadata() {
+ Class<?> serviceClass = service.getClass();
+ Document doc = OsgiMetadataUtil.getMetadata(serviceClass);
+
+ // add service interfaces from OSGi metadata
+ clazzes.addAll(OsgiMetadataUtil.getServiceInterfaces(serviceClass, doc));
+
+ // add properties from OSGi metadata
+ Map<String, Object> props = OsgiMetadataUtil.getProperties(serviceClass, doc);
+ for (Map.Entry<String, Object> entry : props.entrySet()) {
+ properties.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtil.java b/src/main/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtil.java
new file mode 100644
index 0000000..da8bca0
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtil.java
@@ -0,0 +1,278 @@
+/*
+ * 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.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+
+/**
+ * Helper methods to parse OSGi metadata.
+ */
+final class OsgiMetadataUtil {
+
+ private static final Logger log = LoggerFactory.getLogger(OsgiMetadataUtil.class);
+
+ private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY;
+ static {
+ DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
+ DOCUMENT_BUILDER_FACTORY.setNamespaceAware(true);
+ }
+
+ private static final XPathFactory XPATH_FACTORY = XPathFactory.newInstance();
+
+ private static final BiMap<String, String> NAMESPACES = HashBiMap.create();
+ static {
+ NAMESPACES.put("scr", "http://www.osgi.org/xmlns/scr/v1.1.0");
+ }
+
+ private OsgiMetadataUtil() {
+ // static methods only
+ }
+
+ private static final NamespaceContext NAMESPACE_CONTEXT = new NamespaceContext() {
+ @Override
+ public String getNamespaceURI(String prefix) {
+ return NAMESPACES.get(prefix);
+ }
+
+ @Override
+ public String getPrefix(String namespaceURI) {
+ return NAMESPACES.inverse().get(namespaceURI);
+ }
+
+ @Override
+ public Iterator getPrefixes(String namespaceURI) {
+ return NAMESPACES.keySet().iterator();
+ }
+ };
+
+ /**
+ * Try to read OSGI-metadata from /OSGI-INF and read all implemented
+ * interfaces and service properties
+ * @param clazz OSGi service implementation class
+ * @return Metadata document or null
+ */
+ public static Document getMetadata(Class clazz) {
+ String metadataPath = "/OSGI-INF/" + StringUtils.substringBefore(clazz.getName(), "$") + ".xml";
+ InputStream metadataStream = clazz.getResourceAsStream(metadataPath);
+ if (metadataStream == null) {
+ log.debug("No OSGi metadata found at {}", metadataPath);
+ return null;
+ }
+ try {
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+ return documentBuilder.parse(metadataStream);
+ } catch (ParserConfigurationException ex) {
+ throw new RuntimeException("Unable to read classpath resource: " + metadataPath, ex);
+ } catch (SAXException ex) {
+ throw new RuntimeException("Unable to read classpath resource: " + metadataPath, ex);
+ } catch (IOException ex) {
+ throw new RuntimeException("Unable to read classpath resource: " + metadataPath, ex);
+ } finally {
+ try {
+ metadataStream.close();
+ } catch (IOException ex) {
+ // ignore
+ }
+ }
+ }
+
+ public static Set<String> getServiceInterfaces(Class clazz, Document metadata) {
+ Set<String> serviceInterfaces = new HashSet<String>();
+ if (metadata != null) {
+ String query = "/components/component[@name='" + clazz.getName() + "']/service/provide[@interface!='']";
+ NodeList nodes = queryNodes(metadata, query);
+ if (nodes != null) {
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Node node = nodes.item(i);
+ String serviceInterface = getAttributeValue(node, "interface");
+ if (StringUtils.isNotBlank(serviceInterface)) {
+ serviceInterfaces.add(serviceInterface);
+ }
+ }
+ }
+ }
+ return serviceInterfaces;
+ }
+
+ public static Map<String, Object> getProperties(Class clazz, Document metadata) {
+ Map<String, Object> props = new HashMap<String, Object>();
+ if (metadata != null) {
+ String query = "/components/component[@name='" + clazz.getName() + "']/property[@name!='' and @value!='']";
+ NodeList nodes = queryNodes(metadata, query);
+ if (nodes != null) {
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Node node = nodes.item(i);
+ String name = getAttributeValue(node, "name");
+ String value = getAttributeValue(node, "value");
+ String type = getAttributeValue(node, "type");
+ if (StringUtils.equals("Integer", type)) {
+ props.put(name, Integer.parseInt(value));
+ } else {
+ props.put(name, value);
+ }
+ }
+ }
+ }
+ return props;
+ }
+
+ public static List<Reference> getReferences(Class clazz, Document metadata) {
+ List<Reference> references = new ArrayList<Reference>();
+ if (metadata != null) {
+ String query = "/components/component[@name='" + clazz.getName() + "']/reference[@name!='']";
+ NodeList nodes = queryNodes(metadata, query);
+ if (nodes != null) {
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Node node = nodes.item(i);
+ references.add(new Reference(node));
+ }
+ }
+ }
+ return references;
+ }
+
+ public static String getActivateMethodName(Class clazz, Document metadata) {
+ if (metadata != null) {
+ String query = "/components/component[@name='" + clazz.getName() + "']";
+ Node node = queryNode(metadata, query);
+ if (node != null) {
+ return getAttributeValue(node, "activate");
+ }
+ }
+ return null;
+ }
+
+ public static String getDeactivateMethodName(Class clazz, Document metadata) {
+ if (metadata != null) {
+ String query = "/components/component[@name='" + clazz.getName() + "']";
+ Node node = queryNode(metadata, query);
+ if (node != null) {
+ return getAttributeValue(node, "deactivate");
+ }
+ }
+ return null;
+ }
+
+ private static NodeList queryNodes(Document metadata, String xpathQuery) {
+ try {
+ XPath xpath = XPATH_FACTORY.newXPath();
+ xpath.setNamespaceContext(NAMESPACE_CONTEXT);
+ return (NodeList) xpath.evaluate(xpathQuery, metadata, XPathConstants.NODESET);
+ } catch (XPathExpressionException ex) {
+ throw new RuntimeException("Error evaluating XPath: " + xpathQuery, ex);
+ }
+ }
+
+ private static Node queryNode(Document metadata, String xpathQuery) {
+ try {
+ XPath xpath = XPATH_FACTORY.newXPath();
+ xpath.setNamespaceContext(NAMESPACE_CONTEXT);
+ return (Node) xpath.evaluate(xpathQuery, metadata, XPathConstants.NODE);
+ } catch (XPathExpressionException ex) {
+ throw new RuntimeException("Error evaluating XPath: " + xpathQuery, ex);
+ }
+ }
+
+ private static String getAttributeValue(Node node, String attributeName) {
+ Node namedItem = node.getAttributes().getNamedItem(attributeName);
+ if (namedItem != null) {
+ return namedItem.getNodeValue();
+ } else {
+ return null;
+ }
+ }
+
+ public static class Reference {
+
+ private final String name;
+ private final String interfaceType;
+ private final ReferenceCardinality cardinality;
+ private final String bind;
+ private final String unbind;
+
+ public Reference(Node node) {
+ this.name = getAttributeValue(node, "name");
+ this.interfaceType = getAttributeValue(node, "interface");
+ this.cardinality = toCardinality(getAttributeValue(node, "cardinality"));
+ this.bind = getAttributeValue(node, "bind");
+ this.unbind = getAttributeValue(node, "unbind");
+ }
+
+ private ReferenceCardinality toCardinality(String value) {
+ for (ReferenceCardinality item : ReferenceCardinality.values()) {
+ if (StringUtils.equals(item.getCardinalityString(), value)) {
+ return item;
+ }
+ }
+ return ReferenceCardinality.MANDATORY_UNARY;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getInterfaceType() {
+ return this.interfaceType;
+ }
+
+ public ReferenceCardinality getCardinality() {
+ return this.cardinality;
+ }
+
+ public String getBind() {
+ return this.bind;
+ }
+
+ public String getUnbind() {
+ return this.unbind;
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/ReflectionServiceUtil.java b/src/main/java/org/apache/sling/testing/mock/osgi/ReflectionServiceUtil.java
new file mode 100644
index 0000000..aa338a3
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/ReflectionServiceUtil.java
@@ -0,0 +1,301 @@
+/*
+ * 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.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.Reference;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+
+/**
+ * Helper methods to inject dependencies and activate services via reflection.
+ */
+final class ReflectionServiceUtil {
+
+ private static final Logger log = LoggerFactory.getLogger(ReflectionServiceUtil.class);
+
+ private ReflectionServiceUtil() {
+ // static methods only
+ }
+
+ /**
+ * Simulate activation or deactivation of OSGi service instance.
+ * @param target Service instance.
+ * @param componentContext Component context
+ * @return true if activation method was called. False if such a method did
+ * not exist.
+ */
+ public static boolean activateDeactivate(Object target, ComponentContext componentContext, boolean activate) {
+ Class<?> targetClass = target.getClass();
+
+ // get method name for activation/deactivation from osgi metadata
+ Document metadata = OsgiMetadataUtil.getMetadata(targetClass);
+ String methodName;
+ if (activate) {
+ methodName = OsgiMetadataUtil.getActivateMethodName(targetClass, metadata);
+ } else {
+ methodName = OsgiMetadataUtil.getDeactivateMethodName(targetClass, metadata);
+ }
+ if (StringUtils.isEmpty(methodName)) {
+ return false;
+ }
+
+ // if method is defined try to execute it
+ Method method = getMethod(targetClass, methodName, new Class<?>[] { ComponentContext.class }, activate);
+ if (method != null) {
+ try {
+ method.setAccessible(true);
+ method.invoke(target, componentContext);
+ return true;
+ } catch (IllegalAccessException ex) {
+ throw new RuntimeException("Unable to invoke activate/deactivate method for class "
+ + targetClass.getName(), ex);
+ } catch (IllegalArgumentException ex) {
+ throw new RuntimeException("Unable to invoke activate/deactivate method for class "
+ + targetClass.getName(), ex);
+ } catch (InvocationTargetException ex) {
+ throw new RuntimeException("Unable to invoke activate/deactivate method for class "
+ + targetClass.getName(), ex);
+ }
+ }
+ log.warn("Method {}(ComponentContext) not found in class {}", methodName, targetClass.getName());
+ return false;
+ }
+
+ private static Method getMethod(Class clazz, String methodName, Class<?>[] signature, boolean activate) {
+ Method[] methods = clazz.getDeclaredMethods();
+ for (Method method : methods) {
+ if (StringUtils.equals(method.getName(), methodName)
+ && Arrays.equals(method.getParameterTypes(), signature)) {
+ return method;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Simulate OSGi service dependency injection. Injects direct references and
+ * multiple references.
+ * @param target Service instance
+ * @param bundleContext Bundle context from which services are fetched to inject.
+ * @return true if all dependencies could be injected
+ */
+ public static boolean injectServices(Object target, BundleContext bundleContext) {
+
+ // collect all declared reference annotations on class and field level
+ Class<?> targetClass = target.getClass();
+ List<Reference> references = getReferences(targetClass);
+
+ // try to inject services
+ boolean allInjected = true;
+ for (Reference reference : references) {
+ allInjected = allInjected && injectServiceReference(reference, target, bundleContext);
+ }
+ return allInjected;
+ }
+
+ private static List<Reference> getReferences(Class clazz) {
+ Document metadata = OsgiMetadataUtil.getMetadata(clazz);
+ return OsgiMetadataUtil.getReferences(clazz, metadata);
+ }
+
+ private static boolean injectServiceReference(Reference reference, Object target, BundleContext bundleContext) {
+ Class<?> targetClass = target.getClass();
+
+ // get reference type
+ Class<?> type;
+ try {
+ type = Class.forName(reference.getInterfaceType());
+ } catch (ClassNotFoundException ex) {
+ throw new RuntimeException("Unable to instantiate reference type: " + reference.getInterfaceType(), ex);
+ }
+
+ // get matching service references
+ List<ServiceInfo> matchingServices = getMatchingServices(type, bundleContext);
+
+ // no references found? check if reference was optional
+ if (matchingServices.isEmpty()) {
+ boolean isOptional = (reference.getCardinality() == ReferenceCardinality.OPTIONAL_UNARY || reference
+ .getCardinality() == ReferenceCardinality.OPTIONAL_MULTIPLE);
+ if (!isOptional) {
+ log.warn("Unable to inject mandatory reference '{}' for class {}", reference.getName(),
+ targetClass.getName());
+ }
+ return isOptional;
+ }
+
+ // multiple references found? check if reference is not multiple
+ if (matchingServices.size() > 1
+ && (reference.getCardinality() == ReferenceCardinality.MANDATORY_UNARY || reference.getCardinality() == ReferenceCardinality.OPTIONAL_UNARY)) {
+ log.warn("Multiple matches found for unary reference '{}' for class {}", reference.getName(),
+ targetClass.getName());
+ return false;
+ }
+
+ // try to invoke bind method
+ String bindMethodName = reference.getBind();
+ if (StringUtils.isNotEmpty(bindMethodName)) {
+ Method bindMethod = getFirstMethodWithNameAndSignature(targetClass, bindMethodName, new Class<?>[] { type });
+ if (bindMethod != null) {
+ bindMethod.setAccessible(true);
+ for (ServiceInfo matchingService : matchingServices) {
+ try {
+ bindMethod.invoke(target, matchingService.getServiceInstance());
+ } catch (IllegalAccessException ex) {
+ throw new RuntimeException("Unable to invoke method " + bindMethodName + " for class "
+ + targetClass.getName(), ex);
+ } catch (IllegalArgumentException ex) {
+ throw new RuntimeException("Unable to invoke method " + bindMethodName + " for class "
+ + targetClass.getName(), ex);
+ } catch (InvocationTargetException ex) {
+ throw new RuntimeException("Unable to invoke method " + bindMethodName + " for class "
+ + targetClass.getName(), ex);
+ }
+ }
+ return true;
+ } else {
+ Method bindMethodWithConfig = getFirstMethodWithNameAndSignature(targetClass, bindMethodName,
+ new Class<?>[] { type, Map.class });
+ if (bindMethodWithConfig != null) {
+ bindMethodWithConfig.setAccessible(true);
+ for (ServiceInfo matchingService : matchingServices) {
+ try {
+ bindMethodWithConfig.invoke(target, matchingService.getServiceInstance(),
+ matchingService.getServiceConfig());
+ } catch (IllegalAccessException ex) {
+ throw new RuntimeException("Unable to invoke method " + bindMethodName + " for class "
+ + targetClass.getName(), ex);
+ } catch (IllegalArgumentException ex) {
+ throw new RuntimeException("Unable to invoke method " + bindMethodName + " for class "
+ + targetClass.getName(), ex);
+ } catch (InvocationTargetException ex) {
+ throw new RuntimeException("Unable to invoke method " + bindMethodName + " for class "
+ + targetClass.getName(), ex);
+ }
+ }
+ return true;
+ } else {
+ Method bindMethodServiceReference = getFirstMethodWithNameAndSignature(targetClass, bindMethodName,
+ new Class<?>[] { ServiceReference.class });
+ if (bindMethodServiceReference != null) {
+ bindMethodServiceReference.setAccessible(true);
+ for (ServiceInfo matchingService : matchingServices) {
+ if (matchingService.getServiceReference() != null) {
+ try {
+ bindMethodServiceReference.invoke(target, matchingService.getServiceReference());
+ } catch (IllegalAccessException ex) {
+ throw new RuntimeException("Unable to invoke method " + bindMethodName
+ + " for class " + targetClass.getName(), ex);
+ } catch (IllegalArgumentException ex) {
+ throw new RuntimeException("Unable to invoke method " + bindMethodName
+ + " for class " + targetClass.getName(), ex);
+ } catch (InvocationTargetException ex) {
+ throw new RuntimeException("Unable to invoke method " + bindMethodName
+ + " for class " + targetClass.getName(), ex);
+ }
+ }
+ }
+ return true;
+ }
+ }
+ }
+ }
+
+ log.warn("Bind method not found for reference '{}' for class {}", reference.getName(), targetClass.getName());
+ return false;
+ }
+
+ private static Method getFirstMethodWithNameAndSignature(Class<?> clazz, String methodName, Class<?>[] signature) {
+ Method[] methods = clazz.getDeclaredMethods();
+ for (Method method : methods) {
+ if (StringUtils.equals(method.getName(), methodName)
+ && Arrays.equals(method.getParameterTypes(), signature)) {
+ return method;
+ }
+ }
+ // not found? check super classes
+ Class<?> superClass = clazz.getSuperclass();
+ if (superClass != null && superClass != Object.class) {
+ return getFirstMethodWithNameAndSignature(superClass, methodName, signature);
+ }
+ return null;
+ }
+
+ private static List<ServiceInfo> getMatchingServices(Class<?> type, BundleContext bundleContext) {
+ List<ServiceInfo> matchingServices = new ArrayList<ServiceInfo>();
+ try {
+ ServiceReference[] references = bundleContext.getServiceReferences(type.getName(), null);
+ if (references != null) {
+ for (ServiceReference serviceReference : references) {
+ Object serviceInstance = bundleContext.getService(serviceReference);
+ Map<String, Object> serviceConfig = new HashMap<String, Object>();
+ String[] keys = serviceReference.getPropertyKeys();
+ for (String key : keys) {
+ serviceConfig.put(key, serviceReference.getProperty(key));
+ }
+ matchingServices.add(new ServiceInfo(serviceInstance, serviceConfig, serviceReference));
+ }
+ }
+ } catch (InvalidSyntaxException ex) {
+ // ignore
+ }
+ return matchingServices;
+ }
+
+ private static class ServiceInfo {
+
+ private final Object serviceInstance;
+ private final Map<String, Object> serviceConfig;
+ private final ServiceReference serviceReference;
+
+ public ServiceInfo(Object serviceInstance, Map<String, Object> serviceConfig, ServiceReference serviceReference) {
+ this.serviceInstance = serviceInstance;
+ this.serviceConfig = serviceConfig;
+ this.serviceReference = serviceReference;
+ }
+
+ public Object getServiceInstance() {
+ return this.serviceInstance;
+ }
+
+ public Map<String, Object> getServiceConfig() {
+ return this.serviceConfig;
+ }
+
+ public ServiceReference getServiceReference() {
+ return serviceReference;
+ }
+
+ }
+
+}
diff --git a/src/main/resources/simplelogger.properties b/src/main/resources/simplelogger.properties
new file mode 100644
index 0000000..1507469
--- /dev/null
+++ b/src/main/resources/simplelogger.properties
@@ -0,0 +1,18 @@
+# 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.
+
+org.slf4j.simpleLogger.defaultLogLevel=warn
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
new file mode 100644
index 0000000..acf7d98
--- /dev/null
+++ b/src/site/markdown/index.md
@@ -0,0 +1,35 @@
+## About OSGi Mocks
+
+Mock implementation of selected OSGi APIs.
+
+### Maven Dependency
+
+```xml
+<dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.testing.osgi-mock</artifactId>
+ <version>1.0.0-SNAPHOT</version>
+</dependency>
+```
+
+### Documentation
+
+* [Usage](usage.html)
+* [API Documentation](apidocs/)
+* [Changelog](changes-report.html)
+
+### Implemented mock features
+
+The mock implementation supports:
+
+* Instantiating OSGi `Bundle`, `BundleContext` and `ComponentContext` objects and navigate between them.
+* Read and write properties on them.
+* Register OSGi services and get references to service instances
+* Service and bundle listener implementation
+* When adding services to BundleContext OSGi metadata from `/OSGI-INF/<pid>.xml` is read (e.g. for service ranking property)
+* Mock implementation of `LogService` which logs to SLF4J in JUnit context
+
+The following features are *not supported*:
+
+* Activation and deactivation methods of services are not called automatically (but helper methods exist)
+* Dependency injection does not take place automatically (but helper methods exist)
diff --git a/src/site/markdown/usage.md b/src/site/markdown/usage.md
new file mode 100644
index 0000000..c7e8bb8
--- /dev/null
+++ b/src/site/markdown/usage.md
@@ -0,0 +1,59 @@
+## Usage
+
+### Getting OSGi mock objects
+
+The factory class `MockOsgi` allows to instantiate the different mock implementations.
+
+Example:
+
+```java
+// get bundle context
+BundleContext bundleContext = MockOsgi.newBundleContext();
+
+// get component context
+Dictionary<String,Object> properties = new Hashtable<>();
+properties.put("prop1", "value1");
+BundleContext bundleContext = MockOsgi.newComponentContext(properties);
+```
+
+It is possible to simulate registering of OSGi services (backed by a simple hash map internally):
+
+```java
+// register service
+bundleContext.registerService(MyClass.class, myService, properties);
+
+// get service instance
+ServiceReference ref = bundleContext.getServiceReference(MyClass.class.getName());
+MyClass service = bundleContext.getService(ref);
+```
+
+### Activation and Dependency Injection
+
+It is possible to simulate OSGi service activation, deactivation and dependency injection and the mock implementation
+tries to to its best to execute all as expected for an OSGi environment.
+
+Example:
+
+```java
+// get bundle context
+BundleContext bundleContext = MockOsgi.newBundleContext();
+
+// create service instance manually
+MyService service = new MyService();
+
+// inject dependencies
+MockOsgi.injectServices(service, bundleContext);
+
+// activate service
+MockOsgi.activate(service, props);
+
+// operate with service...
+
+// deactivate service
+MockOsgi.deactivate(service);
+```
+
+Please note: The injectServices, activate and deactivate Methods can only work properly when the SCR XML metadata files
+are preset in the classpath at `/OSGI-INF`. They are generated automatically by the Maven SCR plugin, but might be
+missing if your clean and build the project within your IDE (e.g. Eclipse). In this case you have to compile the
+project again with maven and can run the tests - or use a Maven IDE Integration like m2eclipse.
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/MockBundleContextTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/MockBundleContextTest.java
new file mode 100644
index 0000000..6eef138
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/MockBundleContextTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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 static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+@RunWith(MockitoJUnitRunner.class)
+public class MockBundleContextTest {
+
+ private BundleContext bundleContext;
+
+ @Before
+ public void setUp() {
+ this.bundleContext = MockOsgi.newBundleContext();
+ }
+
+ @Test
+ public void testBundle() {
+ assertNotNull(this.bundleContext.getBundle());
+ }
+
+ @Test
+ public void testServiceRegistration() throws InvalidSyntaxException {
+ // prepare test services
+ String clazz1 = String.class.getName();
+ Object service1 = new Object();
+ Dictionary properties1 = getServiceProperties(null);
+ ServiceRegistration reg1 = this.bundleContext.registerService(clazz1, service1, properties1);
+
+ String[] clazzes2 = new String[] { String.class.getName(), Integer.class.getName() };
+ Object service2 = new Object();
+ Dictionary properties2 = getServiceProperties(null);
+ ServiceRegistration reg2 = this.bundleContext.registerService(clazzes2, service2, properties2);
+
+ String clazz3 = Integer.class.getName();
+ Object service3 = new Object();
+ Dictionary properties3 = getServiceProperties(100L);
+ ServiceRegistration reg3 = this.bundleContext.registerService(clazz3, service3, properties3);
+
+ // test get service references
+ ServiceReference refString = this.bundleContext.getServiceReference(String.class.getName());
+ assertSame(reg1.getReference(), refString);
+
+ ServiceReference refInteger = this.bundleContext.getServiceReference(Integer.class.getName());
+ assertSame(reg3.getReference(), refInteger);
+
+ ServiceReference[] refsString = this.bundleContext.getServiceReferences(String.class.getName(), null);
+ assertEquals(2, refsString.length);
+ assertSame(reg1.getReference(), refsString[0]);
+ assertSame(reg2.getReference(), refsString[1]);
+
+ ServiceReference[] refsInteger = this.bundleContext.getServiceReferences(Integer.class.getName(), null);
+ assertEquals(2, refsInteger.length);
+ assertSame(reg3.getReference(), refsInteger[0]);
+ assertSame(reg2.getReference(), refsInteger[1]);
+
+ ServiceReference[] allRefsString = this.bundleContext.getAllServiceReferences(String.class.getName(), null);
+ assertArrayEquals(refsString, allRefsString);
+
+ // test get services
+ assertSame(service1, this.bundleContext.getService(refsString[0]));
+ assertSame(service2, this.bundleContext.getService(refsString[1]));
+ assertSame(service3, this.bundleContext.getService(refInteger));
+
+ // unget does nothing
+ this.bundleContext.ungetService(refsString[0]);
+ this.bundleContext.ungetService(refsString[1]);
+ this.bundleContext.ungetService(refInteger);
+ }
+
+ private Dictionary getServiceProperties(final Long serviceRanking) {
+ Dictionary<String, Object> props = new Hashtable<String, Object>();
+ if (serviceRanking != null) {
+ props.put(Constants.SERVICE_RANKING, serviceRanking);
+ }
+ return props;
+ }
+
+ @Test
+ public void testGetBundles() throws Exception {
+ assertEquals(0, this.bundleContext.getBundles().length);
+ }
+
+ @Test
+ public void testServiceListener() throws Exception {
+ ServiceListener serviceListener = mock(ServiceListener.class);
+ bundleContext.addServiceListener(serviceListener);
+
+ // prepare test services
+ String clazz1 = String.class.getName();
+ Object service1 = new Object();
+ this.bundleContext.registerService(clazz1, service1, null);
+
+ verify(serviceListener).serviceChanged(any(ServiceEvent.class));
+
+ bundleContext.removeServiceListener(serviceListener);
+ }
+
+ @Test
+ public void testBundleListener() throws Exception {
+ BundleListener bundleListener = mock(BundleListener.class);
+ BundleEvent bundleEvent = mock(BundleEvent.class);
+
+ bundleContext.addBundleListener(bundleListener);
+
+ MockOsgi.sendBundleEvent(bundleContext, bundleEvent);
+ verify(bundleListener).bundleChanged(bundleEvent);
+
+ bundleContext.removeBundleListener(bundleListener);
+ }
+
+ @Test
+ public void testFrameworkListener() throws Exception {
+ // ensure that listeners can be called (although they are not expected
+ // to to anything)
+ this.bundleContext.addFrameworkListener(null);
+ this.bundleContext.removeFrameworkListener(null);
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/MockBundleTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/MockBundleTest.java
new file mode 100644
index 0000000..7402241
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/MockBundleTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+
+public class MockBundleTest {
+
+ private Bundle bundle;
+
+ @Before
+ public void setUp() {
+ this.bundle = MockOsgi.newBundleContext().getBundle();
+ }
+
+ @Test
+ public void testBundleId() {
+ assertTrue(this.bundle.getBundleId() > 0);
+ }
+
+ @Test
+ public void testBundleContxt() {
+ assertNotNull(this.bundle.getBundleContext());
+ }
+
+ @Test
+ public void testGetEntry() {
+ assertNotNull(this.bundle.getEntry("/META-INF/test.txt"));
+ assertNull(this.bundle.getEntry("/invalid"));
+ }
+
+ @Test
+ public void testGetStatie() {
+ assertEquals(Bundle.ACTIVE, bundle.getState());
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/MockComponentContextTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/MockComponentContextTest.java
new file mode 100644
index 0000000..18f7ba4
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/MockComponentContextTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+
+public class MockComponentContextTest {
+
+ private ComponentContext underTest;
+
+ @Before
+ public void setUp() {
+ underTest = MockOsgi.newComponentContext();
+ }
+
+ @Test
+ public void testBundleContext() {
+ assertNotNull(underTest.getBundleContext());
+ }
+
+ @Test
+ public void testInitialProperties() {
+ assertEquals(0, underTest.getProperties().size());
+ }
+
+ @Test
+ public void testProvidedProperties() {
+ Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put("prop1", "value1");
+ props.put("prop2", 25);
+ ComponentContext componentContextWithProperties = MockOsgi.newComponentContext(props);
+
+ Dictionary contextProps = componentContextWithProperties.getProperties();
+ assertEquals(2, contextProps.size());
+ assertEquals("value1", contextProps.get("prop1"));
+ assertEquals(25, contextProps.get("prop2"));
+ }
+
+ @Test
+ public void testLocateService() {
+ // prepare test service
+ String clazz = String.class.getName();
+ Object service = new Object();
+ underTest.getBundleContext().registerService(clazz, service, null);
+ ServiceReference ref = underTest.getBundleContext().getServiceReference(clazz);
+
+ // test locate service
+ Object locatedService = underTest.locateService(null, ref);
+ assertSame(service, locatedService);
+ }
+
+ @Test
+ public void testIgnoredMethods() {
+ underTest.enableComponent("myComponent");
+ underTest.disableComponent("myComponent");
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/MockFilterTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/MockFilterTest.java
new file mode 100644
index 0000000..9c4a955
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/MockFilterTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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 static org.junit.Assert.assertFalse;
+
+import java.util.Hashtable;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Filter;
+import org.osgi.framework.ServiceReference;
+
+public class MockFilterTest {
+
+ private Filter filter;
+
+ @Before
+ public void setUp() {
+ this.filter = new MockFilter();
+ }
+
+ @Test
+ public void testDenyAll() {
+ assertFalse(this.filter.match((ServiceReference) null));
+ assertFalse(this.filter.match((Hashtable) null));
+ assertFalse(this.filter.matchCase(null));
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/MockLogServiceTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/MockLogServiceTest.java
new file mode 100644
index 0000000..7fbd7ab
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/MockLogServiceTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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 org.junit.Before;
+import org.junit.Test;
+import org.osgi.service.log.LogService;
+
+public class MockLogServiceTest {
+
+ private LogService logService;
+
+ @Before
+ public void setUp() throws Exception {
+ this.logService = new MockLogService(getClass());
+ }
+
+ @Test
+ public void testLog() {
+ this.logService.log(LogService.LOG_ERROR, "message 1");
+ this.logService.log(LogService.LOG_WARNING, "message 1");
+ this.logService.log(LogService.LOG_INFO, "message 1");
+ this.logService.log(LogService.LOG_DEBUG, "message 1");
+
+ this.logService.log(null, LogService.LOG_ERROR, "message 1");
+ this.logService.log(null, LogService.LOG_WARNING, "message 1");
+ this.logService.log(null, LogService.LOG_INFO, "message 1");
+ this.logService.log(null, LogService.LOG_DEBUG, "message 1");
+ }
+
+ @Test
+ public void testLogException() {
+ this.logService.log(LogService.LOG_ERROR, "message 2", new Exception());
+ this.logService.log(LogService.LOG_WARNING, "message 2", new Exception());
+ this.logService.log(LogService.LOG_INFO, "message 2", new Exception());
+ this.logService.log(LogService.LOG_DEBUG, "message 2", new Exception());
+
+ this.logService.log(null, LogService.LOG_ERROR, "message 2", new Exception());
+ this.logService.log(null, LogService.LOG_WARNING, "message 2", new Exception());
+ this.logService.log(null, LogService.LOG_INFO, "message 2", new Exception());
+ this.logService.log(null, LogService.LOG_DEBUG, "message 2", new Exception());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testLogInvalidLevel() {
+ this.logService.log(0, "message 1");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testLogExceptionInvalidLevel() {
+ this.logService.log(0, "message 2", new Exception());
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/MockServiceReferenceTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/MockServiceReferenceTest.java
new file mode 100644
index 0000000..c525575
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/MockServiceReferenceTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.sling.testing.mock.osgi.OsgiMetadataUtilTest.ServiceWithMetadata;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+public class MockServiceReferenceTest {
+
+ private BundleContext bundleContext;
+ private ServiceReference serviceReference;
+ private Object service;
+
+ @Before
+ public void setUp() {
+ this.bundleContext = MockOsgi.newBundleContext();
+
+ this.service = new Object();
+ String clazz = String.class.getName();
+ Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put("customProp1", "value1");
+
+ this.bundleContext.registerService(clazz, this.service, props);
+ this.serviceReference = this.bundleContext.getServiceReference(clazz);
+ }
+
+ @Test
+ public void testBundle() {
+ assertSame(this.bundleContext.getBundle(), this.serviceReference.getBundle());
+ }
+
+ @Test
+ public void testServiceId() {
+ assertNotNull(this.serviceReference.getProperty(Constants.SERVICE_ID));
+ }
+
+ @Test
+ public void testProperties() {
+ assertEquals(2, this.serviceReference.getPropertyKeys().length);
+ assertEquals("value1", this.serviceReference.getProperty("customProp1"));
+ }
+
+ @Test
+ public void testWithOsgiMetadata() {
+ ServiceWithMetadata serviceWithMetadata = new OsgiMetadataUtilTest.ServiceWithMetadata();
+ bundleContext.registerService((String) null, serviceWithMetadata, null);
+ ServiceReference reference = this.bundleContext.getServiceReference(Comparable.class.getName());
+
+ assertEquals(5000, reference.getProperty("service.ranking"));
+ assertEquals("The Apache Software Foundation", reference.getProperty("service.vendor"));
+ assertEquals("org.apache.sling.models.impl.injectors.OSGiServiceInjector", reference.getProperty("service.pid"));
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java
new file mode 100644
index 0000000..52f9011
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.Reference;
+import org.junit.Test;
+import org.w3c.dom.Document;
+
+public class OsgiMetadataUtilTest {
+
+ @Test
+ public void testMetadata() {
+ Document doc = OsgiMetadataUtil.getMetadata(ServiceWithMetadata.class);
+
+ Set<String> serviceInterfaces = OsgiMetadataUtil.getServiceInterfaces(ServiceWithMetadata.class, doc);
+ assertEquals(3, serviceInterfaces.size());
+ assertTrue(serviceInterfaces.contains("org.apache.sling.models.spi.Injector"));
+ assertTrue(serviceInterfaces
+ .contains("org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory"));
+ assertTrue(serviceInterfaces.contains("java.lang.Comparable"));
+
+ Map<String, Object> props = OsgiMetadataUtil.getProperties(ServiceWithMetadata.class, doc);
+ assertEquals(3, props.size());
+ assertEquals(5000, props.get("service.ranking"));
+ assertEquals("The Apache Software Foundation", props.get("service.vendor"));
+ assertEquals("org.apache.sling.models.impl.injectors.OSGiServiceInjector", props.get("service.pid"));
+ }
+
+ @Test
+ public void testNoMetadata() {
+ Document doc = OsgiMetadataUtil.getMetadata(ServiceWithoutMetadata.class);
+
+ Set<String> serviceInterfaces = OsgiMetadataUtil.getServiceInterfaces(ServiceWithoutMetadata.class, doc);
+ assertEquals(0, serviceInterfaces.size());
+
+ Map<String, Object> props = OsgiMetadataUtil.getProperties(ServiceWithoutMetadata.class, doc);
+ assertEquals(0, props.size());
+ }
+
+ @Test
+ public void testReferences() {
+ Document doc = OsgiMetadataUtil.getMetadata(ReflectionServiceUtilTest.Service3.class);
+ List<Reference> references = OsgiMetadataUtil.getReferences(ReflectionServiceUtilTest.Service3.class, doc);
+ assertEquals(3, references.size());
+
+ Reference ref1 = references.get(0);
+ assertEquals("reference2", ref1.getName());
+ assertEquals("org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface2", ref1.getInterfaceType());
+ assertEquals(ReferenceCardinality.MANDATORY_MULTIPLE, ref1.getCardinality());
+ assertEquals("bindReference2", ref1.getBind());
+ assertEquals("unbindReference2", ref1.getUnbind());
+ }
+
+ @Test
+ public void testActivateMethodName() {
+ Document doc = OsgiMetadataUtil.getMetadata(ReflectionServiceUtilTest.Service3.class);
+ String methodName = OsgiMetadataUtil.getActivateMethodName(ReflectionServiceUtilTest.Service3.class, doc);
+ assertEquals("activate", methodName);
+ }
+
+ static class ServiceWithMetadata {
+ // empty class
+ }
+
+ static class ServiceWithoutMetadata {
+ // empty class
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/ReflectionServiceUtilTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/ReflectionServiceUtilTest.java
new file mode 100644
index 0000000..ff29037
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/ReflectionServiceUtilTest.java
@@ -0,0 +1,226 @@
+/*
+ * 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 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.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.References;
+import org.apache.felix.scr.annotations.Service;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+
+public class ReflectionServiceUtilTest {
+
+ private BundleContext bundleContext = MockOsgi.newBundleContext();
+ private Service1 service1;
+ private Service2 service2;
+
+ @Before
+ public void setUp() {
+ service1 = new Service1();
+ service2 = new Service2();
+ bundleContext.registerService(ServiceInterface1.class.getName(), service1, null);
+ bundleContext.registerService(ServiceInterface2.class.getName(), service2, null);
+ }
+
+ @Test
+ public void testService3() {
+ Service3 service3 = new Service3();
+ assertTrue(MockOsgi.injectServices(service3, bundleContext));
+
+ Dictionary<String, Object> service3Config = new Hashtable<String, Object>();
+ service3Config.put("prop1", "value1");
+ assertTrue(MockOsgi.activate(service3, bundleContext, service3Config));
+
+ assertNotNull(service3.getComponentContext());
+ assertEquals(service3Config, service3.getComponentContext().getProperties());
+
+ assertSame(service1, service3.getReference1());
+
+ List<ServiceInterface2> references2 = service3.getReferences2();
+ assertEquals(1, references2.size());
+ assertSame(service2, references2.get(0));
+
+ List<ServiceInterface3> references3 = service3.getReferences3();
+ assertEquals(1, references3.size());
+ assertSame(service2, references3.get(0));
+
+ List<Map<String, Object>> reference3Configs = service3.getReference3Configs();
+ assertEquals(1, reference3Configs.size());
+ assertEquals(200, reference3Configs.get(0).get(Constants.SERVICE_RANKING));
+
+ assertTrue(MockOsgi.deactivate(service3));
+ assertNull(service3.getComponentContext());
+ }
+
+ @Test
+ public void testService4() {
+ Service4 service4 = new Service4();
+
+ assertTrue(MockOsgi.injectServices(service4, bundleContext));
+ assertFalse(MockOsgi.activate(service4));
+
+ assertSame(service1, service4.getReference1());
+ }
+
+ public interface ServiceInterface1 {
+ // no methods
+ }
+
+ public interface ServiceInterface2 {
+ // no methods
+ }
+
+ public interface ServiceInterface3 {
+ // no methods
+ }
+
+ @Component
+ @Service(ServiceInterface1.class)
+ @Property(name = Constants.SERVICE_RANKING, intValue = 100)
+ public static class Service1 implements ServiceInterface1 {
+ // dummy interface
+ }
+
+ @Component
+ @Service({ ServiceInterface2.class, ServiceInterface3.class })
+ @Property(name = Constants.SERVICE_RANKING, intValue = 200)
+ public static class Service2 implements ServiceInterface2, ServiceInterface3 {
+ // dummy interface
+ }
+
+ @Component
+ @References({ @Reference(name = "reference2", referenceInterface = ServiceInterface2.class, cardinality = ReferenceCardinality.MANDATORY_MULTIPLE) })
+ public static class Service3 {
+
+ @Reference
+ private ServiceInterface1 reference1;
+
+ private List<ServiceReference> references2 = new ArrayList<ServiceReference>();
+
+ @Reference(name = "reference3", referenceInterface = ServiceInterface3.class, cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE)
+ private List<ServiceInterface3> references3 = new ArrayList<ServiceInterface3>();
+ private List<Map<String, Object>> reference3Configs = new ArrayList<Map<String, Object>>();
+
+ private ComponentContext componentContext;
+
+ @Activate
+ private void activate(ComponentContext ctx) {
+ this.componentContext = ctx;
+ }
+
+ @Deactivate
+ private void deactivate(ComponentContext ctx) {
+ this.componentContext = null;
+ }
+
+ public ServiceInterface1 getReference1() {
+ return this.reference1;
+ }
+
+ public List<ServiceInterface2> getReferences2() {
+ List<ServiceInterface2> services = new ArrayList<ServiceInterface2>();
+ for (ServiceReference serviceReference : references2) {
+ services.add((ServiceInterface2)componentContext.getBundleContext().getService(serviceReference));
+ }
+ return services;
+ }
+
+ public List<ServiceInterface3> getReferences3() {
+ return this.references3;
+ }
+
+ public List<Map<String, Object>> getReference3Configs() {
+ return this.reference3Configs;
+ }
+
+ public ComponentContext getComponentContext() {
+ return this.componentContext;
+ }
+
+ protected void bindReference1(ServiceInterface1 service) {
+ reference1 = service;
+ }
+
+ protected void unbindReference1(ServiceInterface1 service) {
+ reference1 = null;
+ }
+
+ protected void bindReference2(ServiceReference serviceReference) {
+ references2.add(serviceReference);
+ }
+
+ protected void unbindReference2(ServiceReference serviceReference) {
+ references2.remove(serviceReference);
+ }
+
+ protected void bindReference3(ServiceInterface3 service, Map<String, Object> serviceConfig) {
+ references3.add(service);
+ reference3Configs.add(serviceConfig);
+ }
+
+ protected void unbindReference3(ServiceInterface3 service, Map<String, Object> serviceConfig) {
+ references3.remove(service);
+ reference3Configs.remove(serviceConfig);
+ }
+
+ }
+
+ @Component
+ @Reference(referenceInterface = ServiceInterface1.class, name = "customName", bind = "customBind", unbind = "customUnbind")
+ public static class Service4 {
+
+ private ServiceInterface1 reference1;
+
+ public ServiceInterface1 getReference1() {
+ return this.reference1;
+ }
+
+ protected void customBind(ServiceInterface1 service) {
+ reference1 = service;
+ }
+
+ protected void customUnbind(ServiceInterface1 service) {
+ reference1 = null;
+ }
+
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/package-info.java b/src/test/java/org/apache/sling/testing/mock/osgi/package-info.java
new file mode 100644
index 0000000..9c9ff28
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+/**
+ * Mock implementation of selected OSGi APIs.
+ */
+package org.apache.sling.testing.mock.osgi;
+
diff --git a/src/test/resources/META-INF/test.txt b/src/test/resources/META-INF/test.txt
new file mode 100644
index 0000000..af27ff4
--- /dev/null
+++ b/src/test/resources/META-INF/test.txt
@@ -0,0 +1 @@
+This is a test file.
\ No newline at end of file
diff --git a/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiMetadataUtilTest.xml b/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiMetadataUtilTest.xml
new file mode 100644
index 0000000..95ac503
--- /dev/null
+++ b/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiMetadataUtilTest.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<components xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
+ <scr:component name="org.apache.sling.testing.mock.osgi.OsgiMetadataUtilTest$ServiceWithMetadata" activate="activate">
+ <implementation class="org.apache.sling.testing.mock.osgi.OsgiMetadataUtilTest$ServiceWithMetadata"/>
+ <service servicefactory="false">
+ <provide interface="org.apache.sling.models.spi.Injector"/>
+ <provide interface="org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory"/>
+ <provide interface="java.lang.Comparable"/>
+ </service>
+ <property name="service.ranking" type="Integer" value="5000"/>
+ <property name="service.vendor" value="The Apache Software Foundation"/>
+ <property name="service.pid" value="org.apache.sling.models.impl.injectors.OSGiServiceInjector"/>
+ </scr:component>
+</components>
diff --git a/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest.xml b/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest.xml
new file mode 100644
index 0000000..923b90f
--- /dev/null
+++ b/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<components xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
+ <scr:component name="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service1">
+ <implementation class="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service1"/>
+ <service servicefactory="false">
+ <provide interface="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface1"/>
+ </service>
+ <property name="service.ranking" type="Integer" value="100"/>
+ <property name="service.pid" value="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service1"/>
+ </scr:component>
+ <scr:component name="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service2">
+ <implementation class="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service2"/>
+ <service servicefactory="false">
+ <provide interface="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface2"/>
+ <provide interface="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface3"/>
+ </service>
+ <property name="service.ranking" type="Integer" value="200"/>
+ <property name="service.pid" value="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service2"/>
+ </scr:component>
+ <scr:component name="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service3" activate="activate" deactivate="deactivate">
+ <implementation class="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service3"/>
+ <property name="service.pid" value="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service3"/>
+ <reference name="reference2" interface="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface2" cardinality="1..n" policy="static" bind="bindReference2" unbind="unbindReference2"/>
+ <reference name="reference1" interface="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface1" cardinality="1..1" policy="static" bind="bindReference1" unbind="unbindReference1"/>
+ <reference name="reference3" interface="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface3" cardinality="0..n" policy="static" bind="bindReference3" unbind="unbindReference3"/>
+ </scr:component>
+ <scr:component name="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service4">
+ <implementation class="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service4"/>
+ <property name="service.pid" value="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service4"/>
+ <reference name="customName" interface="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface1" cardinality="1..1" policy="static" bind="customBind" unbind="customUnbind"/>
+ </scr:component>
+</components>
diff --git a/src/test/resources/simplelogger.properties b/src/test/resources/simplelogger.properties
new file mode 100644
index 0000000..1299698
--- /dev/null
+++ b/src/test/resources/simplelogger.properties
@@ -0,0 +1,18 @@
+# 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.
+
+org.slf4j.simpleLogger.defaultLogLevel=error
--
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.