You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by dj...@apache.org on 2014/01/24 02:34:29 UTC

svn commit: r1560874 - in /felix/trunk/scr/src: main/java/org/apache/felix/scr/impl/helper/ main/java/org/apache/felix/scr/impl/manager/ main/java/org/apache/felix/scr/impl/metadata/ test/java/org/apache/felix/scr/integration/ test/resources/

Author: djencks
Date: Fri Jan 24 01:34:28 2014
New Revision: 1560874

URL: http://svn.apache.org/r1560874
Log:
FELIX-4391 initial support for refname.cardinality.minimum property

Added:
    felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/helper/Coercions.java   (with props)
    felix/trunk/scr/src/test/java/org/apache/felix/scr/integration/MinimumCardinalityTest.java   (with props)
    felix/trunk/scr/src/test/resources/integration_test_min_cardinality.xml   (with props)
Modified:
    felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java
    felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/manager/ServiceTracker.java
    felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/manager/ServiceTrackerCustomizer.java
    felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/metadata/ReferenceMetadata.java

Added: felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/helper/Coercions.java
URL: http://svn.apache.org/viewvc/felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/helper/Coercions.java?rev=1560874&view=auto
==============================================================================
--- felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/helper/Coercions.java (added)
+++ felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/helper/Coercions.java Fri Jan 24 01:34:28 2014
@@ -0,0 +1,396 @@
+/*
+ * 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 java.lang.reflect.Array;
+import java.util.Collection;
+
+import org.osgi.framework.Bundle;
+import org.osgi.service.component.ComponentException;
+
+/**
+ * This implements the coercion table in RFC 190 5.6.3 
+ *
+ */
+public class Coercions
+{
+// Numbers are   AtomicInteger, AtomicLong, BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, Short
+//annotation fields can be primitives, String, Class, enums, annotations, and arrays of the preceding types 
+//    input scalars
+//    String | Integer | Long | Float
+//    | Double | Byte | Short
+//| Character | Boolean
+    private static final byte byte0 = 0;
+    private static final double double0 = 0;
+    private static final float float0 = 0;
+    private static final int int0 = 0;
+    private static final long long0 = 0;
+    private static final short short0 = 0;
+    
+    public static byte coerceToByte(Object o)
+    {
+        o = multipleToSingle( o, byte0 );
+        if (o instanceof Byte)
+        {
+            return (Byte)o;
+        }
+        if (o instanceof String)
+        {
+            try
+            {
+                return Byte.parseByte( (String)o );
+            }
+            catch ( NumberFormatException e )
+            {
+                throw new ComponentException(e);
+            }
+        }
+        if (o instanceof Boolean)
+        {
+            return (Boolean)o? 1: byte0;
+        }
+        if (o instanceof Character)
+        {
+            return ( byte ) ((Character)o).charValue();
+        }
+        if (o instanceof Number)
+        {
+            return ((Number)o).byteValue();
+        }
+        if (o == null) 
+        {
+            return 0;
+        }
+        throw new ComponentException( "Unrecognized input type: " + o);
+    }
+    
+    public static double coerceToDouble(Object o)
+    {
+        o = multipleToSingle( o, double0 );
+        if (o instanceof Double)
+        {
+            return (Double)o;
+        }
+        if (o instanceof String)
+        {
+            try
+            {
+                return Double.parseDouble((String)o );
+            }
+            catch ( NumberFormatException e )
+            {
+                throw new ComponentException(e);
+            }
+        }
+        if (o instanceof Boolean)
+        {
+            return (Boolean)o? 1: 0;
+        }
+        if (o instanceof Character)
+        {
+            return ( double ) ((Character)o).charValue();
+        }
+        if (o instanceof Number)
+        {
+            return ((Number)o).doubleValue();
+        }
+        if (o == null) 
+        {
+            return 0;
+        }
+        throw new ComponentException( "Unrecognized input type: " + o);
+    }
+    
+    public static float coerceToFloat(Object o)
+    {
+        o = multipleToSingle( o, float0 );
+        if (o instanceof Float)
+        {
+            return (Float)o;
+        }
+        if (o instanceof String)
+        {
+            try
+            {
+                return Float.parseFloat((String)o );
+            }
+            catch ( NumberFormatException e )
+            {
+                throw new ComponentException(e);
+            }
+        }
+        if (o instanceof Boolean)
+        {
+            return (Boolean)o? 1: 0;
+        }
+        if (o instanceof Character)
+        {
+            return ( float ) ((Character)o).charValue();
+        }
+        if (o instanceof Number)
+        {
+            return ((Number)o).floatValue();
+        }
+        if (o == null) 
+        {
+            return 0;
+        }
+        throw new ComponentException( "Unrecognized input type: " + o);
+    }
+    
+    public static int coerceToInteger(Object o)
+    {
+        o = multipleToSingle( o, int0 );
+        if (o instanceof Integer)
+        {
+            return (Integer)o;
+        }
+        if (o instanceof String)
+        {
+            try
+            {
+                return Integer.parseInt( (String)o );
+            }
+            catch ( NumberFormatException e )
+            {
+                throw new ComponentException(e);
+            }
+        }
+        if (o instanceof Boolean)
+        {
+            return (Boolean)o? 1: 0;
+        }
+        if (o instanceof Character)
+        {
+            return ( int ) ((Character)o).charValue();
+        }
+        if (o instanceof Number)
+        {
+            return ((Number)o).intValue();
+        }
+        if (o == null) 
+        {
+            return 0;
+        }
+        throw new ComponentException( "Unrecognized input type: " + o);
+    }
+    
+    public static long coerceToLong(Object o)
+    {
+        o = multipleToSingle( o, long0 );
+        if (o instanceof Long)
+        {
+            return (Long)o;
+        }
+        if (o instanceof String)
+        {
+            try
+            {
+                return Long.parseLong( (String)o );
+            }
+            catch ( NumberFormatException e )
+            {
+                throw new ComponentException(e);
+            }
+        }
+        if (o instanceof Boolean)
+        {
+            return (Boolean)o? 1: 0;
+        }
+        if (o instanceof Character)
+        {
+            return ( long ) ((Character)o).charValue();
+        }
+        if (o instanceof Number)
+        {
+            return ((Number)o).longValue();
+        }
+        if (o == null) 
+        {
+            return 0;
+        }
+        throw new ComponentException( "Unrecognized input type: " + o);
+    }
+    
+    public static short coerceToShort(Object o)
+    {
+        o = multipleToSingle( o, short0 );
+        if (o instanceof Short)
+        {
+            return (Short)o;
+        }
+        if (o instanceof String)
+        {
+            try
+            {
+                return Short.parseShort( (String)o );
+            }
+            catch ( NumberFormatException e )
+            {
+                throw new ComponentException(e);
+            }
+        }
+        if (o instanceof Boolean)
+        {
+            return (Boolean)o? 1: short0;
+        }
+        if (o instanceof Character)
+        {
+            return ( short ) ((Character)o).charValue();
+        }
+        if (o instanceof Number)
+        {
+            return ((Number)o).shortValue();
+        }
+        if (o == null) 
+        {
+            return 0;
+        }
+        throw new ComponentException( "Unrecognized input type: " + o);
+    }
+    
+    public static String coerceToString(Object o)
+    {
+        o = multipleToSingle( o, null );
+        if (o instanceof String)
+        {
+            return (String)o;
+        }
+        if (o == null)
+        {
+            return null;
+        }
+        
+        return o.toString();
+    }
+    
+    public static boolean coerceToBoolean(Object o)
+    {
+        o = multipleToSingle( o, false );
+        if (o instanceof Boolean)
+        {
+            return (Boolean)o;
+        }
+        if (o instanceof String)
+        {
+            try
+            {
+                return Boolean.parseBoolean( (String)o );
+            }
+            catch ( NumberFormatException e )
+            {
+                throw new ComponentException(e);
+            }
+        }
+        if (o instanceof Character)
+        {
+            return ((Character)o).charValue() != 0;
+        }
+        if (o instanceof Number)
+        {
+            return ((Number)o).intValue() != 0;
+        }
+        if (o == null) 
+        {
+            return false;
+        }
+        throw new ComponentException( "Unrecognized input type: " + o);
+    }
+    
+    public static Class<?> coerceToClass(Object o, Bundle b)
+    {
+        o = multipleToSingle( o, null );
+        if (o == null)
+        {
+            return null;
+        }
+        if (o instanceof String)
+        {
+            try
+            {
+                return b.loadClass( (String)o );
+            }
+            catch ( ClassNotFoundException e )
+            {
+                throw new ComponentException(e);
+            }
+        }
+        throw new ComponentException( "Unrecognized input type: " + o);
+    }
+    
+    public static <T extends Enum<T>> T coerceToEnum(Object o, Class<T> clazz)
+    {
+        o = multipleToSingle( o, null );
+        if (o instanceof String)
+        {
+            try
+            {
+            return Enum.valueOf( clazz, (String)o );
+            }
+            catch ( IllegalArgumentException e )
+            {
+                throw new ComponentException(e);
+            }
+        }
+        if (o == null) 
+        {
+            return null;
+        }
+        throw new ComponentException( "Unrecognized input type: " + o);
+    }
+    
+    private static Object multipleToSingle(Object o, Object defaultValue)
+    {
+        if (o instanceof Collection)
+        {
+            return firstCollectionElement( o, defaultValue );
+        }
+        if (o != null && o.getClass().isArray()) {
+            return firstArrayElement( o, defaultValue);
+        }
+        return o;
+    }
+    
+    private static Object firstCollectionElement( Object raw, Object defaultValue )
+    {
+        if (!(raw instanceof Collection)) 
+        {
+            throw new ComponentException("Not a collection: " + raw);
+        }
+        Collection c = ( Collection ) raw;
+        if (c.isEmpty())
+        {
+            return defaultValue;
+        }
+        return c.iterator().next();
+    }
+
+    private static Object firstArrayElement(Object o, Object defaultValue)
+    {
+        if (o == null || !o.getClass().isArray()) {
+            throw new ComponentException("Not an array: " + o);
+        }
+        if (Array.getLength( o ) == 0)
+        {
+            return defaultValue;
+        }
+        return Array.get( o, 0 );
+    }
+
+}

