FELIX-387 Fix support for reference service target properties

(+ fix in ImmediateComponentManager.updated to ensure new configuration is actually used on reactivate)

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@580971 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/scr/src/main/java/org/apache/felix/scr/AbstractComponentManager.java b/scr/src/main/java/org/apache/felix/scr/AbstractComponentManager.java
index 3bdd255..7fb7a5c 100644
--- a/scr/src/main/java/org/apache/felix/scr/AbstractComponentManager.java
+++ b/scr/src/main/java/org/apache/felix/scr/AbstractComponentManager.java
@@ -324,10 +324,16 @@
 
             // Before creating the implementation object, we are going to
             // test if all the mandatory dependencies are satisfied
+            Dictionary properties = getProperties();
             Iterator it = m_dependencyManagers.iterator();
             while ( it.hasNext() )
             {
                 DependencyManager dm = ( DependencyManager ) it.next();
+
+                // ensure the target filter is correctly set
+                dm.setTargetFilter( properties );
+
+                // check whether the service is satisfied
                 if ( !dm.isSatisfied() )
                 {
                     // at least one dependency is not satisfied
diff --git a/scr/src/main/java/org/apache/felix/scr/DependencyManager.java b/scr/src/main/java/org/apache/felix/scr/DependencyManager.java
index 8683676..acacfdf 100644
--- a/scr/src/main/java/org/apache/felix/scr/DependencyManager.java
+++ b/scr/src/main/java/org/apache/felix/scr/DependencyManager.java
@@ -24,12 +24,14 @@
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Dictionary;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceEvent;
 import org.osgi.framework.ServiceListener;
@@ -48,10 +50,10 @@
     private static final int STATE_MASK = AbstractComponentManager.STATE_UNSATISFIED
         | AbstractComponentManager.STATE_ACTIVATING | AbstractComponentManager.STATE_ACTIVE
         | AbstractComponentManager.STATE_REGISTERED | AbstractComponentManager.STATE_FACTORY;
-    
+
     // the ServiceReference class instance
     private static final Class SERVICE_REFERENCE_CLASS = ServiceReference.class;
-    
+
     // pseudo service to mark a bound service without actual service instance
     private static final Object BOUND_SERVICE_SENTINEL = new Object();
 
@@ -63,22 +65,29 @@
 
     // The map of bound services indexed by their ServiceReference
     private Map m_bound;
-    
+
     // the number of matching services registered in the system
     private int m_size;
 
     // the bind method
     private Method m_bind;
-    
+
     // whether the bind method takes a service reference
     private boolean m_bindUsesReference;
-    
+
     // the unbind method
     private Method m_unbind;
-    
+
     // whether the unbind method takes a service reference
     private boolean m_unbindUsesReference;
 
+    // the target service filter string
+    private String m_target;
+
+    // the target service filter
+    private Filter m_targetFilter;
+
+
     /**
      * Constructor that receives several parameters.
      *
@@ -91,17 +100,16 @@
         m_dependencyMetadata = dependency;
         m_bound = Collections.synchronizedMap( new HashMap() );
 
+        // setup the target filter from component descriptor
+        setTargetFilter( m_dependencyMetadata.getTarget() );
+
         // register the service listener
         String filterString = "(" + Constants.OBJECTCLASS + "=" + dependency.getInterface() + ")";
-        if ( dependency.getTarget() != null )
-        {
-            filterString = "(&" + filterString + dependency.getTarget() + ")";
-        }
         componentManager.getActivator().getBundleContext().addServiceListener( this, filterString );
 
         // get the current number of registered services available
         ServiceReference refs[] = getServiceReferences();
-        m_size = (refs == null) ? 0 : refs.length;
+        m_size = ( refs == null ) ? 0 : refs.length;
     }
 
 
@@ -116,15 +124,15 @@
         switch ( event.getType() )
         {
             case ServiceEvent.REGISTERED:
-                m_size++;
                 serviceAdded( event.getServiceReference() );
                 break;
+
             case ServiceEvent.MODIFIED:
                 serviceRemoved( event.getServiceReference() );
                 serviceAdded( event.getServiceReference() );
                 break;
+
             case ServiceEvent.UNREGISTERING:
-                m_size--;
                 serviceRemoved( event.getServiceReference() );
                 break;
         }
@@ -138,12 +146,24 @@
      * <p>
      * Depending on the component state and dependency configuration, the
      * component may be activated, re-activated or the service just be provided.
-     * 
+     *
      * @param reference The reference to the service newly registered or
      *      modified.
      */
     private void serviceAdded( ServiceReference reference )
     {
+        // ignore the service, if it does not match the target filter
+        if ( targetFilterMatch( reference ) )
+        {
+            m_componentManager.getActivator().log( LogService.LOG_DEBUG,
+                "Dependency Manager: Ignoring added Service for " + m_dependencyMetadata.getName() + " : does not match target filter " + getTarget(),
+                m_componentManager.getComponentMetadata(), null );
+            return;
+        }
+
+        // increment the number of services
+        m_size++;
+
         // if the component is currently unsatisfied, it may become satisfied
         // by adding this service, try to activate
         if ( m_componentManager.getState() == AbstractComponentManager.STATE_UNSATISFIED )
@@ -189,7 +209,7 @@
      * Depending on the component state and dependency configuration, the
      * component may be deactivated, re-activated, the service just be unbound
      * with or without a replacement service.
-     * 
+     *
      * @param reference The reference to the service unregistering or being
      *      modified.
      */
@@ -201,10 +221,13 @@
             return;
         }
 
+        // decrement the number of services
+        m_size--;
+
         if ( handleServiceEvent() )
         {
             // if the dependency is not satisfied anymore, we have to
-            // deactivate the component 
+            // deactivate the component
             if ( !isSatisfied() )
             {
                 m_componentManager.getActivator()
@@ -259,7 +282,7 @@
                                 + m_dependencyMetadata.getName() + "/" + m_dependencyMetadata.getInterface()
                                 + " not satisfied", m_componentManager.getComponentMetadata(), null );
                         m_componentManager.deactivate();
-                        
+
                         // abort here we do not need to do more
                         return;
                     }
@@ -284,6 +307,7 @@
         //            && state != AbstractComponentManager.INSTANCE_CREATED;
     }
 
+
     //---------- Service tracking support -------------------------------------
 
     /**
@@ -308,7 +332,7 @@
                 ungetService( boundRefs[i] );
             }
         }
-        
+
         // drop the method references (to help GC)
         m_bind = null;
         m_unbind = null;
@@ -322,7 +346,7 @@
      * no correlation to the number of services bound to this dependency
      * manager. It is actually the maximum number of services which may be
      * bound to this dependency manager.
-     * 
+     *
      * @see #isValid()
      */
     int size()
@@ -334,7 +358,7 @@
     /**
      * Returns the first service reference returned by the
      * {@link #getServiceReferences()} method or <code>null</code> if no
-     * matching service can be found. 
+     * matching service can be found.
      */
     ServiceReference getServiceReference()
     {
@@ -359,12 +383,12 @@
         try
         {
             return m_componentManager.getActivator().getBundleContext().getServiceReferences(
-                m_dependencyMetadata.getInterface(), m_dependencyMetadata.getTarget() );
+                m_dependencyMetadata.getInterface(), getTarget() );
         }
         catch ( InvalidSyntaxException ise )
         {
             m_componentManager.getActivator().log( LogService.LOG_ERROR,
-                "Unexpected problem with filter '" + m_dependencyMetadata.getTarget() + "'",
+                "Unexpected problem with filter '" + getTarget() + "'",
                 m_componentManager.getComponentMetadata(), ise );
             return null;
         }
@@ -412,7 +436,7 @@
         return ( services.size() > 0 ) ? services.toArray() : null;
     }
 
