WIP: start implementing support for service scope, and proper implementation of factory service support
diff --git a/core/src/main/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtil.java b/core/src/main/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtil.java
index e094d10..216e022 100644
--- a/core/src/main/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtil.java
+++ b/core/src/main/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtil.java
@@ -22,6 +22,7 @@
 import java.io.InputStream;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -47,10 +48,12 @@
 import org.apache.commons.collections4.bidimap.TreeBidiMap;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.felix.framework.FilterImpl;
+import org.jetbrains.annotations.NotNull;
 import org.osgi.framework.Constants;
 import org.osgi.framework.Filter;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.annotations.ServiceScope;
 import org.reflections.Reflections;
 import org.reflections.scanners.Scanners;
 import org.slf4j.Logger;
@@ -287,6 +290,22 @@
         return serviceInterfaces;
     }
 
+    @SuppressWarnings("null")
+    private static @NotNull ServiceScope getServiceScope(Class clazz, Document metadata) {
+        String query = getComponentXPathQuery(clazz) + "/service";
+        String scopeValue;
+        NodeList nodes = queryNodes(metadata, query);
+        if (nodes != null && nodes.getLength() > 0) {
+            scopeValue = getAttributeValue(nodes.item(0), "scope");
+        }
+        else {
+            scopeValue = null;
+        }
+        return Arrays.stream(ServiceScope.values())
+                .filter(serviceScope -> StringUtils.equals(scopeValue, serviceScope.toString()))
+                .findFirst().orElse(ServiceScope.DEFAULT);
+    }
+
     private static Map<String, Object> getProperties(Class clazz, Document metadata) {
         Map<String, Object> props = new HashMap<String, Object>();
         String query = getComponentXPathQuery(clazz) + "/property[@name!='' and @value!='']";
@@ -384,6 +403,7 @@
         private final String name;
         private final String[] configurationPID;
         private final Set<String> serviceInterfaces;
+        private final ServiceScope serviceScope;
         private final Map<String, Object> properties;
         private final List<Reference> references;
         private final String activateMethodName;
@@ -395,6 +415,7 @@
             this.name = OsgiMetadataUtil.getComponentName(clazz, metadataDocument);
             this.configurationPID = OsgiMetadataUtil.getConfigurationPID(clazz, metadataDocument);
             this.serviceInterfaces = OsgiMetadataUtil.getServiceInterfaces(clazz, metadataDocument);
+            this.serviceScope = OsgiMetadataUtil.getServiceScope(clazz, metadataDocument);
             this.properties = OsgiMetadataUtil.getProperties(clazz, metadataDocument);
             this.references = OsgiMetadataUtil.getReferences(clazz, metadataDocument);
             this.activateMethodName = OsgiMetadataUtil.getLifecycleMethodName(clazz, metadataDocument, "activate");
@@ -407,6 +428,7 @@
             this.name = null;
             this.configurationPID = null;
             this.serviceInterfaces = null;
+            this.serviceScope = ServiceScope.DEFAULT;
             this.properties = null;
             this.references = null;
             this.activateMethodName = null;
@@ -438,6 +460,10 @@
             return serviceInterfaces;
         }
 
+        public @NotNull ServiceScope getServiceScope() {
+            return serviceScope;
+        }
+
         public Map<String, Object> getProperties() {
             return properties;
         }
diff --git a/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java b/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java
index a1d64f7..913bf27 100644
--- a/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java
+++ b/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java
@@ -30,10 +30,12 @@
 import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.OsgiMetadata;
 import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.Reference;
 import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.ReferenceCardinality;
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ScopePrototypeService;
 import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.Service3;
 import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ServiceInterface2;
 import org.junit.Test;
 import org.osgi.framework.Constants;
+import org.osgi.service.component.annotations.ServiceScope;
 
 public class OsgiMetadataUtilTest {
 
@@ -46,6 +48,7 @@
         Set<String> serviceInterfaces = metadata.getServiceInterfaces();
         assertEquals(1, serviceInterfaces.size());
         assertTrue(serviceInterfaces.contains("java.lang.Comparable"));
+        assertEquals(ServiceScope.DEFAULT, metadata.getServiceScope());
 
         Map<String, Object> props = metadata.getProperties();
         assertEquals(3, props.size());
@@ -81,6 +84,12 @@
         assertEquals("activate", metadata.getActivateMethodName());
     }
 
+    @Test
+    public void testServiceScope() {
+        OsgiMetadata metadata = OsgiMetadataUtil.getMetadata(ScopePrototypeService.class);
+        assertEquals(ServiceScope.PROTOTYPE, metadata.getServiceScope());
+    }
+
     static class ServiceWithMetadata {
         // empty class
     }
diff --git a/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java b/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java
index 3e428ce..01208f5 100644
--- a/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java
+++ b/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
@@ -32,13 +33,15 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ScopePrototypeService;
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ScopePrototypeServiceFactory;
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ScopePrototypeServiceFactory.ScopePrototpyeInstance;
 import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.Service1;
 import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.Service2;
 import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.Service3;
 import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.Service3OsgiR6;
 import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.Service4;
 import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.Service5;
-import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ServiceFactory1;
 import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ServiceInterface1;
 import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ServiceInterface2;
 import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ServiceInterface3;
@@ -48,10 +51,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mockito;
-import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceFactory;
-import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.ServiceReference;
 
 import com.google.common.collect.ImmutableMap;
 