Propchange: felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/helper/Coercions.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/helper/Coercions.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/helper/Coercions.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java
URL: http://svn.apache.org/viewvc/felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java?rev=1560874&r1=1560873&r2=1560874&view=diff
==============================================================================
--- felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java (original)
+++ felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java Fri Jan 24 01:34:28 2014
@@ -41,6 +41,7 @@ import org.apache.felix.scr.Reference;
 import org.apache.felix.scr.impl.BundleComponentActivator;
 import org.apache.felix.scr.impl.helper.BindMethod;
 import org.apache.felix.scr.impl.helper.BindMethods;
+import org.apache.felix.scr.impl.helper.Coercions;
 import org.apache.felix.scr.impl.helper.MethodResult;
 import org.apache.felix.scr.impl.metadata.ReferenceMetadata;
 import org.osgi.framework.BundleContext;
@@ -50,6 +51,7 @@ import org.osgi.framework.InvalidSyntaxE
 import org.osgi.framework.ServicePermission;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.component.ComponentConstants;
+import org.osgi.service.component.ComponentException;
 import org.osgi.service.log.LogService;
 
 
@@ -86,7 +88,7 @@ public class DependencyManager<S, T> imp
     // the target service filter
     private volatile Filter m_targetFilter;
 
-    //private volatile boolean m_registered;
+    private volatile int m_minCardinality;
 
     /**
      * Constructor that receives several parameters.
@@ -99,6 +101,8 @@ public class DependencyManager<S, T> imp
         m_dependencyMetadata = dependency;
         m_index = index;
         m_customizer = newCustomizer();
+        
+        m_minCardinality = defaultMinimumCardinality( dependency );
 
         // dump the reference information if DEBUG is enabled
         if ( m_componentManager.isLogEnabled( LogService.LOG_DEBUG ) )
@@ -112,6 +116,11 @@ public class DependencyManager<S, T> imp
                             dependency.getCardinality(), dependency.getBind(), dependency.getUnbind() }, null );
         }
     }
+
+    private static int defaultMinimumCardinality(ReferenceMetadata dependency)
+    {
+        return dependency.isOptional()? 0: 1;
+    }
     
     int getIndex() 
     {
@@ -170,11 +179,7 @@ public class DependencyManager<S, T> imp
             {
                 return false;
             }
-            if (isOptional())
-            {
-                return true;
-            }            
-            return !tracker.isEmpty();
+            return cardinalitySatisfied( tracker.getServiceCount() );
         }
 
         protected ServiceTracker<T, RefPair<T>> getTracker()
@@ -258,9 +263,9 @@ public class DependencyManager<S, T> imp
             return refPair;
         }
 
-        public void addedService( ServiceReference<T> serviceReference, RefPair<T> refPair, int trackingCount )
+        public void addedService( ServiceReference<T> serviceReference, RefPair<T> refPair, int trackingCount, int serviceCount )
         {
-            if ( !isOptional() )
+            if ( cardinalityJustSatisfied( serviceCount ) )
             {
                 m_componentManager.activateInternal( trackingCount );
             }
@@ -273,21 +278,17 @@ public class DependencyManager<S, T> imp
         public void removedService( ServiceReference<T> serviceReference, RefPair<T> refPair, int trackingCount )
         {
             refPair.setDeleted( true );
-            if ( !isOptional() )
+            if ( !cardinalitySatisfied( getTracker().getServiceCount() ) )
             {
-                if (getTracker().isEmpty())
-                {
-                    m_componentManager.deactivateInternal( ComponentConstants.DEACTIVATION_REASON_REFERENCE, false, false );
-                }
+                m_componentManager.deactivateInternal( ComponentConstants.DEACTIVATION_REASON_REFERENCE, false, false );
             }
         }
 
         public boolean prebind()
         {
-            boolean success = m_dependencyMetadata.isOptional() || !getTracker().isEmpty();
             AtomicInteger trackingCount = new AtomicInteger( );
-            getTracker().getTracked( true, trackingCount );
-            return success;
+            int serviceCount = getTracker().getTracked( true, trackingCount ).size();
+            return cardinalitySatisfied( serviceCount );
         }
 
         public void close()
@@ -316,7 +317,7 @@ public class DependencyManager<S, T> imp
             return refPair;
         }
 
-        public void addedService( ServiceReference<T> serviceReference, RefPair<T> refPair, int trackingCount )
+        public void addedService( ServiceReference<T> serviceReference, RefPair<T> refPair, int trackingCount, int serviceCount )
         {
             m_componentManager.log( LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleDynamic added {2} (enter)", new Object[] {getName(), trackingCount, serviceReference}, null );
         	boolean tracked = false;
@@ -334,7 +335,7 @@ public class DependencyManager<S, T> imp
                         m_componentManager.registerMissingDependency( DependencyManager.this, serviceReference, trackingCount );
                     }
                 }
-                else if ( isTrackerOpened() && !isOptional() )
+                else if ( isTrackerOpened() && cardinalityJustSatisfied( serviceCount ) )
                 {
                     m_componentManager.log( LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleDynamic, activating", new Object[] {getName(), trackingCount}, null );
                     tracked( trackingCount );
@@ -368,7 +369,7 @@ public class DependencyManager<S, T> imp
         {
             m_componentManager.log( LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleDynamic removed {2} (enter)", new Object[] {getName(), trackingCount, serviceReference}, null );
             refPair.setDeleted( true );
-            boolean unbind = isOptional() || !getTracker().isEmpty();
+            boolean unbind = cardinalitySatisfied( getTracker().getServiceCount() );
             if ( unbind )
             {
                 if ( isActive() )
@@ -392,21 +393,21 @@ public class DependencyManager<S, T> imp
 
         public boolean prebind()
         {
-            boolean success = m_dependencyMetadata.isOptional();
+            int serviceCount = 0;
             AtomicInteger trackingCount = new AtomicInteger( );
             SortedMap<ServiceReference<T>, RefPair<T>> tracked = getTracker().getTracked( true, trackingCount );
             for (RefPair<T> refPair: tracked.values())
             {
                 if (getServiceObject( m_bindMethods.getBind(), refPair ))
                 {
-                     success = true;
+                     serviceCount++;
                 }
                 else
                 {
                      m_componentManager.registerMissingDependency( DependencyManager.this, refPair.getRef(), trackingCount.get() );
                 }
             }
-            return success;
+            return cardinalitySatisfied( serviceCount );
         }
 
         public void close()
@@ -452,7 +453,7 @@ public class DependencyManager<S, T> imp
             return refPair;
         }
 
-        public void addedService( ServiceReference<T> serviceReference, RefPair<T> refPair, int trackingCount )
+        public void addedService( ServiceReference<T> serviceReference, RefPair<T> refPair, int trackingCount, int serviceCount )
         {
             m_componentManager.log( LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleStaticGreedy added {2} (enter)", new Object[] {getName(), trackingCount, serviceReference}, null );
             tracked( trackingCount );
@@ -465,7 +466,7 @@ public class DependencyManager<S, T> imp
                 m_componentManager.activateInternal( trackingCount );
 
             }
-            else if ( isTrackerOpened() &&  !isOptional() )
+            else if ( isTrackerOpened() && cardinalityJustSatisfied( serviceCount ) )
             {
                 m_componentManager.activateInternal( trackingCount );
             }
@@ -498,7 +499,7 @@ public class DependencyManager<S, T> imp
                 //try to reactivate after ref is no longer tracked.
                 m_componentManager.activateInternal( trackingCount );
             }
-            else if ( !isOptional() && getTracker().isEmpty() )
+            else if ( !cardinalitySatisfied( getTracker().getServiceCount() ) ) //may be called from an old tracker, so getTracker() may give a different answer
             {
                 m_componentManager.log( LogService.LOG_DEBUG,
                         "Dependency Manager: Static dependency on {0}/{1} is broken", new Object[]
@@ -512,19 +513,23 @@ public class DependencyManager<S, T> imp
 
         public boolean prebind()
         {
-            boolean success = m_dependencyMetadata.isOptional();
+            int serviceCount = 0;
             AtomicInteger trackingCount = new AtomicInteger( );
-            SortedMap<ServiceReference<T>, RefPair<T>> tracked = getTracker().getTracked( success || !getTracker().isEmpty(), trackingCount );
+            final ServiceTracker<T, RefPair<T>> tracker = getTracker();
+            SortedMap<ServiceReference<T>, RefPair<T>> tracked = tracker.getTracked( cardinalitySatisfied( tracker.getServiceCount() ), trackingCount );
             for (RefPair<T> refPair: tracked.values())
             {
-                success |= getServiceObject( m_bindMethods.getBind(), refPair );
-                if ( refPair.isFailed() )
+                if ( getServiceObject( m_bindMethods.getBind(), refPair ) )
+                {
+                    serviceCount++;
+                }
+                else
                 {
                     m_componentManager.registerMissingDependency( DependencyManager.this, refPair.getRef(),
                             trackingCount.get() );
                 }
             }
-            return success;
+            return cardinalitySatisfied( serviceCount );
         }
 
         public void close()
@@ -558,11 +563,11 @@ public class DependencyManager<S, T> imp
             return refPair;
         }
 
-        public void addedService( ServiceReference<T> serviceReference, RefPair<T> refPair, int trackingCount )
+        public void addedService( ServiceReference<T> serviceReference, RefPair<T> refPair, int trackingCount, int serviceCount )
         {
             m_componentManager.log( LogService.LOG_DEBUG, "dm {0} tracking {1} MultipleStaticReluctant added {2} (enter)", new Object[] {getName(), trackingCount, serviceReference}, null );
             tracked( trackingCount );
-            if ( isTrackerOpened() && !isOptional() && !isActive())
+            if ( isTrackerOpened() && cardinalityJustSatisfied( serviceCount ) && !isActive())
             {
                 m_componentManager.activateInternal( trackingCount );
             }
@@ -602,7 +607,7 @@ public class DependencyManager<S, T> imp
 
                 }
             }
-            else if ( !isOptional() && getTracker().isEmpty() )
+            else if ( !cardinalitySatisfied( getTracker().getServiceCount() ) )
             {
                 m_componentManager.log( LogService.LOG_DEBUG,
                         "Dependency Manager: Static dependency on {0}/{1} is broken", new Object[]
@@ -615,22 +620,28 @@ public class DependencyManager<S, T> imp
 
         public boolean prebind()
         {
-            boolean success = m_dependencyMetadata.isOptional();
+            int serviceCount = 0;
             Collection<RefPair<T>> refs = this.refs.get();
             if (refs != null) {
                 //another thread is concurrently opening, and it got done already
                 for (RefPair<T> refPair: refs)
                 {
-                    success |= getServiceObject( m_bindMethods.getBind(), refPair );
+                    if (getServiceObject( m_bindMethods.getBind(), refPair ))
+                    {
+                        serviceCount++;
+                    }
                 }
-                return success;
+                return cardinalitySatisfied( serviceCount );
             }
             refs = new ArrayList<RefPair<T>>();
             AtomicInteger trackingCount = new AtomicInteger( );
             SortedMap<ServiceReference<T>, RefPair<T>> tracked = getTracker().getTracked( true, trackingCount );
             for (RefPair<T> refPair: tracked.values())
             {
-                success |= getServiceObject( m_bindMethods.getBind(), refPair );
+                if (getServiceObject( m_bindMethods.getBind(), refPair ))
+                {
+                    serviceCount++;
+                }
                 refs.add(refPair) ;
             }
             if ( this.refs.compareAndSet( null, refs ) )
@@ -647,7 +658,7 @@ public class DependencyManager<S, T> imp
                     ungetService( ref );
                 }
             }
-            return success;
+            return cardinalitySatisfied( serviceCount );
         }
 
         public void close()
@@ -686,7 +697,7 @@ public class DependencyManager<S, T> imp
             return refPair;
         }
 
-        public void addedService( ServiceReference<T> serviceReference, RefPair<T> refPair, int trackingCount )
+        public void addedService( ServiceReference<T> serviceReference, RefPair<T> refPair, int trackingCount, int serviceCount )
         {
             m_componentManager.log( LogService.LOG_DEBUG, "dm {0} tracking {1} SingleDynamic added {2} (enter)", new Object[] {getName(), trackingCount, serviceReference}, null );
             boolean tracked = false;
@@ -721,7 +732,7 @@ public class DependencyManager<S, T> imp
                         this.refPair = refPair;
                     }
                 }
-                else if ( isTrackerOpened() && !isOptional() )
+                else if ( isTrackerOpened() && cardinalityJustSatisfied( serviceCount ) )
                 {
                     tracked( trackingCount );
                     tracked = true;
@@ -772,7 +783,9 @@ public class DependencyManager<S, T> imp
                                 trackingCount2 );
                         nextRefPair = tracked.values().iterator().next();
                     }
-                    if ( isOptional() || nextRefPair != null )
+                    
+                    //n.b. we cannot use cardinalitySatisfied( serviceCount ) here as the call may come from an old tracker during target change.
+                    if ( isEffectivelyOptional() || nextRefPair != null )
                     {
                         oldRefPair = this.refPair;
                         this.refPair = null;
@@ -782,7 +795,7 @@ public class DependencyManager<S, T> imp
                         deactivate = true;            //required and no replacement service, deactivate
                     }
                 }
-                else if ( !isOptional() && this.refPair == null && getTracker().isEmpty())
+                else if ( !cardinalitySatisfied( getTracker().getServiceCount() ) && this.refPair == null)
                 {
                     deactivate = true;
                 }
@@ -833,7 +846,7 @@ public class DependencyManager<S, T> imp
         public boolean prebind()
         {
             RefPair<T> refPair = null;
-            boolean success = m_dependencyMetadata.isOptional();
+            boolean success = cardinalitySatisfied( 0 );
             AtomicInteger trackingCount = new AtomicInteger();
             synchronized ( getTracker().tracked() )
             {
@@ -904,7 +917,7 @@ public class DependencyManager<S, T> imp
             return refPair;
         }
 
-        public void addedService( ServiceReference<T> serviceReference, RefPair<T> refPair, int trackingCount )
+        public void addedService( ServiceReference<T> serviceReference, RefPair<T> refPair, int trackingCount, int serviceCount )
         {
             m_componentManager.log( LogService.LOG_DEBUG, "dm {0} tracking {1} SingleStatic added {2} (enter)", new Object[] {getName(), trackingCount, serviceReference}, null );
             this.trackingCount = trackingCount;
@@ -926,7 +939,7 @@ public class DependencyManager<S, T> imp
                     m_componentManager.log( LogService.LOG_DEBUG, "dm {0} tracking {1} SingleStatic active but new {2} is worse match than old {3}", new Object[] {getName(), trackingCount, refPair, this.refPair, }, null );               
                 }
             }
-            else if (isTrackerOpened() && !isOptional() )
+            else if (isTrackerOpened() && cardinalityJustSatisfied( serviceCount ) )
             {
                 m_componentManager.activateInternal( trackingCount );
             }
@@ -964,7 +977,7 @@ public class DependencyManager<S, T> imp
             final Object sync = getTracker().tracked();
             synchronized (sync)
             {
-                reactivate = ( isActive() && refPair == this.refPair) || ( !isOptional() && getTracker().isEmpty());
+                reactivate = ( isActive() && refPair == this.refPair) || ( !cardinalitySatisfied( getTracker().getServiceCount() ));
                 if (!reactivate && refPair == this.refPair) {
                     this.refPair = null;
                 }
@@ -986,7 +999,7 @@ public class DependencyManager<S, T> imp
 
         public boolean prebind()
         {
-            boolean success = m_dependencyMetadata.isOptional();
+            boolean success = cardinalitySatisfied( 0 );
             if ( success || !getTracker().isEmpty() )
             {
                 RefPair<T> refPair = null;
@@ -1089,7 +1102,7 @@ public class DependencyManager<S, T> imp
             return null;
         }
 
-        public void addedService( ServiceReference<T> tServiceReference, RefPair<T> service, int trackingCount )
+        public void addedService( ServiceReference<T> tServiceReference, RefPair<T> service, int trackingCount, int serviceCount )
         {
         }
 
@@ -1114,7 +1127,21 @@ public class DependencyManager<S, T> imp
         return m_dependencyMetadata.isOptional();
     }
 
+    public boolean isEffectivelyOptional()
+    {
+        return m_minCardinality == 0;
+    }
 
+    private boolean cardinalitySatisfied(int serviceCount)
+    {
+        return m_minCardinality <= serviceCount;
+    } 
+    
+    private boolean cardinalityJustSatisfied(int serviceCount)
+    {
+        return m_minCardinality == serviceCount;
+    }
+    
     public boolean isMultiple()
     {
         return m_dependencyMetadata.isMultiple();
@@ -1763,8 +1790,10 @@ public class DependencyManager<S, T> imp
         // 1. no target filter change
         final String newTarget = ( String ) properties.get( m_dependencyMetadata.getTargetPropertyName() );
         final String currentTarget = getTarget();
-        if ( ( currentTarget == null && newTarget == null )
-            || ( currentTarget != null && currentTarget.equals( newTarget ) ) )
+        int newMinimumCardinality = getMinimumCardinality( properties );
+        if ( m_minCardinality == newMinimumCardinality && 
+                ( ( currentTarget == null && newTarget == null )
+            || ( currentTarget != null && currentTarget.equals( newTarget ) ) ) )
         {
             // can update if target filter is not changed, since there is
             // no change is service binding
@@ -1784,7 +1813,7 @@ public class DependencyManager<S, T> imp
         // invariant: target filter change + dynamic policy
 
         // 3. check optionality
-        if ( m_dependencyMetadata.isOptional() )
+        if ( newMinimumCardinality == 0 )
         {
             // can update since even if no service matches the new filter, this
             // makes no difference because the dependency is optional
@@ -1794,16 +1823,14 @@ public class DependencyManager<S, T> imp
 
         // 4. check target services matching the new filter
         ServiceReference<T>[] refs = getFrameworkServiceReferences( newTarget );
-        if ( refs != null && refs.length > 0 )
+        if ( refs != null )
         {
-            // can update since there is at least on service matching the
-            // new target filter and the services may be exchanged dynamically
-            return true;
+            // Return whether there are enough target services
+            return newMinimumCardinality <= refs.length;
         }
         // invariant: target filter change + dynamic policy + no more matching service + required
 
-        // 5. cannot dynamically update because the target filter results in
-        // no more applicable services which is not acceptable
+        // 5. There are no services, and some are required.
         return false;
     }
 
@@ -1820,7 +1847,34 @@ public class DependencyManager<S, T> imp
      */
     void setTargetFilter( Dictionary<String, Object> properties )
     {
-        setTargetFilter( ( String ) properties.get( m_dependencyMetadata.getTargetPropertyName() ) );
+        Integer minimumCardinality = getMinimumCardinality( properties );
+        setTargetFilter( ( String ) properties.get( m_dependencyMetadata.getTargetPropertyName() ),
+                minimumCardinality);
+    }
+
+    private int getMinimumCardinality(Dictionary<String, Object> properties)
+    {
+        Integer minimumCardinality = null;
+        try
+        {
+            minimumCardinality = Coercions.coerceToInteger( properties.get( m_dependencyMetadata.getMinCardinalityName()));
+        }
+        catch (ComponentException e)
+        {
+            m_componentManager.log( LogService.LOG_WARNING, "Invalid minimum cardinality property for dependency {0}: {1}", new Object[]
+                    {getName(), e.getMessage()}, null );
+        }
+        if (minimumCardinality != null && 
+                (minimumCardinality < defaultMinimumCardinality( m_dependencyMetadata ) ||
+                        (!m_dependencyMetadata.isMultiple() && minimumCardinality > 1 )))
+        {
+            minimumCardinality = null;
+        }
+        if ( minimumCardinality == null)
+        {
+            minimumCardinality = defaultMinimumCardinality( m_dependencyMetadata );
+        }
+        return minimumCardinality;
     }
 
 
