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 <[email protected]> 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 "[email protected]" <[email protected]>.
