/* | |
* 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; | |
} | |
} | |
} |