@@ -1834,7 +1888,7 @@ public class DependencyManager<S, T> imp
      * @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)
+    private void setTargetFilter( String target, int minimumCardinality )
     {
         // if configuration does not set filter, use the value from metadata
         if (target == null)
@@ -1848,6 +1902,7 @@ public class DependencyManager<S, T> imp
                     {getName(), m_tracker != null}, null );
             if (m_tracker != null)
             {
+                m_minCardinality = minimumCardinality;
                 return;
             }
         }
@@ -1907,7 +1962,9 @@ public class DependencyManager<S, T> imp
                 {getName(), initialActive, refMap}, null );
         ServiceTracker<T, RefPair<T>> tracker = new ServiceTracker<T, RefPair<T>>( bundleContext, m_targetFilter, m_customizer, initialActive );
         m_customizer.setTracker( tracker );
-        //        m_registered = true;
+        //set minimum cardinality
+        m_minCardinality = minimumCardinality;
+        
         tracker.open( m_componentManager.getTrackingCount() );
         m_customizer.setTrackerOpened();
         if ( oldTracker != null )
@@ -2017,4 +2074,5 @@ public class DependencyManager<S, T> imp
             return false;
         }
     }
+
 }

Modified: felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/manager/ServiceTracker.java
URL: http://svn.apache.org/viewvc/felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/manager/ServiceTracker.java?rev=1560874&r1=1560873&r2=1560874&view=diff
==============================================================================
--- felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/manager/ServiceTracker.java (original)
+++ felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/manager/ServiceTracker.java Fri Jan 24 01:34:28 2014
@@ -865,15 +865,25 @@ public class ServiceTracker<S, T> {
 	 *         services.
 	 * @since 1.5
 	 */
-	public boolean isEmpty() {
-		final Tracked t = tracked();
-		if (t == null) { /* if ServiceTracker is not open */
-			return true;
-		}
-		synchronized (t) {
-			return t.isEmpty();
-		}
-	}
+    public boolean isEmpty() {
+        final Tracked t = tracked();
+        if (t == null) { /* if ServiceTracker is not open */
+            return true;
+        }
+        synchronized (t) {
+            return t.isEmpty();
+        }
+    }
+
+    public int getServiceCount() {
+        final Tracked t = tracked();
+        if (t == null) { /* if ServiceTracker is not open */
+            return 0;
+        }
+        synchronized (t) {
+            return t.size();
+        }
+    }
 
     public boolean isActive() {
         final Tracked t = tracked();
@@ -1171,6 +1181,7 @@ public class ServiceTracker<S, T> {
             T object = null;
             boolean becameUntracked = false;
             int trackingCount = -1;
+            int serviceCount = -1;
             /* Call customizer outside of synchronized region */
             try {
                 object = customizerAdding(item, related);
@@ -1187,6 +1198,7 @@ public class ServiceTracker<S, T> {
                          */
                         tracked.put( item, object );
                         trackingCount = modified(); /* increment modification count */
+                        serviceCount = tracked.size();
                         notifyAll(); /* notify any waiters */
                     } else {
                         becameUntracked = true;
@@ -1207,7 +1219,7 @@ public class ServiceTracker<S, T> {
                  * let it propagate
                  */
             } else {
-                customizerAdded( item, related, object, trackingCount );
+                customizerAdded( item, related, object, trackingCount, serviceCount );
             }
         }
 
@@ -1366,7 +1378,7 @@ public class ServiceTracker<S, T> {
          */
         abstract T customizerAdding( final S item, final R related );
 
-        abstract void customizerAdded( final S item, final R related, final T object, int trackingCount );
+        abstract void customizerAdded( final S item, final R related, final T object, int trackingCount, int serviceCount );
 
         /**
          * Call the specific customizer modified method. This method must not be
@@ -1476,8 +1488,8 @@ public class ServiceTracker<S, T> {
 			return customizer.addingService( item );
 		}
 
-		final void customizerAdded( final ServiceReference<S> item, final ServiceEvent related, final T object, int trackingCount ) {
-		    customizer.addedService( item, object, trackingCount );
+		final void customizerAdded( final ServiceReference<S> item, final ServiceEvent related, final T object, int trackingCount, int serviceCount ) {
+		    customizer.addedService( item, object, trackingCount, serviceCount );
 		}
 
 		/**

Modified: felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/manager/ServiceTrackerCustomizer.java
URL: http://svn.apache.org/viewvc/felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/manager/ServiceTrackerCustomizer.java?rev=1560874&r1=1560873&r2=1560874&view=diff
==============================================================================
--- felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/manager/ServiceTrackerCustomizer.java (original)
+++ felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/manager/ServiceTrackerCustomizer.java Fri Jan 24 01:34:28 2014
@@ -69,7 +69,7 @@ public interface ServiceTrackerCustomize
 	 */
 	public T addingService( ServiceReference<S> reference );
 
-    public void addedService( ServiceReference<S> reference, T service, int trackingCount );
+    public void addedService( ServiceReference<S> reference, T service, int trackingCount, int serviceCount );
 
 	/**
 	 * A service tracked by the {@code ServiceTracker} has been modified.
@@ -92,8 +92,8 @@ public interface ServiceTrackerCustomize
 	 * {@code ServiceTracker}.
 	 *
      * @param reference The reference to the service that has been removed.
-     * @param service The service object for the specified referenced service.
-     * @param trackingCount
+	 * @param service The service object for the specified referenced service.
+	 * @param trackingCount
      */
 	public void removedService( ServiceReference<S> reference, T service, int trackingCount );
 

Modified: felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/metadata/ReferenceMetadata.java
URL: http://svn.apache.org/viewvc/felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/metadata/ReferenceMetadata.java?rev=1560874&r1=1560873&r2=1560874&view=diff
==============================================================================
--- felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/metadata/ReferenceMetadata.java (original)
+++ felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/metadata/ReferenceMetadata.java Fri Jan 24 01:34:28 2014
@@ -434,6 +434,11 @@ public class ReferenceMetadata
     {
         return getName() + ".target";
     }
+    
+    public String getMinCardinalityName()
+    {
+        return getName() + ".cardinality.minimum";
+    }
 
 
     /**

Added: felix/trunk/scr/src/test/java/org/apache/felix/scr/integration/MinimumCardinalityTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/scr/src/test/java/org/apache/felix/scr/integration/MinimumCardinalityTest.java?rev=1560874&view=auto
==============================================================================
--- felix/trunk/scr/src/test/java/org/apache/felix/scr/integration/MinimumCardinalityTest.java (added)
+++ felix/trunk/scr/src/test/java/org/apache/felix/scr/integration/MinimumCardinalityTest.java Fri Jan 24 01:34:28 2014
@@ -0,0 +1,142 @@
+/*
+ * 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.integration;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.felix.scr.integration.components.SimpleComponent;
+import org.apache.felix.scr.integration.components.SimpleServiceImpl;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.service.cm.Configuration;
+import org.osgi.util.tracker.ServiceTracker;
+
+@RunWith(JUnit4TestRunner.class)
+public class MinimumCardinalityTest extends ComponentTestBase
+{
+    
+    private static final String pid = "MinimumCardinality";
+    
+    static
+    {
+        descriptorFile = "/integration_test_min_cardinality.xml";
+        // uncomment to enable debugging of this test class
+//         paxRunnerVmOption = DEBUG_VM_OPTION;
+        COMPONENT_PACKAGE = COMPONENT_PACKAGE;
+    }
+    
+    @Test
+    public void testMinCardinality() throws Exception
+    {
+        ServiceTracker<SimpleComponent, SimpleComponent> tracker = new ServiceTracker<SimpleComponent, SimpleComponent>(bundleContext, SimpleComponent.class, null);
+        tracker.open();
+        //configuration-policy require
+        assertNull(tracker.getService());
+        onePresent( tracker, null );
+        onePresent( tracker, -1 );
+        onePresent( tracker, 2 );
+        onePresent( tracker, "-1" );
+        onePresent( tracker, 'c' );
+        onePresent( tracker, "2" );
+        onePresent( tracker, new int[] {4, 0} );
+        onePresent( tracker, null );
+        
+        configureOne(1);
+        required(tracker, 1);
+        onePresent( tracker, null );
+        
+        getConfigurationAdmin().getConfiguration( pid, null ).delete();
+        delay();
+        assertNull(tracker.getService());
+        manyPresent( tracker, null );
+        manyPresent( tracker, -1 );
+        manyPresent( tracker, "-1" );
+        manyPresent( tracker, new int[] {-4, 0} );
+        manyPresent( tracker, null );
+        
+        configureMany(1);
+        required(tracker, 1);
+        configureMany(5);
+        required(tracker, 5);
+        manyPresent( tracker, null );
+    }
+    
+    private void required(ServiceTracker<SimpleComponent, SimpleComponent> tracker, int count)
+    {
+        delay();
+        List<SimpleServiceImpl> services = new ArrayList<SimpleServiceImpl>();
+        for (int i = 0; i < count; i++)
+        {
+            assertNull("Expected no tracked with " + i + " services present, count " + count, tracker.getService());
+            services.add(SimpleServiceImpl.create( bundleContext, String.valueOf( i ) ));
+        }
+        assertNotNull(tracker.getService());
+        for (SimpleServiceImpl service: services)
+        {
+            service.drop();
+            assertNull(tracker.getService());
+        }
+    }
+
+    private void onePresent(ServiceTracker<SimpleComponent, SimpleComponent> tracker, Object value) throws IOException
+    {
+        configureOne(value);
+        delay();
+        assertNotNull(tracker.getService());
+        assertEquals(1, tracker.getServices().length);
+    }
+    
+    private void configureOne(Object value ) throws IOException
+    {
+        configureTarget( "one.cardinality.minimum", value );
+    }
+
+    private void manyPresent(ServiceTracker<SimpleComponent, SimpleComponent> tracker, Object value) throws IOException
+    {
+        configureMany(value);
+        delay();
+        assertNotNull(tracker.getService());
+        assertEquals(1, tracker.getServices().length);
+    }
+    
+    private void configureMany(Object value ) throws IOException
+    {
+        configureTarget( "many.cardinality.minimum", value );
+    }
+
+    private void configureTarget(final String targetKey, Object value) throws IOException
+    {
+        Hashtable<String, Object> props = new Hashtable<String, Object>();
+        if ( value != null )
+        {
+            props.put( targetKey, value );
+        }
+        Configuration config = getConfigurationAdmin().getConfiguration( pid, null );
+        config.update(props);
+    }
+
+}

Propchange: felix/trunk/scr/src/test/java/org/apache/felix/scr/integration/MinimumCardinalityTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/scr/src/test/java/org/apache/felix/scr/integration/MinimumCardinalityTest.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: felix/trunk/scr/src/test/java/org/apache/felix/scr/integration/MinimumCardinalityTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: felix/trunk/scr/src/test/resources/integration_test_min_cardinality.xml
URL: http://svn.apache.org/viewvc/felix/trunk/scr/src/test/resources/integration_test_min_cardinality.xml?rev=1560874&view=auto
==============================================================================
--- felix/trunk/scr/src/test/resources/integration_test_min_cardinality.xml (added)
+++ felix/trunk/scr/src/test/resources/integration_test_min_cardinality.xml Fri Jan 24 01:34:28 2014
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<components xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
+
+    <scr:component name="MinimumCardinality"
+        configuration-policy="require"
+        modified="configure">
+        <implementation class="org.apache.felix.scr.integration.components.SimpleComponent" />
+        <service>
+            <provide interface="org.apache.felix.scr.integration.components.SimpleComponent" />
+        </service>
+        <property name="service.pid" value="DynamicConfigurationComponentWithRequiredReference" />
+        <reference
+            name="one"
+            interface="org.apache.felix.scr.integration.components.SimpleService"
+            cardinality="0..1"
+            policy="dynamic"
+            bind="setSimpleService"
+            unbind="unsetSimpleService"
+        />
+        <reference
+            name="many"
+            interface="org.apache.felix.scr.integration.components.SimpleService"
+            cardinality="0..n"
+            policy="dynamic"
+            bind="bindSimpleService"
+            unbind="unbindSimpleService"
+        />
+    </scr:component>
+
+</components>

Propchange: felix/trunk/scr/src/test/resources/integration_test_min_cardinality.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/scr/src/test/resources/integration_test_min_cardinality.xml
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: felix/trunk/scr/src/test/resources/integration_test_min_cardinality.xml
------------------------------------------------------------------------------
    svn:mime-type = text/xml