-    
+
     //---------- bound services maintenance -----------------------------------
 
     /**
@@ -444,7 +468,7 @@
      * We have to keep track of all services for which we called the bind
      * method to be able to call the unbind method in case the service is
      * unregistered.
-     * 
+     *
      * @param serviceReference The reference to the service being marked as
      *      bound.
      */
@@ -458,9 +482,9 @@
      * Returns the bound service represented by the given service reference
      * or <code>null</code> if this is instance is not currently bound to that
      * service.
-     * 
+     *
      * @param serviceReference The reference to the bound service
-     * 
+     *
      * @return the service for the reference or the {@link #BOUND_SERVICE_SENTINEL}
      *      if the service is bound or <code>null</code> if the service is not
      *      bound.
@@ -476,9 +500,9 @@
      * is already bound the given service, that bound service instance is
      * returned. Otherwise the service retrieved from the service registry
      * and kept as a bound service for future use.
-     * 
+     *
      * @param serviceReference The reference to the service to be returned
-     * 
+     *
      * @return The requested service or <code>null</code> if no service is
      *      registered for the service reference (any more).
      */
@@ -572,13 +596,13 @@
         {
             return true;
         }
-        
+
         // assume success to begin with: if the dependency is optional,
         // we don't care, whether we can bind a service. Otherwise, we
         // require at least one service to be bound, thus we require
         // flag being set in the loop below
         boolean success = m_dependencyMetadata.isOptional();
-        
+
         // number of services to bind
         for ( int index = 0; index < refs.length; index++ )
         {
@@ -754,7 +778,8 @@
                 // Get the bind method
                 m_componentManager.getActivator().log( LogService.LOG_DEBUG,
                     "getting bind: " + m_dependencyMetadata.getBind(), m_componentManager.getComponentMetadata(), null );
-                if (m_bind == null) {
+                if ( m_bind == null )
+                {
                     m_bind = getBindingMethod( m_dependencyMetadata.getBind(), implementationObject.getClass(),
                         m_dependencyMetadata.getInterface() );
 
@@ -767,7 +792,7 @@
                             m_componentManager.getComponentMetadata(), null );
                         return true;
                     }
-                    
+
                     // cache whether the bind method takes a reference
                     m_bindUsesReference = SERVICE_REFERENCE_CLASS.equals( m_bind.getParameterTypes()[0] );
                 }
@@ -777,7 +802,7 @@
                 if ( m_bindUsesReference )
                 {
                     parameter = ref;
-                    
+
                     // mark this service as bound using the special sentinel
                     bindService( ref );
                 }
@@ -941,4 +966,117 @@
         }
     }
 
