FELIX-1893 Add support for a configurable "updated" method

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@887877 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/helper/UpdatedMethod.java b/scr/src/main/java/org/apache/felix/scr/impl/helper/UpdatedMethod.java
new file mode 100644
index 0000000..2283888
--- /dev/null
+++ b/scr/src/main/java/org/apache/felix/scr/impl/helper/UpdatedMethod.java
@@ -0,0 +1,43 @@
+/*
+ * 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.scr.impl.helper;
+
+
+import org.apache.felix.scr.impl.manager.AbstractComponentManager;
+
+
+/**
+ * Component method to be invoked on service property update of a bound service.
+ */
+public class UpdatedMethod extends BindMethod
+{
+
+    public UpdatedMethod( final AbstractComponentManager componentManager, final String methodName,
+        final Class componentClass, final String referenceName, final String referenceClassName )
+    {
+        super( componentManager, methodName, componentClass, referenceName, referenceClassName );
+    }
+
+
+    protected String getMethodNamePrefix()
+    {
+        return "update";
+    }
+
+}
\ No newline at end of file
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java b/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java
index 04bd06b..61f6591 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java
@@ -30,6 +30,7 @@
 import org.apache.felix.scr.Reference;
 import org.apache.felix.scr.impl.helper.BindMethod;
 import org.apache.felix.scr.impl.helper.UnbindMethod;
+import org.apache.felix.scr.impl.helper.UpdatedMethod;
 import org.apache.felix.scr.impl.metadata.ReferenceMetadata;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
@@ -76,6 +77,9 @@
     // the bind method
     private BindMethod m_bind;
 
+    // the updated method
+    private UpdatedMethod m_updated;
+
     // the unbind method
     private UnbindMethod m_unbind;
 
@@ -124,6 +128,12 @@
                                  m_dependencyMetadata.getName(),
                                  m_dependencyMetadata.getInterface()
         );
