| /* |
| * 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.IOException; |
| import java.io.InputStream; |
| import java.util.Collection; |
| import java.util.Comparator; |
| import java.util.Dictionary; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Queue; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentLinkedQueue; |
| import java.util.concurrent.ConcurrentSkipListSet; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.felix.framework.FilterImpl; |
| import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.Reference; |
| import org.apache.sling.testing.mock.osgi.OsgiServiceUtil.ReferenceInfo; |
| import org.apache.sling.testing.mock.osgi.OsgiServiceUtil.ServiceInfo; |
| 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.InvalidSyntaxException; |
| import org.osgi.framework.ServiceEvent; |
| import org.osgi.framework.ServiceFactory; |
| import org.osgi.framework.ServiceListener; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.framework.ServiceRegistration; |
| import org.osgi.service.cm.ConfigurationAdmin; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.io.Files; |
| |
| /** |
| * Mock {@link BundleContext} implementation. |
| */ |
| class MockBundleContext implements BundleContext { |
| |
| private final MockBundle bundle; |
| private final SortedSet<MockServiceRegistration> registeredServices = new ConcurrentSkipListSet<MockServiceRegistration>(); |
| private final Map<ServiceListener, Filter> serviceListeners = new ConcurrentHashMap<ServiceListener, Filter>(); |
| private final Queue<BundleListener> bundleListeners = new ConcurrentLinkedQueue<BundleListener>(); |
| private final ConfigurationAdmin configAdmin = new MockConfigurationAdmin(); |
| private File dataFileBaseDir; |
| |
| public MockBundleContext() { |
| this.bundle = new MockBundle(this); |
| |
| // register configuration admin by default |
| registerService(ConfigurationAdmin.class.getName(), configAdmin, null); |
| } |
| |
| @Override |
| public Bundle getBundle() { |
| return this.bundle; |
| } |
| |
| @Override |
| public Filter createFilter(final String s) throws InvalidSyntaxException { |
| if (s == null) { |
| return new MatchAllFilter(); |
| } |
| else { |
| return new FilterImpl(s); |
| } |
| } |
| |
| @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); |
| } |
| |
| // this is part of org.osgi.core 6.0.0 |
| public <S> ServiceRegistration registerService(Class<S> clazz, S service, Dictionary<String, ?> properties) { |
| return registerService(clazz.getName(), service, properties); |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public ServiceRegistration registerService(final String[] clazzes, final Object service, final Dictionary properties) { |
| Dictionary<String, Object> mergedPropertes = MapMergeUtil.propertiesMergeWithOsgiMetadata(service, configAdmin, properties); |
| MockServiceRegistration registration = new MockServiceRegistration(this.bundle, clazzes, service, mergedPropertes, this); |
| this.registeredServices.add(registration); |
| handleRefsUpdateOnRegister(registration); |
| notifyServiceListeners(ServiceEvent.REGISTERED, registration.getReference()); |
| return registration; |
| } |
| |
| /** |
| * Check for already registered services that may be affected by the service registration - either |
| * adding by additional optional references, or creating a conflict in the dependencies. |
| * @param registration |
| */ |
| private void handleRefsUpdateOnRegister(MockServiceRegistration registration) { |
| |
| // handle DYNAMIC references to this registration |
| List<ReferenceInfo> affectedDynamicReferences = OsgiServiceUtil.getMatchingDynamicReferences(registeredServices, registration); |
| for (ReferenceInfo referenceInfo : affectedDynamicReferences) { |
| Reference reference = referenceInfo.getReference(); |
| switch (reference.getCardinality()) { |
| case MANDATORY_UNARY: |
| throw new ReferenceViolationException("Mandatory unary reference of type " + reference.getInterfaceType() + " already fulfilled " |
| + "for service " + reference.getServiceClass().getName() + ", registration of new service with this interface failed."); |
| case MANDATORY_MULTIPLE: |
| case OPTIONAL_MULTIPLE: |
| case OPTIONAL_UNARY: |
| OsgiServiceUtil.invokeBindMethod(reference, referenceInfo.getServiceRegistration().getService(), |
| new ServiceInfo(registration)); |
| break; |
| default: |
| throw new RuntimeException("Unepxected cardinality: " + reference.getCardinality()); |
| } |
| } |
| |
| // handle STATIC+GREEDY references to this registration |
| List<ReferenceInfo> affectedStaticGreedyReferences = OsgiServiceUtil.getMatchingStaticGreedyReferences(registeredServices, registration); |
| for (ReferenceInfo referenceInfo : affectedStaticGreedyReferences) { |
| Reference reference = referenceInfo.getReference(); |
| switch (reference.getCardinality()) { |
| case MANDATORY_UNARY: |
| throw new ReferenceViolationException("Mandatory unary reference of type " + reference.getInterfaceType() + " already fulfilled " |
| + "for service " + reference.getServiceClass().getName() + ", registration of new service with this interface failed."); |
| case MANDATORY_MULTIPLE: |
| case OPTIONAL_MULTIPLE: |
| case OPTIONAL_UNARY: |
| restartService(referenceInfo.getServiceRegistration()); |
| break; |
| default: |
| throw new RuntimeException("Unepxected cardinality: " + reference.getCardinality()); |
| } |
| } |
| } |
| |
| void unregisterService(MockServiceRegistration registration) { |
| this.registeredServices.remove(registration); |
| handleRefsUpdateOnUnregister(registration); |
| notifyServiceListeners(ServiceEvent.UNREGISTERING, registration.getReference()); |
| } |
| |
| void restartService(MockServiceRegistration registration) { |
| // get current service properties |
| Class<?> serviceClass = registration.getService().getClass(); |
| Map<String,Object> properties = MapUtil.toMap(registration.getProperties()); |
| |
| // deactivate & unregister service |
| MockOsgi.deactivate(registration.getService(), this); |
| unregisterService(registration); |
| |
| // newly create and register service |
| Object newService; |
| try { |
| newService = serviceClass.newInstance(); |
| } |
| catch (InstantiationException e) { |
| throw new RuntimeException("Unable to instantiate service: " + serviceClass); |
| } |
| catch (IllegalAccessException e) { |
| throw new RuntimeException("Unable to access service class: " + serviceClass); |
| } |
| MockOsgi.injectServices(newService, this); |
| MockOsgi.activate(newService, this, properties); |
| registerService(serviceClass.getName(), newService, MapUtil.toDictionary(properties)); |
| } |
| |
| /** |
| * Check for already registered services that may be affected by the service unregistration - either |
| * adding by removing optional references, or creating a conflict in the dependencies. |
| * @param registration |
| */ |
| private void handleRefsUpdateOnUnregister(MockServiceRegistration registration) { |
| |
| // handle DYNAMIC references to this registration |
| List<ReferenceInfo> affectedDynamicReferences = OsgiServiceUtil.getMatchingDynamicReferences(registeredServices, registration); |
| for (ReferenceInfo referenceInfo : affectedDynamicReferences) { |
| Reference reference = referenceInfo.getReference(); |
| switch (reference.getCardinality()) { |
| case MANDATORY_UNARY: |
| throw new ReferenceViolationException("Reference of type " + reference.getInterfaceType() + " " |
| + "for service " + reference.getServiceClass().getName() + " is mandatory unary, " |
| + "unregistration of service with this interface failed."); |
| case MANDATORY_MULTIPLE: |
| case OPTIONAL_MULTIPLE: |
| case OPTIONAL_UNARY: |
| // it is currently not checked if for a MANDATORY_MULTIPLE reference the last reference is removed |
| OsgiServiceUtil.invokeUnbindMethod(reference, referenceInfo.getServiceRegistration().getService(), |
| new ServiceInfo(registration)); |
| break; |
| default: |
| throw new RuntimeException("Unepxected cardinality: " + reference.getCardinality()); |
| } |
| } |
| |
| // handle STATIC+GREEDY references to this registration |
| List<ReferenceInfo> affectedStaticGreedyReferences = OsgiServiceUtil.getMatchingStaticGreedyReferences(registeredServices, registration); |
| for (ReferenceInfo referenceInfo : affectedStaticGreedyReferences) { |
| Reference reference = referenceInfo.getReference(); |
| switch (reference.getCardinality()) { |
| case MANDATORY_UNARY: |
| throw new ReferenceViolationException("Reference of type " + reference.getInterfaceType() + " " |
| + "for service " + reference.getServiceClass().getName() + " is mandatory unary, " |
| + "unregistration of service with this interface failed."); |
| case MANDATORY_MULTIPLE: |
| case OPTIONAL_MULTIPLE: |
| case OPTIONAL_UNARY: |
| restartService(referenceInfo.getServiceRegistration()); |
| break; |
| default: |
| throw new RuntimeException("Unepxected cardinality: " + reference.getCardinality()); |
| } |
| } |
| } |
| |
| @Override |
| public ServiceReference getServiceReference(final String clazz) { |
| try { |
| ServiceReference[] serviceRefs = getServiceReferences(clazz, null); |
| if (serviceRefs != null && serviceRefs.length > 0) { |
| return serviceRefs[0]; |
| } |
| } |
| catch (InvalidSyntaxException ex) { |
| // should not happen |
| } |
| return null; |
| } |
| |
| // this is part of org.osgi.core 6.0.0 |
| public ServiceReference getServiceReference(Class clazz) { |
| return getServiceReference(clazz.getName()); |
| } |
| |
| @Override |
| public ServiceReference[] getServiceReferences(final String clazz, final String filter) throws InvalidSyntaxException { |
| Set<ServiceReference> result = new TreeSet<ServiceReference>(new Comparator<ServiceReference>() { |
| @Override |
| public int compare(ServiceReference o1, ServiceReference o2) { |
| // reverse sort order to get highest ranking first |
| return o2.compareTo(o1); |
| } |
| }); |
| 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()]); |
| } |
| } |
| |
| // this is part of org.osgi.core 6.0.0 |
| public Collection<ServiceReference> getServiceReferences(Class clazz, String filter) throws InvalidSyntaxException { |
| return ImmutableList.<ServiceReference>copyOf(getServiceReferences(clazz.getName(), filter)); |
| } |
| |
| @Override |
| public ServiceReference[] getAllServiceReferences(final String clazz, final String filter) throws InvalidSyntaxException { |
| // 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) { |
| try { |
| addServiceListener(serviceListener, null); |
| } |
| catch (InvalidSyntaxException ex) { |
| throw new RuntimeException(ex); |
| } |
| } |
| |
| @Override |
| public void addServiceListener(final ServiceListener serviceListener, final String filter) throws InvalidSyntaxException { |
| serviceListeners.put(serviceListener, createFilter(filter)); |
| } |
| |
| @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 ( Map.Entry<ServiceListener, Filter> entry : serviceListeners.entrySet()) { |
| if ( entry.getValue() == null || entry.getValue().match(serviceReference)) { |
| entry.getKey().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]; |
| } |
| |
| @Override |
| public String getProperty(final String s) { |
| // no mock implementation, simulate that no property is found and return null |
| return null; |
| } |
| |
| @Override |
| public File getDataFile(final String path) { |
| if (path == null) { |
| throw new IllegalArgumentException("Invalid path: " + path); |
| } |
| synchronized (this) { |
| if (dataFileBaseDir == null) { |
| dataFileBaseDir = Files.createTempDir(); |
| } |
| } |
| if (path.isEmpty()) { |
| return dataFileBaseDir; |
| } |
| else { |
| return new File(dataFileBaseDir, path); |
| } |
| } |
| |
| /** |
| * Deactivates all bundles registered in this mocked bundle context. |
| */ |
| public void shutdown() { |
| for (MockServiceRegistration serviceRegistration : ImmutableList.copyOf(registeredServices).reverse()) { |
| try { |
| MockOsgi.deactivate(serviceRegistration.getService(), this, serviceRegistration.getProperties()); |
| } |
| catch (NoScrMetadataException ex) { |
| // ignore, no deactivate method is available then |
| } |
| } |
| if (dataFileBaseDir != null) { |
| try { |
| FileUtils.deleteDirectory(dataFileBaseDir); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| |
| // --- unsupported operations --- |
| @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(); |
| } |
| |
| // this is part of org.osgi.core 6.0.0 |
| public Bundle getBundle(String location) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| // this is part of org.osgi.core 6.0.0 |
| public <S> ServiceRegistration registerService(Class<S> clazz, ServiceFactory factory, Dictionary<String, ?> properties) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| // this is part of org.osgi.core 6.0.0 |
| /* class org.osgi.framework.ServiceObjects does not exist in older OSGi versions |
| public ServiceObjects getServiceObjects(ServiceReference reference) { |
| throw new UnsupportedOperationException(); |
| } |
| */ |
| |
| } |