blob: 01408e6709748ce4dbcc584f6b9b052816ca4d2c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.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();
}
}