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