@@ -210,34 +211,38 @@
     }
 
     @Test
-    public void testServiceFactoryViaScr() {
-        ServiceFactory1 serviceFactory1 = new ServiceFactory1();
+    public void testScopePrototypeService() {
+        MockOsgi.registerInjectActivateService(ScopePrototypeService.class, bundleContext);
 
-        MockOsgi.injectServices(serviceFactory1, bundleContext);
-        MockOsgi.activate(serviceFactory1, bundleContext, (Dictionary<String, Object>) null);
-        bundleContext.registerService(ServiceFactory1.class.getName(), serviceFactory1, null);
+        ServiceReference<ScopePrototypeService> ref = bundleContext.getServiceReference(ScopePrototypeService.class);
+        assertNotNull(ref);
 
-        assertSame(serviceFactory1, bundleContext.getService(
-                bundleContext.getServiceReference(ServiceFactory1.class.getName())));
+        ScopePrototypeService instance1 = bundleContext.getService(ref);
+        assertNotNull(instance1);
+
+        ScopePrototypeService instance2 = bundleContext.getService(ref);
+        assertNotNull(instance2);
+
+        assertNotSame(instance1, instance2);
+        assertTrue(instance2.getInstanceId() > instance1.getInstanceId());
     }
 
     @Test
-    public void testServiceFactoryViaManualRegistration() {
-        final ServiceFactory1 serviceFactory1 = new ServiceFactory1();
+    @SuppressWarnings("unchecked")
+    public void testScopePrototypeServiceFactory() {
+        MockOsgi.registerInjectActivateService(ScopePrototypeServiceFactory.class, bundleContext);
 
-        bundleContext.registerService(ServiceFactory1.class.getName(), new ServiceFactory() {
-            @Override
-            public Object getService(Bundle bundle, ServiceRegistration registration) {
-                return serviceFactory1;
-            }
-            @Override
-            public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {
-                // nothing to do
-            }
-        }, null);
+        ServiceReference<ScopePrototpyeInstance> ref = (ServiceReference)bundleContext.getServiceReference(ScopePrototypeServiceFactory.class);
+        assertNotNull(ref);
 
-        assertSame(serviceFactory1, bundleContext.getService(
-                bundleContext.getServiceReference(ServiceFactory1.class.getName())));
+        ScopePrototpyeInstance instance1 = bundleContext.getService(ref);
+        assertNotNull(instance1);
+
+        ScopePrototpyeInstance instance2 = bundleContext.getService(ref);
+        assertNotNull(instance2);
+
+        assertNotSame(instance1, instance2);
+        assertTrue(instance2.getInstanceId() > instance1.getInstanceId());
     }
 
 }
diff --git a/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/ServiceFactory1.java b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/ScopePrototypeService.java
similarity index 67%
rename from test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/ServiceFactory1.java
rename to test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/ScopePrototypeService.java
index 3fcbef7..199ed3a 100644
--- a/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/ServiceFactory1.java
+++ b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/ScopePrototypeService.java
@@ -18,9 +18,20 @@
  */
 package org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil;
 
-import org.osgi.service.component.annotations.Component;
+import java.util.concurrent.atomic.AtomicLong;
 
-@Component(service = ServiceFactory1.class, servicefactory = true)
-public class ServiceFactory1 {
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ServiceScope;
+
+@Component(service = ScopePrototypeService.class, scope = ServiceScope.PROTOTYPE)
+public class ScopePrototypeService {
+
+    private static final AtomicLong INSTANCE_COUNTER = new AtomicLong();
+
+    private final long instanceId = INSTANCE_COUNTER.incrementAndGet();
+
+    public long getInstanceId() {
+        return instanceId;
+    }
 
 }
diff --git a/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/ScopePrototypeServiceFactory.java b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/ScopePrototypeServiceFactory.java
new file mode 100644
index 0000000..31744f1
--- /dev/null
+++ b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/ScopePrototypeServiceFactory.java
@@ -0,0 +1,60 @@
+/*
+ * 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.sling.testing.mock.osgi.testsvc.osgiserviceutil;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.sling.testing.mock.osgi.testsvc.osgiserviceutil.ScopePrototypeServiceFactory.ScopePrototpyeInstance;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ServiceScope;
+
+@Component(service = ScopePrototypeServiceFactory.class, scope = ServiceScope.PROTOTYPE)
+public class ScopePrototypeServiceFactory implements ServiceFactory<ScopePrototpyeInstance> {
+
+    private static final AtomicLong INSTANCE_COUNTER = new AtomicLong();
+
+    @Override
+    public ScopePrototpyeInstance getService(Bundle bundle, ServiceRegistration<ScopePrototpyeInstance> registration) {
+        return new ScopePrototpyeInstance(INSTANCE_COUNTER.incrementAndGet());
+    }
+
+    @Override
+    public void ungetService(Bundle bundle, ServiceRegistration<ScopePrototpyeInstance> registration,
+            ScopePrototpyeInstance service) {
+        // nothing to do
+    }
+
+    public static class ScopePrototpyeInstance {
+
+        private final long instanceId;
+
+        private ScopePrototpyeInstance(long instanceId) {
+            this.instanceId = instanceId;
+        }
+
+        public long getInstanceId() {
+            return instanceId;
+        }
+
+    }
+
+}