+        m_updated = new UpdatedMethod( m_componentManager,
+                m_dependencyMetadata.getUpdated(),
+                m_componentInstance.getClass(),
+                m_dependencyMetadata.getName(),
+                m_dependencyMetadata.getInterface()
+        );
         m_unbind = new UnbindMethod( m_componentManager,
             m_dependencyMetadata.getUnbind(),
             m_componentInstance.getClass(),
@@ -922,24 +932,18 @@
     /**
      * Handles an update in the service reference properties of a bound service.
      * <p>
-     * For now this just calls the bind method with the service again if
-     * the <code>ds.rebind.enabled</code> configuration property is set to
-     * <code>true</code>. If the property is not set to <code>true</code> this
-     * method does nothing.
+     * This just calls the {@link #invokeUpdatedMethod(ServiceReference)}
+     * method if the method has been configured in the component metadata. If
+     * the method is not configured, this method does nothing.
      *
      * @param ref The <code>ServiceReference</code> representing the updated
      *      service.
      */
     private void update( final ServiceReference ref )
     {
-        if ( m_componentManager.getActivator().getConfiguration().isRebindEnabled() )
+        if ( m_dependencyMetadata.getUpdated() != null )
         {
-            // The updated method is only invoked if the implementation object is not
-            // null. This is valid for both immediate and delayed components
-            if ( m_dependencyMetadata.getBind() != null )
-            {
-                invokeBindMethod( ref );
-            }
+            invokeUpdatedMethod( ref );
         }
     }
 
@@ -1045,6 +1049,43 @@
 
 
     /**
+     * Calls the updated method.
+     *
+     * @param ref A service reference corresponding to the service whose service
+     *      registration properties have been updated
+     */
+    private void invokeUpdatedMethod( final ServiceReference ref )
+    {
+        // The updated method is only invoked if the implementation object is not
+        // null. This is valid for both immediate and delayed components
+        if ( m_componentInstance != null )
+        {
+            m_updated.invoke( m_componentInstance, new BindMethod.Service()
+            {
+                public ServiceReference getReference()
+                {
+                    return ref;
+                }
+
+
+                public Object getInstance()
+                {
+                    return getService( ref );
+                }
+            } );
+        }
+        else
+        {
+            // don't care whether we can or cannot call the unbind method
+            // if the component instance has already been cleared by the
+            // close() method
+            m_componentManager.log( LogService.LOG_DEBUG,
+                "DependencyManager : Component not set, no need to call updated method", null );
+        }
+    }
+
+
+    /**
      * Calls the unbind method.
      * <p>
      * If the reference is singular and the given service is not the one bound
@@ -1053,7 +1094,6 @@
      *
      * @param ref A service reference corresponding to the service that will be
      *            unbound
-     * @return true if the call was successful, false otherwise
      */
     private void invokeUnbindMethod( final ServiceReference ref )
     {
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/metadata/ComponentMetadata.java b/scr/src/main/java/org/apache/felix/scr/impl/metadata/ComponentMetadata.java
index d55bcf3..8216b1d 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/metadata/ComponentMetadata.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/metadata/ComponentMetadata.java
@@ -363,6 +363,18 @@
 
 
     /**
+     * Returns <code>true</code> if the metadata declaration has used the
+     * Declarative Services version 1.1-felixnamespace or a later namespace.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/FELIX-1893">FELIX-1893</a>
+     */
+    public boolean isDS11Felix()
+    {
+        return getNamespaceCode() >= XmlHandler.DS_VERSION_1_1_FELIX;
+    }
+
+
+    /**
      * Returns the name of the component
      *
      * @return A string containing the name of the component
@@ -707,7 +719,7 @@
         while ( referenceIterator.hasNext() )
         {
             ReferenceMetadata refMeta = ( ReferenceMetadata ) referenceIterator.next();
-            refMeta.validate( this );
+            refMeta.validate( this, logger );
 
             // flag duplicates
             if ( !refs.add( refMeta.getName() ) )
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/metadata/ReferenceMetadata.java b/scr/src/main/java/org/apache/felix/scr/impl/metadata/ReferenceMetadata.java
index 471c099..aeb4c16 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/metadata/ReferenceMetadata.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/metadata/ReferenceMetadata.java
@@ -21,6 +21,9 @@
 import java.util.Set;
 import java.util.TreeSet;
 
+import org.apache.felix.scr.impl.helper.Logger;
+import org.osgi.service.log.LogService;
+
 /**
  * Information associated to a dependency
  *
@@ -66,6 +69,9 @@
     // Name of the bind method (optional)
     private String m_bind = null;
 
+    // Name of the updated method (optional, since DS 1.1-felix)
+    private String m_updated = null;
+
     // Name of the unbind method (optional)
     private String m_unbind = null;
 
@@ -201,6 +207,22 @@
 
 
     /**
+     * Setter for the updated method attribute
+     *
+     * @param updated
+     */
+    public void setUpdated( String updated )
+    {
+        if ( m_validated )
+        {
+            return;
+        }
+
+        m_updated = updated;
+    }
+
+
+    /**
      * Setter for the unbind method attribute
      *
      * @param unbind
@@ -287,6 +309,18 @@
 
     /**
      * Get the name of a method in the component implementation class that is used to notify that
+     * the service properties of a bound service have been updated
+     *
+     * @return a String with the name of the updated method
+     **/
+    public String getUpdated()
+    {
+        return m_updated;
+    }
+
+
+    /**
+     * Get the name of a method in the component implementation class that is used to notify that
      * a service is unbound from the component configuration
      *
      * @return a String with the name of the unbind method
@@ -349,7 +383,7 @@
      *  Method used to verify if the semantics of this metadata are correct
      *
      */
-    void validate( ComponentMetadata componentMetadata )
+    void validate( final ComponentMetadata componentMetadata, final Logger logger )
     {
         if ( m_name == null )
         {
@@ -384,6 +418,16 @@
         {
             throw componentMetadata.validationFailure( "Policy must be one of " + POLICY_VALID );
         }
+
+        // updated method is only supported in namespace xxx and later
+        if ( m_updated != null && !componentMetadata.isDS11Felix() )
+        {
+            logger
+                .log( LogService.LOG_WARNING,
+                    "Ignoring updated method definition, DS 1.1-felix or later namespace required", componentMetadata,
+                    null );
+            m_updated = null;
+        }
     }
 
 }
\ No newline at end of file
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/metadata/XmlHandler.java b/scr/src/main/java/org/apache/felix/scr/impl/metadata/XmlHandler.java
index 3100c7f..bbdc988 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/metadata/XmlHandler.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/metadata/XmlHandler.java
@@ -52,15 +52,21 @@
     // Namespace URI of DS 1.1
     public static final String NAMESPACE_URI_1_1 = "http://www.osgi.org/xmlns/scr/v1.1.0";
 
+    // Namespace URI of DS 1.1-felix (see FELIX-1893)
+    public static final String NAMESPACE_URI_1_1_FELIX = "http://felix.apache.org/xmlns/scr/v1.1.0-felix";
+
     // namespace code for non-DS namespace
     public static final int DS_VERSION_NONE = -1;
 
     // namespace code for the DS 1.0 specification
     public static final int DS_VERSION_1_0 = 0;
 
-    // namespace code for the DS 1.0 specification
+    // namespace code for the DS 1.1 specification
     public static final int DS_VERSION_1_1 = 1;
 
+    // namespace code for the DS 1.1-felix specification
+    public static final int DS_VERSION_1_1_FELIX = 2;
+
     // mapping of namespace URI to namespace code
     private static final Map NAMESPACE_CODE_MAP;
 
@@ -97,6 +103,7 @@
         NAMESPACE_CODE_MAP.put( NAMESPACE_URI_EMPTY, new Integer( DS_VERSION_1_0 ) );
         NAMESPACE_CODE_MAP.put( NAMESPACE_URI, new Integer( DS_VERSION_1_0 ) );
         NAMESPACE_CODE_MAP.put( NAMESPACE_URI_1_1, new Integer( DS_VERSION_1_1 ) );
+        NAMESPACE_CODE_MAP.put( NAMESPACE_URI_1_1_FELIX, new Integer( DS_VERSION_1_1_FELIX ) );
     }
 
 
@@ -306,6 +313,7 @@
                     //if
                     ref.setTarget( attrib.getProperty( "target" ) );
                     ref.setBind( attrib.getProperty( "bind" ) );
+                    ref.setUpdated( attrib.getProperty( "updated" ) );
                     ref.setUnbind( attrib.getProperty( "unbind" ) );
 
                     m_currentComponent.addDependency( ref );
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/metadata/ComponentMetadataTest.java b/scr/src/test/java/org/apache/felix/scr/impl/metadata/ComponentMetadataTest.java
index 1c51f68..6bd1645 100644
--- a/scr/src/test/java/org/apache/felix/scr/impl/metadata/ComponentMetadataTest.java
+++ b/scr/src/test/java/org/apache/felix/scr/impl/metadata/ComponentMetadataTest.java
@@ -482,6 +482,55 @@
     }
 
 
