You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aries.apache.org by da...@apache.org on 2011/11/18 16:51:33 UTC

svn commit: r1203707 - in /aries/trunk/spi-fly/spi-fly-core/src: main/java/org/apache/aries/spifly/ main/java/org/apache/aries/spifly/api/ test/java/org/apache/aries/spifly/

Author: davidb
Date: Fri Nov 18 15:51:33 2011
New Revision: 1203707

URL: http://svn.apache.org/viewvc?rev=1203707&view=rev
Log:
Initial support for specifying the SPI Provider capability as 
  Provide-Capability: osgi.spi.provider
  
Retained the possibility of specifying this via the SPI-Provider header.

Added:
    aries/trunk/spi-fly/spi-fly-core/src/test/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizerTest2.java   (with props)
Modified:
    aries/trunk/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/BaseActivator.java
    aries/trunk/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizer.java
    aries/trunk/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/api/SpiFlyConstants.java

Modified: aries/trunk/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/BaseActivator.java
URL: http://svn.apache.org/viewvc/aries/trunk/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/BaseActivator.java?rev=1203707&r1=1203706&r2=1203707&view=diff
==============================================================================
--- aries/trunk/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/BaseActivator.java (original)
+++ aries/trunk/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/BaseActivator.java Fri Nov 18 15:51:33 2011
@@ -132,7 +132,7 @@ public abstract class BaseActivator impl
     }
 
     public Set<WeavingData> getWeavingData(Bundle b) {
-        // Simply return the value as it's already an unmovable set.
+        // Simply return the value as it's already an immutable set.
         Set<WeavingData> wd = bundleWeavingData.get(b);
         if (wd == null)
             return null;

Modified: aries/trunk/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizer.java
URL: http://svn.apache.org/viewvc/aries/trunk/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizer.java?rev=1203707&r1=1203706&r2=1203707&view=diff
==============================================================================
--- aries/trunk/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizer.java (original)
+++ aries/trunk/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizer.java Fri Nov 18 15:51:33 2011
@@ -24,6 +24,7 @@ import java.io.InputStreamReader;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.Hashtable;
 import java.util.List;
@@ -60,7 +61,15 @@ public class ProviderBundleTrackerCustom
             return null;
         }
 