+
+    //------------- Service target filter support -----------------------------
+
+    /**
+     * Sets the target filter from target filter property contained in the
+     * properties. The filter is taken from a property whose name is derived
+     * from the dependency name and the suffix <code>.target</code> as defined
+     * for target properties on page 302 of the Declarative Services
+     * Specification, section 112.6.
+     *
+     * @param properties The properties containing the optional target service
+     *      filter property
+     */
+    void setTargetFilter( Dictionary properties )
+    {
+        setTargetFilter( ( String ) properties.get( m_dependencyMetadata.getTargetPropertyName() ) );
+    }
+
+
+    /**
+     * Sets the target filter of this dependency to the new filter value. If the
+     * new target filter is the same as the old target filter, this method has
+     * not effect. Otherwise any services currently bound but not matching the
+     * new filter are unbound. Likewise any registered services not currently
+     * bound but matching the new filter are bound.
+     *
+     * @param target The new target filter to be set. This may be
+     *      <code>null</code> if no target filtering is to be used.
+     */
+    private void setTargetFilter( String target )
+    {
+        // do nothing if target filter does not change
+        if ( ( m_target == null && target == null ) || ( m_target != null && m_target.equals( target ) ) )
+        {
+            return;
+        }
+
+        m_target = target;
+        if ( target != null )
+        {
+            try
+            {
+                m_targetFilter = m_componentManager.getActivator().getBundleContext().createFilter( target );
+            }
+            catch ( InvalidSyntaxException ise )
+            {
+                // log
+                m_targetFilter = null;
+            }
+        }
+        else
+        {
+            m_targetFilter = null;
+        }
+
+        // check for services to be removed
+        if ( m_targetFilter != null )
+        {
+            ServiceReference[] refs = getBoundServiceReferences();
+            if ( refs != null )
+            {
+                for ( int i = 0; i < refs.length; i++ )
+                {
+                    if ( !m_targetFilter.match( refs[i] ) )
+                    {
+                        // might want to do this asynchronously ??
+                        serviceRemoved( refs[i] );
+                    }
+                }
+            }
+        }
+
+        // check for new services to be added
+        ServiceReference[] refs = getServiceReferences();
+        if ( refs != null )
+        {
+            for ( int i = 0; i < refs.length; i++ )
+            {
+                if ( getBoundService( refs[i] ) == null )
+                {
+                    // might want to do this asynchronously ??
+                    serviceAdded( refs[i] );
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Returns the target filter of this dependency as a string or
+     * <code>null</code> if this dependency has no target filter set.
+     *
+     * @return The target filter of this dependency or <code>null</code> if
+     *      none is set.
+     */
+    private String getTarget()
+    {
+        return m_target;
+    }
+
+
+    /**
+     * Checks whether the service references matches the target filter of this
+     * dependency.
+     *
+     * @param ref The service reference to check
+     * @return <code>true</code> if this dependency has no target filter or if
+     *      the target filter matches the service reference.
+     */
+    private boolean targetFilterMatch( ServiceReference ref )
+    {
+        return m_targetFilter == null || m_targetFilter.match( ref );
+    }
 }
diff --git a/scr/src/main/java/org/apache/felix/scr/ImmediateComponentManager.java b/scr/src/main/java/org/apache/felix/scr/ImmediateComponentManager.java
index eb98100..14d5693 100644
--- a/scr/src/main/java/org/apache/felix/scr/ImmediateComponentManager.java
+++ b/scr/src/main/java/org/apache/felix/scr/ImmediateComponentManager.java
@@ -24,6 +24,7 @@
 import java.util.Dictionary;
 import java.util.Hashtable;
 import java.util.Iterator;
+import java.util.List;
 
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceRegistration;
@@ -342,13 +343,25 @@
             // 1. the properties from the component descriptor
             Dictionary props = copyTo( null, getComponentMetadata().getProperties() );
 
-            // 2. overlay with Configuration Admin properties
+            // 2. add target properties of references
+            // 112.6 Component Properties, target properties (p. 302)
+            List depMetaData = getComponentMetadata().getDependencies();
+            for ( Iterator di = depMetaData.iterator(); di.hasNext(); )
+            {
+                ReferenceMetadata rm = ( ReferenceMetadata ) di.next();
+                if ( rm.getTarget() != null )
+                {
+                    props.put( rm.getTargetPropertyName(), rm.getTarget() );
+                }
+            }
+
+            // 3. overlay with Configuration Admin properties
             copyTo( props, m_configurationProperties );
 
-            // 3. copy any component factory properties, not supported yet
+            // 4. copy any component factory properties, not supported yet
             copyTo( props, m_factoryProperties );
 
-            // 4. set component.name and component.id
+            // 5. set component.name and component.id
             props.put( ComponentConstants.COMPONENT_NAME, getComponentMetadata().getName() );
             props.put( ComponentConstants.COMPONENT_ID, new Long( m_componentId ) );
 
@@ -374,6 +387,9 @@
         // store the properties
         m_configurationProperties = configuration;
 
+        // clear the current properties to force using the configuration data
+        m_properties = null;
+
         // reactivate the component to ensure it is provided with the
         // configuration data
         if ( ( getState() & ( STATE_ACTIVE | STATE_FACTORY | STATE_REGISTERED ) ) != 0 )
diff --git a/scr/src/main/java/org/apache/felix/scr/ReferenceMetadata.java b/scr/src/main/java/org/apache/felix/scr/ReferenceMetadata.java
index 31b4a86..66296c2 100644
--- a/scr/src/main/java/org/apache/felix/scr/ReferenceMetadata.java
+++ b/scr/src/main/java/org/apache/felix/scr/ReferenceMetadata.java
@@ -161,7 +161,7 @@
             return;
         }
 
-        m_target = target;
+        m_target = ( target == null || target.length() == 0 ) ? null : target;
     }
 
 
@@ -314,6 +314,19 @@
 
 
     /**
+     * Returns the name of the component property referring to the {@link #getTarget() target}
+     * property of this reference.
+     *
+     * @return the name of the target property which is the name of this referene
+     *      suffixed with the string ".target".
+     */
+    public String getTargetPropertyName()
+    {
+        return getName() + ".target";
+    }
+
+
+    /**
      *  Method used to verify if the semantics of this metadata are correct
      *
      */