+    public void test_reference_updated_ds10()
+    {
+        // updated method ignored for DS 1.0
+        final ReferenceMetadata rm3 = createReferenceMetadata( "test" );
+        rm3.setUpdated( "my_updated_method" );
+        final ComponentMetadata cm3 = createComponentMetadata( Boolean.TRUE, null );
+        cm3.addDependency( rm3 );
+
+        // validates fine (though logging a warning) and sets field to null
+        cm3.validate( logger );
+
+        assertTrue( "Expected warning for unsupported updated method name", logger
+            .messageContains( "Ignoring updated method definition" ) );
+        assertNull( rm3.getUpdated() );
+    }
+
+
+    public void test_reference_updated_ds11()
+    {
+        // updated method ignored for DS 1.1
+        final ReferenceMetadata rm3 = createReferenceMetadata( "test" );
+        rm3.setUpdated( "my_updated_method" );
+        final ComponentMetadata cm3 = createComponentMetadata11( Boolean.TRUE, null );
+        cm3.addDependency( rm3 );
+
+        // validates fine (though logging a warning) and sets field to null
+        cm3.validate( logger );
+
+        assertTrue( "Expected warning for unsupported updated method name", logger
+            .messageContains( "Ignoring updated method definition" ) );
+        assertNull( rm3.getUpdated() );
+    }
+
+
+    public void test_reference_updated_ds11_felix()
+    {
+        // updated method accepted for DS 1.1-felix
+        final ReferenceMetadata rm3 = createReferenceMetadata( "test" );
+        rm3.setUpdated( "my_updated_method" );
+        final ComponentMetadata cm3 = createComponentMetadata( XmlHandler.DS_VERSION_1_1_FELIX, Boolean.TRUE, null );
+        cm3.addDependency( rm3 );
+
+        // validates fine and logs no message
+        cm3.validate( logger );
+
+        assertEquals( "my_updated_method", rm3.getUpdated() );
+    }
+
+
     public void test_duplicate_implementation_ds10()
     {
         final ComponentMetadata cm = createComponentMetadata( Boolean.TRUE, null );
@@ -636,10 +685,10 @@
 
     //---------- Helper methods
 
-    // Creates DS 1.0 Component Metadata
-    private ComponentMetadata createComponentMetadata( Boolean immediate, String factory )
+    // Creates Component Metadata for the given namespace
+    private ComponentMetadata createComponentMetadata( int nameSpaceCode, Boolean immediate, String factory )
     {
-        ComponentMetadata meta = new ComponentMetadata( XmlHandler.DS_VERSION_1_0 );
+        ComponentMetadata meta = new ComponentMetadata( nameSpaceCode );
         meta.setName( "place.holder" );
         meta.setImplementationClassName( "place.holder.implementation" );
         if ( immediate != null )
@@ -653,22 +702,17 @@
         return meta;
     }
 
+    // Creates DS 1.0 Component Metadata
+    private ComponentMetadata createComponentMetadata( Boolean immediate, String factory )
+    {
+        return createComponentMetadata( XmlHandler.DS_VERSION_1_0, immediate, factory );
+    }
+
 
     // Creates DS 1.1 Component Metadata
     private ComponentMetadata createComponentMetadata11( Boolean immediate, String factory )
     {
-        ComponentMetadata meta = new ComponentMetadata( XmlHandler.DS_VERSION_1_1 );
-        meta.setName( "place.holder" );
-        meta.setImplementationClassName( "place.holder.implementation" );
-        if ( immediate != null )
-        {
-            meta.setImmediate( immediate.booleanValue() );
-        }
-        if ( factory != null )
-        {
-            meta.setFactoryIdentifier( factory );
-        }
-        return meta;
+        return createComponentMetadata( XmlHandler.DS_VERSION_1_1, immediate, factory );
     }