-        if (bundle.getHeaders().get(SpiFlyConstants.SPI_PROVIDER_HEADER) == null) {
+        List<String> providedServices = null;
+        if (bundle.getHeaders().get("Provide-Capability") != null) {
+            providedServices = readProvideCapability(bundle.getHeaders());
+        }
+        if (providedServices == null && bundle.getHeaders().get(SpiFlyConstants.SPI_PROVIDER_HEADER) != null) {
+            providedServices = new ArrayList<String>();
+        }
+
+        if (providedServices == null) {
             log(LogService.LOG_INFO, "No '"
                     + SpiFlyConstants.SPI_PROVIDER_HEADER
                     + "' Manifest header. Skipping bundle: "
@@ -106,36 +115,83 @@ public class ProviderBundleTrackerCustom
                         new InputStreamReader(serviceFile.openStream()));
                 String className = null;
                 while((className = reader.readLine()) != null) {
-                    Class<?> cls = bundle.loadClass(className);
-                    Object o = cls.newInstance();
-                    log(LogService.LOG_INFO, "Instantiated SPI provider: " + o);
-
-                    Hashtable<String, Object> props = new Hashtable<String, Object>();
-                    props.put(SpiFlyConstants.SPI_PROVIDER_URL, serviceFile);
-
-                    String s = serviceFile.toExternalForm();
-                    int idx = s.lastIndexOf('/');
-                    String registrationClassName = className;
-                    if (s.length() > idx) {
-                        registrationClassName = s.substring(idx + 1);
+                    try {
+                        if (className.startsWith("#"))
+                            continue; // a comment
+
+                        Class<?> cls = bundle.loadClass(className);
+                        Object o = cls.newInstance();
+                        log(LogService.LOG_INFO, "Instantiated SPI provider: " + o);
+
+                        Hashtable<String, Object> props = new Hashtable<String, Object>();
+                        props.put(SpiFlyConstants.SPI_PROVIDER_URL, serviceFile);
+
+                        String s = serviceFile.toExternalForm();
+                        int idx = s.lastIndexOf('/');
+                        String registrationClassName = className;
+                        if (s.length() > idx) {
+                            registrationClassName = s.substring(idx + 1);
+                        }
+
+                        ServiceRegistration reg = bundle.getBundleContext()
+                                .registerService(registrationClassName, o, props);
+                        registrations.add(reg);
+
+                        activator.registerProviderBundle(registrationClassName, bundle);
+                        log(LogService.LOG_INFO, "Registered service: " + reg);
+                    } catch (Exception e) {
+                        log(LogService.LOG_WARNING,
+                                "Could not load SPI implementation referred from " + serviceFile, e);
                     }
-
-                    ServiceRegistration reg = bundle.getBundleContext()
-                            .registerService(registrationClassName, o, props);
-                    registrations.add(reg);
-
-                    activator.registerProviderBundle(registrationClassName, bundle);
-                    log(LogService.LOG_INFO, "Registered service: " + reg);
                 }
-            } catch (Exception e) {
-                log(LogService.LOG_WARNING,
-                        "Could not load SPI implementation referred from " + serviceFile, e);
+            } catch (IOException e) {
+                log(LogService.LOG_WARNING, "Could not read SPI metadata from " + serviceFile, e);
             }
         }
 
         return registrations;
     }
 
