blob: 10407870ee7823d2a30208ce9b605489ea5ddd97 [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.ipojo.util;
import org.apache.felix.ipojo.ComponentInstance;
import org.apache.felix.ipojo.IPOJOServiceFactory;
import org.apache.felix.ipojo.dependency.impl.ServiceReferenceManager;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Abstract dependency model.
* This class is the parent class of every service dependency. It manages the most
* part of dependency management. This class creates an interface between the service
* tracker and the concrete dependency.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public abstract class DependencyModel {
/**
* Dependency state : BROKEN.
* A broken dependency cannot be fulfilled anymore. The dependency becomes
* broken when a used service disappears in the static binding policy.
*/
public static final int BROKEN = -1;
/**
* Dependency state : UNRESOLVED.
* A dependency is unresolved if the dependency is not valid and no service
* providers are available.
*/
public static final int UNRESOLVED = 0;
/**
* Dependency state : RESOLVED.
* A dependency is resolved if the dependency is optional or at least one
* provider is available.
*/
public static final int RESOLVED = 1;
/**
* Binding policy : Dynamic.
* In this policy, services can appears and departs without special treatment.
*/
public static final int DYNAMIC_BINDING_POLICY = 0;
/**
* Binding policy : Static.
* Once a service is used, if this service disappears the dependency becomes
* {@link DependencyModel#BROKEN}. The instance needs to be recreated.
*/
public static final int STATIC_BINDING_POLICY = 1;
/**
* Binding policy : Dynamic-Priority.
* In this policy, services can appears and departs. However, once a service
* with a highest ranking (according to the used comparator) appears, this
* new service is re-injected.
*/
public static final int DYNAMIC_PRIORITY_BINDING_POLICY = 2;
/**
* The service reference manager.
*/
protected final ServiceReferenceManager m_serviceReferenceManager;
/**
* The manager handling context sources.
*/
private final ContextSourceManager m_contextSourceManager;
/**
* Listener object on which invoking the {@link DependencyStateListener#validate(DependencyModel)}
* and {@link DependencyStateListener#invalidate(DependencyModel)} methods.
*/
private final DependencyStateListener m_listener;
/**
* The instance requiring the service.
*/
private final ComponentInstance m_instance;
/**
* Does the dependency bind several providers ?
*/
private boolean m_aggregate;
/**
* Is the dependency optional ?
*/
private boolean m_optional;
/**
* The required specification.
* Cannot change once set.
*/
private Class m_specification;
/**
* Bundle context used by the dependency.
* (may be a {@link org.apache.felix.ipojo.ServiceContext}).
*/
private BundleContext m_context;
/**
* The actual state of the dependency.
* {@link DependencyModel#UNRESOLVED} at the beginning.
*/
private int m_state;
/**
* The Binding policy of the dependency.
*/
private int m_policy = DYNAMIC_BINDING_POLICY;
/**
* The tracker used by this dependency to track providers.
*/
private Tracker m_tracker;
/**
* Map {@link ServiceReference} -> Service Object.
* This map stores service object, and so is able to handle
* iPOJO custom policies.
*/
private Map<ServiceReference, ServiceBindingHolder> m_serviceObjects = new HashMap<ServiceReference, ServiceBindingHolder>();
/**
* The current list of bound services.
*/
private List<ServiceReference> m_boundServices = new ArrayList<ServiceReference>();
/**
* The lock ensuring state consistency of the dependency.
* This lock can be acquired from all collaborators.
*/
private ReentrantReadWriteLock m_lock = new ReentrantReadWriteLock();
/**
* The listeners of the dependency model.
*/
private final List<DependencyModelListener> m_listeners = new ArrayList<DependencyModelListener>();
/**
* Creates a DependencyModel.
* If the dependency has no comparator and follows the
* {@link DependencyModel#DYNAMIC_PRIORITY_BINDING_POLICY} policy
* the OSGi Service Reference Comparator is used.
*
* @param specification the required specification
* @param aggregate is the dependency aggregate ?
* @param optional is the dependency optional ?
* @param filter the LDAP filter
* @param comparator the comparator object to sort references
* @param policy the binding policy
* @param context the bundle context (or service context)
* @param listener the dependency lifecycle listener to notify from dependency
* @param ci instance managing the dependency
* state changes.
*/
public DependencyModel(Class specification, boolean aggregate, boolean optional, Filter filter,
Comparator<ServiceReference> comparator, int policy,
BundleContext context, DependencyStateListener listener, ComponentInstance ci) {
m_specification = specification;
m_aggregate = aggregate;
m_optional = optional;
m_instance = ci;
m_policy = policy;
// If the dynamic priority policy is chosen, and we have no comparator, fix it to OSGi standard service reference comparator.
if (m_policy == DYNAMIC_PRIORITY_BINDING_POLICY && comparator == null) {
comparator = new ServiceReferenceRankingComparator();
}
if (context != null) {
m_context = context;
// If the context is null, it gonna be set later using the setBundleContext method.
}
m_serviceReferenceManager = new ServiceReferenceManager(this, filter, comparator);
if (filter != null) {
try {
m_contextSourceManager = new ContextSourceManager(this);
} catch (InvalidSyntaxException e) {
throw new IllegalArgumentException(e);
}
} else {
m_contextSourceManager = null;
}
m_state = UNRESOLVED;
m_listener = listener;
}
/**
* Opens the tracking.
* This method computes the dependency state.
* <p/>
* As the dependency is starting, locking is not required here.
*
* @see DependencyModel#computeAndSetDependencyState()
*/
public void start() {
m_state = UNRESOLVED;
m_tracker = new Tracker(m_context, m_specification.getName(), m_serviceReferenceManager);
m_serviceReferenceManager.open();
m_tracker.open();
if (m_contextSourceManager != null) {
m_contextSourceManager.start();
}
computeAndSetDependencyState();
}
/**
* Gets the bundle context used by the dependency.
* @return the bundle context
*/
public BundleContext getBundleContext() {
// Immutable member, no lock required.
return m_context;
}
/**
* This callback is called by ranking interceptor to notify the dependency that the selected service set has
* changed and must be recomputed.
*/
public void invalidateSelectedServices() {
m_serviceReferenceManager.invalidateSelectedServices();
}
public void invalidateMatchingServices() {
m_serviceReferenceManager.invalidateMatchingServices();
}
/**
* Closes the tracking.
* The dependency becomes {@link DependencyModel#UNRESOLVED}
* at the end of this method.
*/
public void stop() {
// We're stopping, we must take the exclusive lock
try {
acquireWriteLockIfNotHeld();
if (m_tracker != null) {
m_tracker.close();
m_tracker = null;
}
m_boundServices.clear();
m_serviceReferenceManager.close();
ungetAllServices();
m_state = UNRESOLVED;
if (m_contextSourceManager != null) {
m_contextSourceManager.stop();
}
} finally {
releaseWriteLockIfHeld();
}
}
/**
* Ungets all 'get' service references.
* This also clears the service object map.
* The method is called while holding the exclusive lock.
*/
private void ungetAllServices() {
for (Map.Entry<ServiceReference, ServiceBindingHolder> entry : m_serviceObjects.entrySet()) {
ServiceReference ref = entry.getKey();
ServiceBindingHolder sbh = entry.getValue();
if (m_tracker != null) {
m_tracker.ungetService(ref);
}
if (sbh.factory != null) {
sbh.factory.ungetService(m_instance, sbh.service);
}
m_serviceReferenceManager.unweavingServiceBinding(sbh);
}
m_serviceObjects.clear();
}
/**
* Is the reference set frozen (cannot change anymore)?
* This method must be override by concrete dependency to support
* the static binding policy. In fact, this method allows optimizing
* the static dependencies to become frozen only when needed.
* This method returns <code>false</code> by default.
* The method must always return <code>false</code> for non-static dependencies.
*
* @return <code>true</code> if the reference set is frozen.
*/
public boolean isFrozen() {
return false;
}
/**
* Unfreezes the dependency.
* This method must be override by concrete dependency to support
* the static binding policy. This method is called after tracking restarting.
*/
public void unfreeze() {
// nothing to do
}
/**
* Does the service reference match ? This method must be overridden by
* concrete dependencies if they need advanced testing on service reference
* (that cannot be expressed in the LDAP filter). By default this method
* returns <code>true</code>.
*
* @param ref the tested reference.
* @return <code>true</code> if the service reference matches.
*/
public boolean match(ServiceReference ref) {
return true;
}
/**
* Computes the actual dependency state.
* This methods invokes the {@link DependencyStateListener}.
* If this method is called without the write lock, it takes it. Anyway, the lock will be released before called
* the
* callbacks.
*/
private void computeAndSetDependencyState() {
try {
boolean mustCallValidate = false;
boolean mustCallInvalidate = false;
acquireWriteLockIfNotHeld();
// The dependency is broken, nothing else can be done
if (m_state == BROKEN) {
return;
}
if (m_optional || !m_serviceReferenceManager.isEmpty()) {
// The dependency is valid
if (m_state == UNRESOLVED) {
m_state = RESOLVED;
mustCallValidate = true;
}
} else {
// The dependency is invalid
if (m_state == RESOLVED) {
m_state = UNRESOLVED;
mustCallInvalidate = true;
}
}
// Invoke callback in a non-synchronized region
// First unlock the lock
releaseWriteLockIfHeld();
// Now we can call the callbacks
if (mustCallInvalidate) {
invalidate();
} else if (mustCallValidate) {
validate();
}
} finally {
// If we are still holding the exclusive lock, unlock it.
releaseWriteLockIfHeld();
}
}
/**
* Gets the first bound service reference.
*
* @return <code>null</code> if no more provider is available,
* else returns the first reference from the matching set.
*/
public ServiceReference getServiceReference() {
// Read lock required
try {
acquireReadLockIfNotHeld();
if (m_boundServices.isEmpty()) {
return null;
} else {
return m_boundServices.get(0);
}
} finally {
releaseReadLockIfHeld();
}
}
/**
* Gets bound service references.
*
* @return the sorted (if a comparator is used) array of matching service
* references, <code>null</code> if no references are available.
*/
public ServiceReference[] getServiceReferences() {
// Read lock required
try {
acquireReadLockIfNotHeld();
if (m_boundServices.isEmpty()) {
return null;
}
return m_boundServices.toArray(new ServiceReference[m_boundServices.size()]);
} finally {
releaseReadLockIfHeld();
}
}
/**
* Gets the list of currently used service references.
* If no service references, returns <code>null</code>
*
* @return the list of used reference (according to the service tracker).
*/
public List<ServiceReference> getUsedServiceReferences() {
// Read lock required
try {
acquireReadLockIfNotHeld();
// The list must confront actual matching services with already get services from the tracker.
int size = m_boundServices.size();
List<ServiceReference> usedByTracker = null;
if (m_tracker != null) {
usedByTracker = m_tracker.getUsedServiceReferences();
}
if (size == 0 || usedByTracker == null) {
return null;
}
List<ServiceReference> list = new ArrayList<ServiceReference>(1);
for (ServiceReference ref : m_boundServices) {
if (usedByTracker.contains(ref)) {
list.add(ref); // Add the service in the list.
if (!isAggregate()) { // IF we are not multiple, return the list when the first element is found.
return list;
}
}
}
return list;
} finally {
releaseReadLockIfHeld();
}
}
/**
* @return the component instance on which this dependency is plugged.
*/
public ComponentInstance getComponentInstance() {
// No lock required as m_instance is final
return m_instance;
}
/**
* Gets the number of actual matching references.
*
* @return the number of matching references
*/
public int getSize() {
try {
acquireReadLockIfNotHeld();
return m_boundServices.size();
} finally {
releaseReadLockIfHeld();
}
}
/**
* Concrete dependency callback.
* This method is called when a new service needs to be
* re-injected in the underlying concrete dependency.
*
* @param ref the service reference to inject.
*/
public abstract void onServiceArrival(ServiceReference ref);
/**
* Concrete dependency callback.
* This method is called when a used service (already injected) is leaving.
*
* @param ref the leaving service reference.
*/
public abstract void onServiceDeparture(ServiceReference ref);
/**
* Concrete dependency callback.
* This method is called when a used service (already injected) is modified.
*
* @param ref the modified service reference.
*/
public abstract void onServiceModification(ServiceReference ref);
/**
* Concrete dependency callback.
* This method is called when the dependency is reconfigured and when this
* reconfiguration implies changes on the matching service set ( and by the
* way on the injected service).
*
* @param departs the service leaving the matching set.
* @param arrivals the service arriving in the matching set.
*/
public abstract void onDependencyReconfiguration(ServiceReference[] departs, ServiceReference[] arrivals);
/**
* Calls the listener callback to notify the new state of the current
* dependency.
* No lock hold when calling this callback.
*/
private void invalidate() {
m_listener.invalidate(this);
// Notify dependency invalidation to listeners
notifyListeners(DependencyEventType.INVALIDATE, null, null);
}
/**
* Calls the listener callback to notify the new state of the current
* dependency.
* No lock hold when calling this callback.
*/
private void validate() {
m_listener.validate(this);
// Notify dependency validation to listeners
notifyListeners(DependencyEventType.VALIDATE, null, null);
}
/**
* Gets the actual state of the dependency.
* @return the state of the dependency.
*/
public int getState() {
try {
acquireReadLockIfNotHeld();
return m_state;
} finally {
releaseReadLockIfHeld();
}
}
/**
* Gets the tracked specification.
*
* @return the Class object tracked by the dependency.
*/
public Class getSpecification() {
return m_specification;
}
/**
* Sets the required specification of this service dependency.
* This operation is not supported if the dependency tracking has already begun.
* So, we don't have to hold a lock.
*
* @param specification the required specification.
*/
public void setSpecification(Class specification) {
if (m_tracker == null) {
m_specification = specification;
} else {
throw new UnsupportedOperationException("Dynamic specification change is not yet supported");
}
}
/**
* Acquires the write lock only and only if the write lock is not already held by the current thread.
* @return {@literal true} if the lock was acquired within the method, {@literal false} otherwise.
*/
public boolean acquireWriteLockIfNotHeld() {
if (! m_lock.isWriteLockedByCurrentThread()) {
m_lock.writeLock().lock();
return true;
}
return false;
}
/**
* Releases the write lock only and only if the write lock is held by the current thread.
* @return {@literal true} if the lock has no more holders, {@literal false} otherwise.
*/
public boolean releaseWriteLockIfHeld() {
if (m_lock.isWriteLockedByCurrentThread()) {
m_lock.writeLock().unlock();
}
return m_lock.getWriteHoldCount() == 0;
}
/**
* Acquires the read lock only and only if no read lock is already held by the current thread.
*
* As the introspection methods provided by this method are java 6+, we just take a read lock.
* @return {@literal true} if the lock was acquired within the method, {@literal false} otherwise.
*/
public boolean acquireReadLockIfNotHeld() {
m_lock.readLock().lock();
return true;
}
/**
* Releases the read lock only and only if the read lock is held by the current thread.
* * As the introspection methods provided by this method are java 6+, we just unlock the read lock.
* @return {@literal true} if the lock has no more holders, {@literal false} otherwise.
*/
public boolean releaseReadLockIfHeld() {
try {
m_lock.readLock().unlock();
} catch (IllegalMonitorStateException e) {
// Oupsy we were not holding the lock...
}
return true;
}
/**
* Returns the dependency filter (String form).
*
* @return the String form of the LDAP filter used by this dependency,
* <code>null</code> if not set.
*/
public String getFilter() {
Filter filter;
try {
acquireReadLockIfNotHeld();
filter = m_serviceReferenceManager.getFilter();
} finally {
releaseReadLockIfHeld();
}
if (filter == null) {
return null;
} else {
return filter.toString();
}
}
/**
* Sets the filter of the dependency. This method recomputes the
* matching set and call the onDependencyReconfiguration callback.
*
* @param filter the new LDAP filter.
*/
public void setFilter(Filter filter) {
try {
acquireWriteLockIfNotHeld();
ServiceReferenceManager.ChangeSet changeSet = m_serviceReferenceManager.setFilter(filter, m_tracker);
// We call this method when holding the lock, but the method may decide to release the lock to invoke
// callbacks, so we must defensively unlock the lock in the finally block.
applyReconfiguration(changeSet);
} finally {
releaseWriteLockIfHeld();
}
}
/**
* Applies the given reconfiguration.
* This method check if the current thread is holding the write lock, if not, acquire it.
* The lock will be released before calling callbacks. As a consequence, the caller has to check if the lock is
* still hold when this method returns.
* @param changeSet the reconfiguration changes
*/
public void applyReconfiguration(ServiceReferenceManager.ChangeSet changeSet) {
List<ServiceReference> arr = new ArrayList<ServiceReference>();
List<ServiceReference> dep = new ArrayList<ServiceReference>();
try {
acquireWriteLockIfNotHeld();
if (m_tracker == null) {
// Nothing else to do.
return;
} else {
// Update bindings
m_boundServices.clear();
if (m_aggregate) {
m_boundServices = new ArrayList<ServiceReference>(changeSet.selected);
arr = changeSet.arrivals;
dep = changeSet.departures;
} else {
ServiceReference used = null;
if (!m_boundServices.isEmpty()) {
used = m_boundServices.get(0);
}
if (!changeSet.selected.isEmpty()) {
final ServiceReference best = changeSet.newFirstReference;
// We didn't a provider
if (used == null) {
// We are not bound with anyone yet, so take the first of the selected set
m_boundServices.add(best);
arr.add(best);
} else {
// A provider was already bound, did we changed ?
if (changeSet.selected.contains(used)) {
// We are still valid - but in dynamic priority, we may have to change
if (getBindingPolicy() == DYNAMIC_PRIORITY_BINDING_POLICY && used != best) {
m_boundServices.add(best);
dep.add(used);
arr.add(best);
} else {
// We restore the old binding.
m_boundServices.add(used);
}
} else {
// The used service has left.
m_boundServices.add(best);
dep.add(used);
arr.add(best);
}
}
} else {
// We don't have any service anymore
if (used != null) {
arr.add(used);
}
}
}
}
} finally {
releaseWriteLockIfHeld();
}
// This method releases the exclusive lock.
computeAndSetDependencyState();
// As the previous method has released the lock, we can call the callback safely.
onDependencyReconfiguration(
dep.toArray(new ServiceReference[dep.size()]),
arr.toArray(new ServiceReference[arr.size()]));
// Notify dependency reconfiguration to listeners
notifyListeners(DependencyEventType.RECONFIGURED, null, null);
}
public boolean isAggregate() {
try {
acquireReadLockIfNotHeld();
return m_aggregate;
} finally {
releaseReadLockIfHeld();
}
}
/**
* Sets the aggregate attribute of the current dependency.
* If the tracking is opened, it will call arrival and departure callbacks.
*
* @param isAggregate the new aggregate attribute value.
*/
public void setAggregate(boolean isAggregate) {
// Acquire the write lock here.
acquireWriteLockIfNotHeld();
List<ServiceReference> arrivals = new ArrayList<ServiceReference>();
List<ServiceReference> departures = new ArrayList<ServiceReference>();
try {
if (m_tracker == null) { // Not started ...
m_aggregate = isAggregate;
} else {
// We become aggregate.
if (!m_aggregate && isAggregate) {
m_aggregate = true;
// Call the callback on all non already injected service.
if (m_state == RESOLVED) {
for (ServiceReference ref : m_serviceReferenceManager.getSelectedServices()) {
if (!m_boundServices.contains(ref)) {
m_boundServices.add(ref);
arrivals.add(ref);
}
}
}
} else if (m_aggregate && !isAggregate) {
m_aggregate = false;
// We become non-aggregate.
if (m_state == RESOLVED) {
List<ServiceReference> list = new ArrayList<ServiceReference>(m_boundServices);
for (int i = 1; i < list.size(); i++) { // The loop begin at 1, as the 0 stays injected.
m_boundServices.remove(list.get(i));
departures.add(list.get(i));
}
}
}
// Else, do nothing.
}
} finally {
releaseWriteLockIfHeld();
}
// TODO shouldn't we call onDependencyReconfiguration here????
// Now call callbacks, the lock is not held anymore
// Only one of the list is not empty..
try {
acquireReadLockIfNotHeld();
for (ServiceReference ref : arrivals) {
onServiceArrival(ref);
// Notify service binding to listeners
notifyListeners(DependencyEventType.BINDING, ref, m_serviceObjects.get(ref).service);
}
for (ServiceReference ref : departures) {
onServiceDeparture(ref);
// Notify service unbinding to listeners
notifyListeners(DependencyEventType.UNBINDING, ref, m_serviceObjects.get(ref).service);
}
} finally {
releaseReadLockIfHeld();
}
}
/**
* Sets the optionality attribute of the current dependency.
*
* @param isOptional the new optional attribute value.
*/
public void setOptionality(boolean isOptional) {
try {
acquireWriteLockIfNotHeld();
m_optional = isOptional;
computeAndSetDependencyState();
} finally {
releaseWriteLockIfHeld();
}
}
public boolean isOptional() {
try {
acquireReadLockIfNotHeld();
return m_optional;
} finally {
releaseReadLockIfHeld();
}
}
/**
* Gets the used binding policy.
*
* @return the current binding policy.
*/
public int getBindingPolicy() {
try {
acquireReadLockIfNotHeld();
return m_policy;
} finally {
releaseReadLockIfHeld();
}
}
/**
* Gets the used comparator name.
* <code>null</code> if no comparator (i.e. the OSGi one is used).
*
* @return the comparator class name or <code>null</code> if the dependency doesn't use a comparator.
*/
public String getComparator() {
final Comparator<ServiceReference> comparator;
try {
acquireReadLockIfNotHeld();
comparator = m_serviceReferenceManager.getComparator();
} finally {
releaseReadLockIfHeld();
}
if (comparator != null) {
return comparator.getClass().getName();
} else {
return null;
}
}
public void setComparator(Comparator<ServiceReference> cmp) {
try {
acquireWriteLockIfNotHeld();
m_serviceReferenceManager.setComparator(cmp);
} finally {
releaseWriteLockIfHeld();
}
}
/**
* Sets the bundle context used by this dependency.
* This operation is not supported if the tracker is already opened, and as a consequence does not require locking.
*
* @param context the bundle context or service context to use
*/
public void setBundleContext(BundleContext context) {
if (m_tracker == null) { // Not started ...
m_context = context;
} else {
throw new UnsupportedOperationException("Dynamic bundle (i.e. service) context change is not supported");
}
}
/**
* Gets a service object for the given reference.
* The service object is stored to handle custom policies.
*
* @param ref the wanted service reference
* @return the service object attached to the given reference
*/
public Object getService(ServiceReference ref) {
return getService(ref, true);
}
/**
* Gets a service object for the given reference.
*
* @param ref the wanted service reference
* @param store enables / disables the storing of the reference.
* @return the service object attached to the given reference
*/
public Object getService(ServiceReference ref, boolean store) {
if (m_tracker == null) {
// The tracker is already closed, we can't access the service anymore.
return null;
}
// If we already have the service object, just return it.
if (m_serviceObjects.containsKey(ref)) {
return m_serviceObjects.get(ref).service;
}
ServiceBindingHolder holder = null;
Object svc = m_tracker.getService(ref);
IPOJOServiceFactory factory = null;
if (svc instanceof IPOJOServiceFactory) {
factory = (IPOJOServiceFactory) svc;
svc = factory.getService(m_instance);
}
holder = new ServiceBindingHolder(ref, factory, svc);
svc = m_serviceReferenceManager.weavingServiceBinding(holder);
if (store) {
try {
acquireWriteLockIfNotHeld();
// The service object may have been modified by the interceptor, update the holder
if (svc != holder.service) {
m_serviceObjects.put(ref, new ServiceBindingHolder(ref, factory, svc));
} else {
m_serviceObjects.put(ref, holder);
}
} finally {
releaseWriteLockIfHeld();
}
}
return svc;
}
/**
* Ungets a used service reference.
*
* @param ref the reference to unget.
*/
public void ungetService(ServiceReference ref) {
m_tracker.ungetService(ref);
ServiceBindingHolder sbh;
try {
acquireWriteLockIfNotHeld();
sbh = m_serviceObjects.remove(ref);
} finally {
releaseWriteLockIfHeld();
}
// Call the callback outside the lock.
if (sbh != null && sbh.factory != null) {
sbh.factory.ungetService(m_instance, sbh.service);
}
m_serviceReferenceManager.unweavingServiceBinding(sbh);
}
public ContextSourceManager getContextSourceManager() {
// Final member, no lock required.
return m_contextSourceManager;
}
/**
* Gets the dependency id.
*
* @return the dependency id. Specification name by default.
*/
public String getId() {
// Immutable, no lock required.
return getSpecification().getName();
}
/**
* Callbacks call by the ServiceReferenceManager when the selected service set has changed.
* @param set the change set.
*/
public void onChange(ServiceReferenceManager.ChangeSet set) {
try {
acquireWriteLockIfNotHeld();
// First handle the static case with a frozen state
if (isFrozen() && getState() != BROKEN) {
for (ServiceReference ref : set.departures) {
// Check if any of the service that have left was in used.
if (m_boundServices.contains(ref)) {
// Static dependency broken.
m_state = BROKEN;
// We are going to call callbacks, releasing the lock.
ServiceBindingHolder sbh = m_serviceObjects.get(ref);
releaseWriteLockIfHeld();
// Notify listeners
notifyListeners(DependencyEventType.UNBINDING, ref, sbh.service);
notifyListeners(DependencyEventType.DEPARTURE, ref, null);
invalidate(); // This will invalidate the instance.
m_instance.stop(); // Stop the instance
unfreeze();
m_instance.start();
return;
}
}
}
List<ServiceReference> arrivals = new ArrayList<ServiceReference>();
List<ServiceReference> departures = new ArrayList<ServiceReference>();
// Manage departures
// We unbind all bound services that are leaving.
for (ServiceReference ref : set.departures) {
if (m_boundServices.contains(ref)) {
// We were using the reference
m_boundServices.remove(ref);
departures.add(ref);
}
}
// Manage arrivals
// For aggregate dependencies, call onServiceArrival for all services not-yet-bound and in the order of the
// selection.
if (m_aggregate) {
// If the dependency is not already in used,
// the bindings must be sorted as in set.selected
if (m_serviceObjects.isEmpty() || DYNAMIC_PRIORITY_BINDING_POLICY == getBindingPolicy()) {
m_boundServices.clear();
m_boundServices.addAll(set.selected);
}
// Now we notify from the arrival.
// If we didn't add the reference yet, we add it.
for (ServiceReference ref : set.arrivals) {
// We bind all not-already bound services, so it's an arrival
if (!m_boundServices.contains(ref)) {
m_boundServices.add(ref);
}
arrivals.add(ref);
}
} else {
if (!set.selected.isEmpty()) {
final ServiceReference best = set.selected.get(0);
// We have a provider
if (m_boundServices.isEmpty()) {
// We are not bound with anyone yet, so take the first of the selected set
m_boundServices.add(best);
arrivals.add(best);
} else {
final ServiceReference current = m_boundServices.get(0);
// We are already bound, to the rebinding decision depends on the binding strategy
if (getBindingPolicy() == DYNAMIC_PRIORITY_BINDING_POLICY) {
// Rebinding in the DP binding policy if the bound one if not the new best one.
if (current != best) {
m_boundServices.remove(current);
m_boundServices.add(best);
departures.add(current);
arrivals.add(best);
}
} else {
// In static and dynamic binding policy, if the service is not yet used and the new best is not
// the currently selected one, we should switch.
boolean isUsed = m_serviceObjects.containsKey(current);
if (!isUsed && current != best) {
m_boundServices.remove(current);
m_boundServices.add(best);
departures.add(current);
arrivals.add(best);
}
}
}
}
}
// Before leaving the protected region, copy used services.
Map<ServiceReference, ServiceBindingHolder> services = new HashMap<ServiceReference, ServiceBindingHolder>(m_serviceObjects);
// Leaving the locked region to invoke callbacks
releaseWriteLockIfHeld();
for (ServiceReference ref : departures) {
onServiceDeparture(ref);
// Notify service unbinding to listeners
final ServiceBindingHolder sbh = services.get(ref);
if (sbh != null) {
notifyListeners(DependencyEventType.UNBINDING, ref, sbh.service);
} else {
notifyListeners(DependencyEventType.UNBINDING, ref, null);
}
// Unget the service reference.
ungetService(ref);
}
for (ServiceReference ref : arrivals) {
onServiceArrival(ref);
// Notify service binding to listeners
final ServiceBindingHolder sbh = services.get(ref);
if (sbh != null) {
notifyListeners(DependencyEventType.BINDING, ref, sbh.service);
} else {
notifyListeners(DependencyEventType.BINDING, ref, null);
}
}
// Do we have a modified service ?
if (set.modified != null && m_boundServices.contains(set.modified)) {
onServiceModification(set.modified);
// TODO call boundServiceModified on listeners???
}
// Did our state changed ?
// this method will manage its own synchronization.
computeAndSetDependencyState();
} finally {
releaseWriteLockIfHeld();
}
}
public ServiceReferenceManager getServiceReferenceManager() {
return m_serviceReferenceManager;
}
public Tracker getTracker() {
return m_tracker;
}
public enum DependencyEventType {
VALIDATE,
INVALIDATE,
ARRIVAL,
MODIFIED,
DEPARTURE,
BINDING,
UNBINDING,
RECONFIGURED
}
/**
* Add the given listener to the dependency model's list of listeners.
*
* @param listener the {@code DependencyModelListener} object to be added
* @throws NullPointerException if {@code listener} is {@code null}
*/
public void addListener(DependencyModelListener listener) {
if (listener == null) {
throw new NullPointerException("null listener");
}
synchronized (m_listeners) {
m_listeners.add(listener);
}
}
/**
* Remove the given listener from the dependency model's list of listeners.
*
* @param listener the {@code DependencyModelListener} object to be removed
* @throws NullPointerException if {@code listener} is {@code null}
* @throws NoSuchElementException if {@code listener} wasn't present in the dependency model's list of listeners
*/
public void removeListener(DependencyModelListener listener) {
if (listener == null) {
throw new NullPointerException("null listener");
}
synchronized (m_listeners) {
// We definitely cannot rely on listener's equals method...
// ...so we need to manually search for the listener, using ==.
int i = -1;
for(int j = m_listeners.size() -1; j>=0 ; j--) {
if (m_listeners.get(j) == listener) {
// Found!
i = j;
break;
}
}
if (i != -1) {
m_listeners.remove(i);
} else {
throw new NoSuchElementException("no such listener");
}
}
}
/**
* Notify all listeners that a change has occurred in this dependency model.
*
* @param type the type of event
* @param service the reference of the concerned service (may be null)
* @param object the concerned service object (may be null)
*/
public void notifyListeners(DependencyEventType type, ServiceReference<?> service, Object object) {
// Get a snapshot of the listeners
List<DependencyModelListener> tmp;
synchronized (m_listeners) {
tmp = new ArrayList<DependencyModelListener>(m_listeners);
}
// Do notify, outside the m_listeners lock
for (DependencyModelListener l : tmp) {
try {
switch (type) {
case VALIDATE:
l.validate(this);
break;
case INVALIDATE:
l.invalidate(this);
break;
case ARRIVAL:
l.matchingServiceArrived(this, service);
break;
case MODIFIED:
l.matchingServiceModified(this, service);
break;
case DEPARTURE:
l.matchingServiceDeparted(this, service);
break;
case BINDING:
l.serviceBound(this, service, object);
break;
case UNBINDING:
l.serviceUnbound(this, service, object);
break;
case RECONFIGURED:
l.reconfigured(this);
break;
}
} catch (Throwable e) {
// Put a warning on the logger, and continue
getComponentInstance().getFactory().getLogger().log(Log.WARNING,
String.format(
"[%s] A DependencyModelListener has failed: %s",
getComponentInstance().getInstanceName(),
e.getMessage())
, e);
}
}
}
/**
* Removes all the listeners from this dependency before it gets disposed.
*/
public void cleanup() {
synchronized (m_listeners) {
m_listeners.clear();
}
}
/**
* Service binding structure.
*/
public class ServiceBindingHolder {
public final Object service;
public final IPOJOServiceFactory factory;
public final ServiceReference reference;
private ServiceBindingHolder(ServiceReference reference, IPOJOServiceFactory factory, Object service) {
this.service = service;
this.factory = factory;
this.reference = reference;
}
private ServiceBindingHolder(ServiceReference reference, Object service) {
this.service = service;
this.factory = null;
this.reference = reference;
}
}
}