| /* |
| * 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.felix.dm.impl; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Proxy; |
| import java.util.AbstractMap; |
| import java.util.Arrays; |
| import java.util.Dictionary; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Supplier; |
| |
| import org.apache.felix.dm.Component; |
| import org.apache.felix.dm.ComponentDeclaration; |
| import org.apache.felix.dm.ServiceDependency; |
| import org.apache.felix.dm.context.AbstractDependency; |
| import org.apache.felix.dm.context.DependencyContext; |
| import org.apache.felix.dm.context.Event; |
| import org.apache.felix.dm.context.EventType; |
| import org.apache.felix.dm.tracker.ServiceTracker; |
| import org.apache.felix.dm.tracker.ServiceTrackerCustomizer; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.InvalidSyntaxException; |
| import org.osgi.framework.ServiceObjects; |
| import org.osgi.framework.ServiceReference; |
| |
| /** |
| * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> |
| */ |
| public class ServiceDependencyImpl extends AbstractDependency<ServiceDependency> implements ServiceDependency, ServiceTrackerCustomizer { |
| protected volatile ServiceTracker m_tracker; |
| protected volatile String m_swap; |
| protected volatile Class<?> m_trackedServiceName; |
| private volatile String m_trackedServiceFilter; |
| private volatile String m_trackedServiceFilterUnmodified; |
| private volatile ServiceReference<?> m_trackedServiceReference; |
| private volatile Object m_defaultImplementation; |
| private volatile Object m_defaultImplementationInstance; |
| private volatile Object m_nullObject; |
| private volatile boolean m_debug = false; |
| private volatile String m_debugKey; |
| private volatile long m_trackedServiceReferenceId; |
| private volatile boolean m_obtainServiceBeforeInjection = true; |
| |
| public ServiceDependency setDebug(String debugKey) { |
| m_debugKey = debugKey; |
| m_debug = true; |
| return this; |
| } |
| |
| /** |
| * Entry to wrap service properties behind a Map. |
| */ |
| private static final class ServicePropertiesMapEntry implements Map.Entry<String, Object> { |
| private final String m_key; |
| private Object m_value; |
| |
| public ServicePropertiesMapEntry(String key, Object value) { |
| m_key = key; |
| m_value = value; |
| } |
| |
| public String getKey() { |
| return m_key; |
| } |
| |
| public Object getValue() { |
| return m_value; |
| } |
| |
| public String toString() { |
| return m_key + "=" + m_value; |
| } |
| |
| public Object setValue(Object value) { |
| Object oldValue = m_value; |
| m_value = value; |
| return oldValue; |
| } |
| |
| @SuppressWarnings("unchecked") |
| public boolean equals(Object o) { |
| if (!(o instanceof Map.Entry)) { |
| return false; |
| } |
| Map.Entry<String, Object> e = (Map.Entry<String, Object>) o; |
| return eq(m_key, e.getKey()) && eq(m_value, e.getValue()); |
| } |
| |
| public int hashCode() { |
| return ((m_key == null) ? 0 : m_key.hashCode()) ^ ((m_value == null) ? 0 : m_value.hashCode()); |
| } |
| |
| private static final boolean eq(Object o1, Object o2) { |
| return (o1 == null ? o2 == null : o1.equals(o2)); |
| } |
| } |
| |
| /** |
| * Wraps service properties behind a Map. |
| */ |
| private final static class ServicePropertiesMap extends AbstractMap<String, Object> { |
| private final ServiceReference<?> m_ref; |
| |
| public ServicePropertiesMap(ServiceReference<?> ref) { |
| m_ref = ref; |
| } |
| |
| public Object get(Object key) { |
| return m_ref.getProperty(key.toString()); |
| } |
| |
| public int size() { |
| return m_ref.getPropertyKeys().length; |
| } |
| |
| public Set<Map.Entry<String, Object>> entrySet() { |
| Set<Map.Entry<String, Object>> set = new HashSet<>(); |
| String[] keys = m_ref.getPropertyKeys(); |
| for (int i = 0; i < keys.length; i++) { |
| set.add(new ServicePropertiesMapEntry(keys[i], m_ref.getProperty(keys[i]))); |
| } |
| return set; |
| } |
| } |
| |
| public ServiceDependencyImpl() { |
| } |
| |
| public ServiceDependencyImpl(ServiceDependencyImpl prototype) { |
| super(prototype); |
| m_trackedServiceName = prototype.m_trackedServiceName; |
| m_nullObject = prototype.m_nullObject; |
| m_trackedServiceFilter = prototype.m_trackedServiceFilter; |
| m_trackedServiceFilterUnmodified = prototype.m_trackedServiceFilterUnmodified; |
| m_trackedServiceReference = prototype.m_trackedServiceReference; |
| m_autoConfigInstance = prototype.m_autoConfigInstance; |
| m_defaultImplementation = prototype.m_defaultImplementation; |
| m_autoConfig = prototype.m_autoConfig; |
| m_obtainServiceBeforeInjection = prototype.m_obtainServiceBeforeInjection; |
| } |
| |
| // --- CREATION |
| |
| public ServiceDependency setCallbacks(Object instance, String added, String changed, String removed, String swapped) { |
| setCallbacks(instance, added, changed, removed); |
| m_swap = swapped; |
| return this; |
| } |
| |
| public ServiceDependency setCallbacks(String added, String changed, String removed, String swapped) { |
| setCallbacks(added, changed, removed); |
| m_swap = swapped; |
| return this; |
| } |
| |
| @Override |
| public ServiceDependency setDefaultImplementation(Object implementation) { |
| ensureNotActive(); |
| m_defaultImplementation = implementation; |
| return this; |
| } |
| |
| @Override |
| public ServiceDependency setService(Class<?> serviceName) { |
| setService(serviceName, null, null); |
| return this; |
| } |
| |
| public ServiceDependency setService(Class<?> serviceName, String serviceFilter) { |
| setService(serviceName, null, serviceFilter); |
| return this; |
| } |
| |
| public ServiceDependency setService(String serviceFilter) { |
| if (serviceFilter == null) { |
| throw new IllegalArgumentException("Service filter cannot be null."); |
| } |
| setService(null, null, serviceFilter); |
| return this; |
| } |
| |
| @SuppressWarnings("rawtypes") |
| public ServiceDependency setService(Class<?> serviceName, ServiceReference serviceReference) { |
| setService(serviceName, serviceReference, null); |
| return this; |
| } |
| |
| @Override |
| public ServiceDependency setDereference(boolean obtainServiceBeforeInjection) { |
| m_obtainServiceBeforeInjection = obtainServiceBeforeInjection; |
| return this; |
| } |
| |
| @Override |
| public void start() { |
| if (m_trackedServiceName != null) { |
| BundleContext ctx = m_component.getBundleContext(); |
| if (m_trackedServiceFilter != null) { |
| try { |
| m_tracker = new ServiceTracker(ctx, ctx.createFilter(m_trackedServiceFilter), this); |
| } catch (InvalidSyntaxException e) { |
| throw new IllegalStateException("Invalid filter definition for dependency: " |
| + m_trackedServiceFilter); |
| } |
| } else if (m_trackedServiceReference != null) { |
| m_tracker = new ServiceTracker(ctx, m_trackedServiceReference, this); |
| } else { |
| m_tracker = new ServiceTracker(ctx, m_trackedServiceName.getName(), this); |
| } |
| } else { |
| throw new IllegalStateException("Could not create tracker for dependency, no service name specified."); |
| } |
| if (m_debug) { |
| m_tracker.setDebug(m_debugKey); |
| } |
| m_tracker.open(); |
| super.start(); |
| } |
| |
| @Override |
| public void stop() { |
| m_tracker.close(); |
| m_tracker = null; |
| super.stop(); |
| } |
| |
| @Override |
| public Object addingService(@SuppressWarnings("rawtypes") ServiceReference reference) { |
| try { |
| ServiceEventImpl event = new ServiceEventImpl(m_component, reference, null); |
| if (obtainServiceBeforeInjecting()) { |
| Object service = event.getEvent(); // will dereference the service object. |
| if (service == null) { |
| // service concurrently removed, ignore |
| return null; |
| } |
| } |
| return event; |
| } catch (IllegalStateException e) { |
| // most likely our bundle is being stopped. Only log an exception if our component is enabled. |
| if (m_component.isActive()) { |
| m_component.getLogger().warn("could not handle service dependency for component %s", e, |
| m_component.getComponentDeclaration().getClassName()); |
| } |
| return null; |
| } |
| } |
| |
| @Override |
| public void addedService(@SuppressWarnings("rawtypes") ServiceReference reference, Object event) { |
| ServiceEventImpl evt = (ServiceEventImpl) event; |
| if (m_debug) { |
| System.out.println(m_debugKey + " addedService: ref=" + reference); |
| } |
| m_component.handleEvent(this, EventType.ADDED, evt); |
| } |
| |
| @Override |
| public void modifiedService(@SuppressWarnings("rawtypes") ServiceReference reference, Object event) { |
| ServiceEventImpl evt = (ServiceEventImpl) event; |
| m_component.handleEvent(this, EventType.CHANGED, evt); |
| } |
| |
| @Override |
| public void removedService(@SuppressWarnings("rawtypes") ServiceReference reference, Object event) { |
| ServiceEventImpl evt = (ServiceEventImpl) event; |
| m_component.handleEvent(this, EventType.REMOVED, evt); |
| } |
| |
| @SuppressWarnings("rawtypes") |
| @Override |
| public void swappedService(final ServiceReference reference, final Object service, final ServiceReference newReference, final Object newService) { |
| ServiceEventImpl evt = (ServiceEventImpl) service; |
| ServiceEventImpl newEvt = (ServiceEventImpl) newService; |
| |
| if (obtainServiceBeforeInjecting()) { |
| try { |
| newEvt.getEvent(); |
| } catch (IllegalStateException e) { |
| // most likely our bundle is being stopped. Only log an exception if our component is enabled. |
| if (m_component.isActive()) { |
| m_component.getLogger().warn("could not handle service dependency for component %s", e, |
| m_component.getComponentDeclaration().getClassName()); |
| } |
| return; |
| } |
| } |
| |
| if (m_swap != null) { |
| // it will not trigger a state change, but the actual swap should be scheduled to prevent things |
| // getting out of order. |
| // We delegate the swap handling to the ComponentImpl, which is the class responsible for state management. |
| // The ComponentImpl will first check if the component is in the proper state so the swap method can be invoked. |
| m_component.handleEvent(this, EventType.SWAPPED, evt, newEvt); |
| } else { |
| addedService(newReference, newService); |
| removedService(reference, service); |
| } |
| } |
| |
| @Override |
| public void invokeCallback(EventType type, Event ... events) { |
| switch (type) { |
| case ADDED: |
| if (m_add != null) { |
| invoke (m_add, events[0], getInstances()); |
| } |
| break; |
| case CHANGED: |
| if (m_change != null) { |
| invoke (m_change, events[0], getInstances()); |
| } |
| break; |
| case REMOVED: |
| if (m_remove != null) { |
| invoke (m_remove, events[0], getInstances()); |
| } |
| break; |
| case SWAPPED: |
| if (m_swap != null) { |
| ServiceEventImpl oldEvent = (ServiceEventImpl) events[0]; |
| ServiceEventImpl newEvent = (ServiceEventImpl) events[1]; |
| invokeSwap(m_swap, oldEvent, newEvent, getInstances()); |
| } |
| break; |
| } |
| } |
| |
| @Override |
| public Class<?> getAutoConfigType() { |
| return m_trackedServiceName; |
| } |
| |
| @Override |
| public DependencyContext createCopy() { |
| return new ServiceDependencyImpl(this); |
| } |
| |
| @Override |
| public String getName() { |
| StringBuilder sb = new StringBuilder(); |
| if (m_trackedServiceName != null) { |
| sb.append(m_trackedServiceName.getName()); |
| if (m_trackedServiceFilterUnmodified != null) { |
| sb.append(' '); |
| sb.append(m_trackedServiceFilterUnmodified); |
| } |
| } |
| if (m_trackedServiceReference != null) { |
| sb.append("{service.id=" + m_trackedServiceReference.getProperty(Constants.SERVICE_ID) + "}"); |
| } |
| return sb.toString(); |
| } |
| |
| @Override |
| public String getSimpleName() { |
| if (m_trackedServiceName != null) { |
| return m_trackedServiceName.getName(); |
| } |
| return null; |
| } |
| |
| @Override |
| public String getFilter() { |
| if (m_trackedServiceFilterUnmodified != null) { |
| return m_trackedServiceFilterUnmodified; |
| } else if (m_trackedServiceReference != null) { |
| return new StringBuilder("(").append(Constants.SERVICE_ID).append("=").append( |
| String.valueOf(m_trackedServiceReferenceId)).append(")").toString(); |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| public String getType() { |
| return "service"; |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public Dictionary<String, Object> getProperties() { |
| ServiceEventImpl se = (ServiceEventImpl) m_component.getDependencyEvent(this); |
| if (se != null) { |
| if (m_propagateCallbackInstance != null && m_propagateCallbackMethod != null) { |
| try { |
| CallbackTypeDef callbackInfo = new CallbackTypeDef(new Class[][] { { ServiceReference.class, Object.class }, { ServiceReference.class } }, |
| new Object[][] { { se.getReference(), se.getEvent() }, { se.getReference() } }); |
| return (Dictionary<String, Object>) InvocationUtil.invokeCallbackMethod(m_propagateCallbackInstance, m_propagateCallbackMethod, callbackInfo.m_sigs, callbackInfo.m_args); |
| } catch (InvocationTargetException e) { |
| m_component.getLogger().warn("Exception while invoking callback method", e.getCause()); |
| } catch (Throwable e) { |
| m_component.getLogger().warn("Exception while trying to invoke callback method", e); |
| } |
| throw new IllegalStateException("Could not invoke callback"); |
| } else { |
| Hashtable<String, Object> props = new Hashtable<>(); |
| String[] keys = se.getReference().getPropertyKeys(); |
| for (int i = 0; i < keys.length; i++) { |
| if (!(keys[i].equals(Constants.SERVICE_ID) || keys[i].equals(Constants.SERVICE_PID))) { |
| props.put(keys[i], se.getReference().getProperty(keys[i])); |
| } |
| } |
| return props; |
| } |
| } else { |
| throw new IllegalStateException("cannot find service reference"); |
| } |
| } |
| |
| /** Internal method to set the name, service reference and/or filter. */ |
| private void setService(Class<?> serviceName, ServiceReference<?> serviceReference, String serviceFilter) { |
| ensureNotActive(); |
| if (serviceName == null) { |
| m_trackedServiceName = Object.class; |
| } |
| else { |
| m_trackedServiceName = serviceName; |
| } |
| if (serviceFilter != null) { |
| m_trackedServiceFilterUnmodified = serviceFilter; |
| if (serviceName == null) { |
| m_trackedServiceFilter = serviceFilter; |
| } |
| else { |
| m_trackedServiceFilter = "(&(" + Constants.OBJECTCLASS + "=" + serviceName.getName() + ")" + serviceFilter + ")"; |
| } |
| } |
| else { |
| m_trackedServiceFilterUnmodified = null; |
| m_trackedServiceFilter = null; |
| } |
| if (serviceReference != null) { |
| m_trackedServiceReference = serviceReference; |
| if (serviceFilter != null) { |
| throw new IllegalArgumentException("Cannot specify both a filter and a service reference."); |
| } |
| m_trackedServiceReferenceId = (Long) m_trackedServiceReference.getProperty(Constants.SERVICE_ID); |
| } |
| else { |
| m_trackedServiceReference = null; |
| } |
| } |
| |
| @Override |
| public Object getDefaultService(boolean nullObject) { |
| Object service = null; |
| if (isAutoConfig()) { |
| service = getDefaultImplementation(); |
| if (service == null && nullObject) { |
| service = getNullObject(); |
| } |
| } |
| return service; |
| } |
| |
| private Object getNullObject() { |
| if (m_nullObject == null) { |
| Class<?> trackedServiceName; |
| trackedServiceName = m_trackedServiceName; |
| try { |
| m_nullObject = Proxy.newProxyInstance(trackedServiceName.getClassLoader(), |
| new Class[] { trackedServiceName }, new DefaultNullObject()); |
| } |
| catch (Throwable err) { |
| m_component.getLogger().err("Could not create null object for %s.", err, trackedServiceName); |
| } |
| } |
| return m_nullObject; |
| } |
| |
| private Object getDefaultImplementation() { |
| if (m_defaultImplementation != null) { |
| if (m_defaultImplementation instanceof Class) { |
| try { |
| m_defaultImplementationInstance = ((Class<?>) m_defaultImplementation).newInstance(); |
| } |
| catch (Throwable e) { |
| m_component.getLogger().err("Could not create default implementation instance of class %s.", e, |
| m_defaultImplementation); |
| } |
| } |
| else { |
| m_defaultImplementationInstance = m_defaultImplementation; |
| } |
| } |
| return m_defaultImplementationInstance; |
| } |
| |
| public void invoke(String method, Event e, Object[] instances) { |
| ServiceEventImpl se = (ServiceEventImpl) e; |
| // Be as lazy as possible, in case no properties or map has to be injected |
| Supplier<Dictionary<?,?>> properties = () -> se.getProperties(); |
| Supplier<ServicePropertiesMap> propertiesMap = () -> new ServicePropertiesMap(se.getReference()); |
| |
| m_component.invokeCallback(instances, method, |
| new Class[][]{ |
| {Component.class, ServiceReference.class, m_trackedServiceName}, |
| {Component.class, ServiceReference.class, Object.class}, |
| {Component.class, ServiceReference.class}, |
| {Component.class, m_trackedServiceName}, |
| {Component.class, Object.class}, |
| {Component.class}, |
| {Component.class, Map.class, m_trackedServiceName}, |
| {ServiceReference.class, m_trackedServiceName}, |
| {ServiceReference.class, Object.class}, |
| {ServiceReference.class}, |
| {m_trackedServiceName}, |
| {m_trackedServiceName, Map.class}, |
| {Map.class, m_trackedServiceName}, |
| {m_trackedServiceName, Dictionary.class}, |
| {Dictionary.class, m_trackedServiceName}, |
| {Object.class}, |
| {ServiceObjects.class}, |
| {}}, |
| |
| new Supplier[][]{ |
| new Supplier<?>[] {() -> m_component, () -> se.getReference(), () -> se.getEvent()}, |
| new Supplier<?>[] {() -> m_component, () -> se.getReference(), () -> se.getEvent()}, |
| new Supplier<?>[] {() -> m_component, () -> se.getReference()}, |
| new Supplier<?>[] {() -> m_component, () -> se.getEvent()}, |
| new Supplier<?>[] {() -> m_component, () -> se.getEvent()}, |
| new Supplier<?>[] {() -> m_component}, |
| new Supplier<?>[] {() -> m_component, () -> propertiesMap.get(), () -> se.getEvent()}, |
| new Supplier<?>[] {() -> se.getReference(), () -> se.getEvent()}, |
| new Supplier<?>[] {() -> se.getReference(), () -> se.getEvent()}, |
| new Supplier<?>[] {() -> se.getReference()}, |
| new Supplier<?>[] {() -> se.getEvent()}, |
| new Supplier<?>[] {() -> se.getEvent(), () -> propertiesMap.get()}, |
| new Supplier<?>[] {() -> propertiesMap.get(), () -> se.getEvent()}, |
| new Supplier<?>[] {() -> se.getEvent(), () -> properties.get()}, |
| new Supplier<?>[] {() -> properties.get(), () -> se.getEvent()}, |
| new Supplier<?>[] {() -> se.getEvent()}, |
| new Supplier<?>[] {() -> se.getServiceObjects()}, |
| {}}, |
| true // log if method is not found |
| ); |
| } |
| |
| private void invokeSwap(String swapMethod, ServiceEventImpl previous, ServiceEventImpl current, Object[] instances) { |
| if (m_debug) { |
| System.out.println("invoke swap: " + swapMethod + " on component " + m_component + ", instances: " + Arrays.toString(instances) + " - " + ((ComponentDeclaration)m_component).getState()); |
| } |
| try { |
| m_component.invokeCallback(instances, swapMethod, |
| new Class[][]{ |
| {m_trackedServiceName, m_trackedServiceName}, |
| {Object.class, Object.class}, |
| {ServiceReference.class, m_trackedServiceName, ServiceReference.class, m_trackedServiceName}, |
| {ServiceReference.class, Object.class, ServiceReference.class, Object.class}, |
| {Component.class, m_trackedServiceName, m_trackedServiceName}, |
| {Component.class, Object.class, Object.class}, |
| {Component.class, ServiceReference.class, m_trackedServiceName, ServiceReference.class, m_trackedServiceName}, |
| {Component.class, ServiceReference.class, Object.class, ServiceReference.class, Object.class}, |
| {ServiceReference.class, ServiceReference.class}, |
| {Component.class, ServiceReference.class, ServiceReference.class}, |
| {ServiceObjects.class, ServiceObjects.class}, |
| {Component.class, ServiceObjects.class, ServiceObjects.class}, |
| }, |
| |
| new Supplier[][]{ |
| new Supplier<?>[] {() -> previous.getEvent(), () -> current.getEvent()}, |
| new Supplier<?>[] {() -> previous.getEvent(), () -> current.getEvent()}, |
| new Supplier<?>[] {() -> previous.getReference(), () -> previous.getEvent(), () -> current.getReference(), () -> current.getEvent()}, |
| new Supplier<?>[] {() -> previous.getReference(), () -> previous.getEvent(), () -> current.getReference(), () -> current.getEvent()}, |
| new Supplier<?>[] {() -> m_component, () -> previous.getEvent(), () -> current.getEvent()}, |
| new Supplier<?>[] {() -> m_component, () -> previous.getEvent(), () -> current.getEvent()}, |
| new Supplier<?>[] {() -> m_component, () -> previous.getReference(), () -> previous.getEvent(), () -> current.getReference(), () -> current.getEvent()}, |
| new Supplier<?>[] {() -> m_component, () -> previous.getReference(), () -> previous.getEvent(), () -> current.getReference(), () -> current.getEvent()}, |
| new Supplier<?>[] {() -> previous.getReference(), () -> current.getReference()}, |
| new Supplier<?>[] {() -> m_component, () -> previous.getReference(), () -> current.getReference()}, |
| new Supplier<?>[] {() -> previous.getServiceObjects(), () -> current.getServiceObjects()}, |
| new Supplier<?>[] {() -> m_component, () -> previous.getServiceObjects(), () -> current.getServiceObjects()} |
| }, |
| |
| true); // log if not found |
| } catch (Throwable e) { |
| m_component.getLogger().err("Could not invoke swap callback", e); |
| } |
| } |
| |
| private boolean obtainServiceBeforeInjecting() { |
| return m_obtainServiceBeforeInjection && ! m_component.injectionDisabled(); |
| } |
| |
| } |