+    // An empty list returned means 'all SPIs'
+    // A return value of null means no SPIs
+    // A populated list means: only these SPIs
+    private List<String> readProvideCapability(Dictionary<?,?> headers) {
+        if (headers.get(SpiFlyConstants.PROVIDE_CAPABILITY) == null)
+            return null;
+
+        String pc = headers.get(SpiFlyConstants.PROVIDE_CAPABILITY).toString();
+        for (String c : pc.split(",")) { // TODO cover situation where ',' is inside a string (e.g. service:List<String>).
+            c = c.trim();
+            int idx = c.indexOf(';');
+            if (idx < 0)
+                continue;
+
+            String ns = c.substring(0, idx);
+            if (!SpiFlyConstants.SPI_CAPABILITY_NAMESPACE.equals(ns))
+                continue;
+
+            List<String> providedServices = new ArrayList<String>();
+            String attrs = c.substring(idx);
+            for (String attr : attrs.split(";")) {
+                attr = attr.trim();
+                if (attr.startsWith("service=")) {
+                    String val = attr.substring("service=".length());
+                    providedServices.add(val);
+                } else if (attr.startsWith("service:String=")) {
+                    String val = attr.substring("service:String=".length());
+                    providedServices.add(val);
+                } else if (attr.startsWith("service:List<String>=")) {
+                    String val = attr.substring("service:List<String>=".length());
+                    for (String v : val.split(",")) {
+                        providedServices.add(v.trim());
+                    }
+                }
+            }
+            return providedServices; // An empty array means all SPIs
+        }
+        return null;
+    }
+
     private List<URL> getMetaInfServiceURLsFromJar(URL url) {
         List<URL> urls = new ArrayList<URL>();
         try {

Modified: aries/trunk/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/api/SpiFlyConstants.java
URL: http://svn.apache.org/viewvc/aries/trunk/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/api/SpiFlyConstants.java?rev=1203707&r1=1203706&r2=1203707&view=diff
==============================================================================
--- aries/trunk/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/api/SpiFlyConstants.java (original)
+++ aries/trunk/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/api/SpiFlyConstants.java Fri Nov 18 15:51:33 2011
@@ -19,9 +19,15 @@
 package org.apache.aries.spifly.api;
 
 public interface SpiFlyConstants {
+    String PROVIDE_CAPABILITY = "Provide-Capability";
+    String REQUIRE_CAPABILITY = "Require-Capability";
+
     String SPI_CONSUMER_HEADER = "SPI-Consumer";
     String SPI_PROVIDER_HEADER = "SPI-Provider";
 
+    String SPI_CAPABILITY_NAMESPACE = "osgi.spi.provider";
+    String EXTENDER_CAPABILITY_NAMESPACE = "osgi.jse.serviceloader";
+
     String PROCESSED_SPI_CONSUMER_HEADER = "X-SpiFly-Processed-SPI-Consumer";
 
     String SPI_PROVIDER_URL = "spi.provider.url";

Added: aries/trunk/spi-fly/spi-fly-core/src/test/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizerTest2.java
URL: http://svn.apache.org/viewvc/aries/trunk/spi-fly/spi-fly-core/src/test/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizerTest2.java?rev=1203707&view=auto
==============================================================================
--- aries/trunk/spi-fly/spi-fly-core/src/test/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizerTest2.java (added)
+++ aries/trunk/spi-fly/spi-fly-core/src/test/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizerTest2.java Fri Nov 18 15:51:33 2011
@@ -0,0 +1,205 @@
+/**
+ * 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.aries.spifly;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import org.apache.aries.spifly.impl1.MySPIImpl1;
+import org.apache.aries.spifly.impl2.MySPIImpl2a;
+import org.apache.aries.spifly.impl2.MySPIImpl2b;
+import org.apache.aries.spifly.impl3.MySPIImpl3;
+import org.easymock.EasyMock;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+
+public class ProviderBundleTrackerCustomizerTest2 {
+    @Test
+    public void testAddingRemovedBundle() throws Exception {
+        Bundle spiBundle = EasyMock.createMock(Bundle.class);
+        EasyMock.replay(spiBundle);
+        BaseActivator activator = new BaseActivator() {
+            @Override
+            public void start(BundleContext context) throws Exception {}
+        };
+
+        ProviderBundleTrackerCustomizer customizer = new ProviderBundleTrackerCustomizer(activator, spiBundle);
+
+        ServiceRegistration sreg = EasyMock.createMock(ServiceRegistration.class);
+        sreg.unregister();
+        EasyMock.expectLastCall();
+        EasyMock.replay(sreg);
+
+        BundleContext implBC = mockSPIBundleContext(sreg);
+        Bundle implBundle = mockSPIBundle(implBC);
+
+        assertEquals("Precondition", 0, activator.findProviderBundles("org.apache.aries.mytest.MySPI").size());
+        // Call addingBundle();
+        List<ServiceRegistration> registrations = customizer.addingBundle(implBundle, null);
+        Collection<Bundle> bundles = activator.findProviderBundles("org.apache.aries.mytest.MySPI");
+        assertEquals(1, bundles.size());
+        assertSame(implBundle, bundles.iterator().next());
+
+        // The bc.registerService() call should now have been made
+        EasyMock.verify(implBC);
+
+        // Call removedBundle();
+        customizer.removedBundle(implBundle, null, registrations);
+        // sreg.unregister() should have been called.
+        EasyMock.verify(sreg);
+    }
+
+    @Test
+    public void testAddingBundleSPIBundle() throws Exception {
+        BundleContext implBC = mockSPIBundleContext(EasyMock.createNiceMock(ServiceRegistration.class));
+        Bundle spiBundle = mockSPIBundle(implBC);
+
+        ProviderBundleTrackerCustomizer customizer = new ProviderBundleTrackerCustomizer(EasyMock.createNiceMock(BaseActivator.class), spiBundle);
+        assertNull("The SpiFly bundle itself should be ignored", customizer.addingBundle(spiBundle, null));
+    }
+
+    @Test
+    public void testAddingNonOptInBundle() throws Exception {
+        BundleContext implBC = mockSPIBundleContext(EasyMock.createNiceMock(ServiceRegistration.class));
+        Bundle implBundle = mockSPIBundle(implBC, null);
+
+        ProviderBundleTrackerCustomizer customizer = new ProviderBundleTrackerCustomizer(EasyMock.createNiceMock(BaseActivator.class), null);
+        assertNull("Bundle doesn't opt-in so should be ignored", customizer.addingBundle(implBundle, null));
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testAddingBundleWithBundleClassPath() throws Exception {
+        Bundle spiBundle = EasyMock.createMock(Bundle.class);
+        EasyMock.replay(spiBundle);
+        BaseActivator activator = new BaseActivator() {
+            @Override
+            public void start(BundleContext context) throws Exception {}
+        };
+
+        ProviderBundleTrackerCustomizer customizer = new ProviderBundleTrackerCustomizer(activator, spiBundle);
+
+        BundleContext implBC = EasyMock.createMock(BundleContext.class);
+        EasyMock.<Object>expect(implBC.registerService(
+                EasyMock.eq("org.apache.aries.mytest.MySPI"),
+                EasyMock.isA(MySPIImpl2a.class),
+                (Dictionary<String,?>) EasyMock.anyObject())).andReturn(EasyMock.createNiceMock(ServiceRegistration.class));
+        EasyMock.<Object>expect(implBC.registerService(
+                EasyMock.eq("org.apache.aries.mytest.MySPI"),
+                EasyMock.isA(MySPIImpl2b.class),
+                (Dictionary<String,?>) EasyMock.anyObject())).andReturn(EasyMock.createNiceMock(ServiceRegistration.class));
+        EasyMock.<Object>expect(implBC.registerService(
+                EasyMock.eq("org.apache.aries.mytest.MySPI"),
+                EasyMock.isA(MySPIImpl3.class),
+                (Dictionary<String,?>) EasyMock.anyObject())).andReturn(EasyMock.createNiceMock(ServiceRegistration.class));
+        EasyMock.replay(implBC);
+
+
+        Bundle implBundle = EasyMock.createNiceMock(Bundle.class);
+        EasyMock.expect(implBundle.getBundleContext()).andReturn(implBC).anyTimes();
+
+        Dictionary<String, String> headers = new Hashtable<String, String>();
+        headers.put("Provide-Capability", "osgi.spi.provider; effective:=active;");
+        headers.put(Constants.BUNDLE_CLASSPATH, ".,non-jar.jar,embedded.jar,embedded2.jar");
+        EasyMock.expect(implBundle.getHeaders()).andReturn(headers).anyTimes();
+
+        URL embeddedJar = getClass().getResource("/embedded.jar");
+        assertNotNull("precondition", embeddedJar);
+        EasyMock.expect(implBundle.getResource("embedded.jar")).andReturn(embeddedJar).anyTimes();
+        URL embedded2Jar = getClass().getResource("/embedded2.jar");
+        assertNotNull("precondition", embedded2Jar);
+        EasyMock.expect(implBundle.getResource("embedded2.jar")).andReturn(embedded2Jar).anyTimes();
+        URL dir = new URL("jar:" + embeddedJar + "!/META-INF/services");
+        assertNotNull("precondition", dir);
+        EasyMock.expect(implBundle.getResource("/META-INF/services")).andReturn(dir).anyTimes();
+        EasyMock.expect(implBundle.findEntries((String) EasyMock.anyObject(), (String) EasyMock.anyObject(), EasyMock.anyBoolean())).
+            andReturn(null).anyTimes();
+
+        ClassLoader cl = new URLClassLoader(new URL [] {embeddedJar}, getClass().getClassLoader());
+        Class<?> clsA = cl.loadClass("org.apache.aries.spifly.impl2.MySPIImpl2a");
+        EasyMock.<Object>expect(implBundle.loadClass("org.apache.aries.spifly.impl2.MySPIImpl2a")).andReturn(clsA).anyTimes();
+        Class<?> clsB = cl.loadClass("org.apache.aries.spifly.impl2.MySPIImpl2b");
+        EasyMock.<Object>expect(implBundle.loadClass("org.apache.aries.spifly.impl2.MySPIImpl2b")).andReturn(clsB).anyTimes();
+        ClassLoader cl2 = new URLClassLoader(new URL [] {embedded2Jar}, getClass().getClassLoader());
+        Class<?> clsC = cl2.loadClass("org.apache.aries.spifly.impl3.MySPIImpl3");
+        EasyMock.<Object>expect(implBundle.loadClass("org.apache.aries.spifly.impl3.MySPIImpl3")).andReturn(clsC).anyTimes();
+        EasyMock.replay(implBundle);
+
+        assertEquals("Precondition", 0, activator.findProviderBundles("org.apache.aries.mytest.MySPI").size());
+        // Call addingBundle();
+        List<ServiceRegistration> registrations = customizer.addingBundle(implBundle, null);
+        Collection<Bundle> bundles = activator.findProviderBundles("org.apache.aries.mytest.MySPI");
+        assertEquals(1, bundles.size());
+        assertSame(implBundle, bundles.iterator().next());
+
+        // The bc.registerService() call should now have been made
+        EasyMock.verify(implBC);
+    }
+
+    @SuppressWarnings("unchecked")
+    private BundleContext mockSPIBundleContext(ServiceRegistration sreg) {
+        BundleContext implBC = EasyMock.createMock(BundleContext.class);
+        EasyMock.<Object>expect(implBC.registerService(
+                EasyMock.eq("org.apache.aries.mytest.MySPI"),
+                EasyMock.isA(MySPIImpl1.class),
+                (Dictionary<String,?>) EasyMock.anyObject())).andReturn(sreg);
+        EasyMock.replay(implBC);
+        return implBC;
+    }
+
+    private Bundle mockSPIBundle(BundleContext implBC) throws ClassNotFoundException {
+        return mockSPIBundle(implBC, "osgi.spi.provider; effective:=active;");
+    }
+
+    private Bundle mockSPIBundle(BundleContext implBC, String spiProviderHeader) throws ClassNotFoundException {
+        Bundle implBundle = EasyMock.createNiceMock(Bundle.class);
+        EasyMock.expect(implBundle.getBundleContext()).andReturn(implBC).anyTimes();
+
+        Dictionary<String, String> headers = new Hashtable<String, String>();
+        if (spiProviderHeader != null)
+            headers.put("Provide-Capability", spiProviderHeader);
+        EasyMock.expect(implBundle.getHeaders()).andReturn(headers).anyTimes();
+
+        // List the resources found at META-INF/services in the test bundle
+        URL dir = getClass().getResource("impl1/META-INF/services");
+        assertNotNull("precondition", dir);
+        EasyMock.expect(implBundle.getResource("/META-INF/services")).andReturn(dir).anyTimes();
+        URL res = getClass().getResource("impl1/META-INF/services/org.apache.aries.mytest.MySPI");
+        assertNotNull("precondition", res);
+        EasyMock.expect(implBundle.findEntries("META-INF/services", "*", false)).andReturn(
+                Collections.enumeration(Collections.singleton(res))).anyTimes();
+        Class<?> cls = getClass().getClassLoader().loadClass("org.apache.aries.spifly.impl1.MySPIImpl1");
+        EasyMock.<Object>expect(implBundle.loadClass("org.apache.aries.spifly.impl1.MySPIImpl1")).andReturn(cls).anyTimes();
+        EasyMock.replay(implBundle);
+        return implBundle;
+    }
+}

Propchange: aries/trunk/spi-fly/spi-fly-core/src/test/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizerTest2.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain