Return-Path: Delivered-To: apmail-incubator-aries-commits-archive@minotaur.apache.org Received: (qmail 19660 invoked from network); 6 Mar 2010 20:21:08 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 6 Mar 2010 20:21:08 -0000 Received: (qmail 70251 invoked by uid 500); 6 Mar 2010 20:20:50 -0000 Delivered-To: apmail-incubator-aries-commits-archive@incubator.apache.org Received: (qmail 70157 invoked by uid 500); 6 Mar 2010 20:20:50 -0000 Mailing-List: contact aries-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: aries-dev@incubator.apache.org Delivered-To: mailing list aries-commits@incubator.apache.org Received: (qmail 70149 invoked by uid 99); 6 Mar 2010 20:20:50 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 06 Mar 2010 20:20:50 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 06 Mar 2010 20:20:46 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 0325D238890A; Sat, 6 Mar 2010 20:20:25 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r919842 - in /incubator/aries/trunk/jndi/jndi-url/src: main/java/org/apache/aries/jndi/services/ main/java/org/apache/aries/jndi/url/ test/java/org/apache/aries/jndi/url/ Date: Sat, 06 Mar 2010 20:20:24 -0000 To: aries-commits@incubator.apache.org From: not@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20100306202025.0325D238890A@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: not Date: Sat Mar 6 20:20:24 2010 New Revision: 919842 URL: http://svn.apache.org/viewvc?rev=919842&view=rev Log: ARIES-128 Implement support for querying multiple services via JNDI url. Modified: incubator/aries/trunk/jndi/jndi-url/src/main/java/org/apache/aries/jndi/services/ServiceHelper.java incubator/aries/trunk/jndi/jndi-url/src/main/java/org/apache/aries/jndi/url/ServiceRegistryContext.java incubator/aries/trunk/jndi/jndi-url/src/main/java/org/apache/aries/jndi/url/ServiceRegistryListContext.java incubator/aries/trunk/jndi/jndi-url/src/test/java/org/apache/aries/jndi/url/ServiceRegistryContextTest.java Modified: incubator/aries/trunk/jndi/jndi-url/src/main/java/org/apache/aries/jndi/services/ServiceHelper.java URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jndi/jndi-url/src/main/java/org/apache/aries/jndi/services/ServiceHelper.java?rev=919842&r1=919841&r2=919842&view=diff ============================================================================== --- incubator/aries/trunk/jndi/jndi-url/src/main/java/org/apache/aries/jndi/services/ServiceHelper.java (original) +++ incubator/aries/trunk/jndi/jndi-url/src/main/java/org/apache/aries/jndi/services/ServiceHelper.java Sat Mar 6 20:20:24 2010 @@ -136,7 +136,9 @@ boolean foundLookup = false; int i = 0; for (; i < stackTrace.length && !!!found; i++) { - if (!!!foundLookup && "lookup".equals(stackTrace[i].getMethodName())) { + if (!!!foundLookup && ("lookup".equals(stackTrace[i].getMethodName()) || + "list".equals(stackTrace[i].getMethodName()) || + "listBindings".equals(stackTrace[i].getMethodName()))) { foundLookup = true; } else if (foundLookup && !!!(stackTrace[i].getClassName().startsWith("org.apache.aries.jndi") || stackTrace[i].getClassName().startsWith("javax.naming"))) { @@ -178,62 +180,80 @@ return result; } - public static Object getService(String interface1, String filter, String serviceName, boolean dynamicRebind, Map env) throws NamingException + public static Object getService(String interface1, String filter, String serviceName, String id, boolean dynamicRebind, Map env) throws NamingException { Object result = null; BundleContext ctx = getBundleContext(env); + if (id != null && filter == null) { + filter = '(' + Constants.SERVICE_ID + '=' + id + ')'; + } else if (id != null && filter != null) { + filter = "(&(" + Constants.SERVICE_ID + '=' + id + ')' + filter + ')'; + } + ServicePair pair = findService(ctx, interface1, filter); if (pair == null) { interface1 = null; - filter = "(osgi.jndi.service.name=" + serviceName + ")"; + if (id == null) { + filter = "(osgi.jndi.service.name=" + serviceName + ')'; + } else { + filter = "(&(" + Constants.SERVICE_ID + '=' + id + ")(osgi.jndi.service.name=" + serviceName + "))"; + } pair = findService(ctx, interface1, filter); } if (pair != null) { - String[] interfaces = (String[]) pair.ref.getProperty(Constants.OBJECTCLASS); - - List> clazz = new ArrayList>(interfaces.length); - - // We load the interface classes the service is registered under using the defining - // bundle. This is ok because the service must be able to see the classes to be - // registered using them. We then check to see if isAssignableTo on the reference - // works for the owning bundle and the interface name and only use the interface if - // true is returned there. - - // This might seem odd, but equinox and felix return true for isAssignableTo if the - // Bundle provided does not import the package. This is under the assumption the - // caller will then use reflection. The upshot of doing it this way is that a utility - // bundle can be created which centralizes JNDI lookups, but the service will be used - // by another bundle. It is true that class space consistency is less safe, but we - // are enabling a slightly odd use case anyway. - - Bundle serviceProviderBundle = pair.ref.getBundle(); - Bundle owningBundle = ctx.getBundle(); - - for (String interfaceName : interfaces) { - try { - Class potentialClass = serviceProviderBundle.loadClass(interfaceName); - - if (pair.ref.isAssignableTo(owningBundle, interfaceName)) clazz.add(potentialClass); - } catch (ClassNotFoundException e) { - } - } - - if (clazz.isEmpty()) { - throw new IllegalArgumentException(Arrays.asList(interfaces).toString()); + result = proxy(interface1, filter, dynamicRebind, ctx, pair); + } + + return result; + } + + private static Object proxy(String interface1, String filter, boolean dynamicRebind, + BundleContext ctx, ServicePair pair) + { + Object result; + String[] interfaces = (String[]) pair.ref.getProperty(Constants.OBJECTCLASS); + + List> clazz = new ArrayList>(interfaces.length); + + // We load the interface classes the service is registered under using the defining + // bundle. This is ok because the service must be able to see the classes to be + // registered using them. We then check to see if isAssignableTo on the reference + // works for the owning bundle and the interface name and only use the interface if + // true is returned there. + + // This might seem odd, but equinox and felix return true for isAssignableTo if the + // Bundle provided does not import the package. This is under the assumption the + // caller will then use reflection. The upshot of doing it this way is that a utility + // bundle can be created which centralizes JNDI lookups, but the service will be used + // by another bundle. It is true that class space consistency is less safe, but we + // are enabling a slightly odd use case anyway. + + Bundle serviceProviderBundle = pair.ref.getBundle(); + Bundle owningBundle = ctx.getBundle(); + + for (String interfaceName : interfaces) { + try { + Class potentialClass = serviceProviderBundle.loadClass(interfaceName); + + if (pair.ref.isAssignableTo(owningBundle, interfaceName)) clazz.add(potentialClass); + } catch (ClassNotFoundException e) { } - - InvocationHandler ih = new JNDIServiceDamper(ctx, interface1, filter, pair, dynamicRebind); - - // The ClassLoader needs to be able to load the service interface classes so it needs to be - // wrapping the service provider bundle. The class is actually defined on this adapter. - - result = Proxy.newProxyInstance(new BundleToClassLoaderAdapter(serviceProviderBundle), clazz.toArray(new Class[clazz.size()]), ih); } + if (clazz.isEmpty()) { + throw new IllegalArgumentException(Arrays.asList(interfaces).toString()); + } + + InvocationHandler ih = new JNDIServiceDamper(ctx, interface1, filter, pair, dynamicRebind); + + // The ClassLoader needs to be able to load the service interface classes so it needs to be + // wrapping the service provider bundle. The class is actually defined on this adapter. + + result = Proxy.newProxyInstance(new BundleToClassLoaderAdapter(serviceProviderBundle), clazz.toArray(new Class[clazz.size()]), ih); return result; } @@ -253,8 +273,6 @@ } }); - Bundle b = ctx.getBundle(); - for (ServiceReference ref : refs) { Object service = ctx.getService(ref); @@ -273,4 +291,51 @@ return p; } + + public static ServiceReference[] getServiceReferences(String interface1, String filter, + String serviceName, Map env) throws NamingException + { + BundleContext ctx = getBundleContext(env); + ServiceReference[] refs = null; + + try { + refs = ctx.getServiceReferences(interface1, filter); + + if (refs == null || refs.length == 0) { + refs = ctx.getServiceReferences(null, "(osgi.jndi.service.name=" + serviceName + ')'); + } + } catch (InvalidSyntaxException e) { + throw (NamingException) new NamingException(e.getFilter()).initCause(e); + } + + if (refs != null) { + // natural order is the exact opposite of the order we desire. + Arrays.sort(refs, new Comparator() { + public int compare(ServiceReference o1, ServiceReference o2) + { + return o2.compareTo(o1); + } + }); + } + + return refs; + } + + public static Object getService(BundleContext ctx, ServiceReference ref) + { + Object service = ctx.getService(ref); + + Object result = null; + + if (service != null) { + ServicePair pair = new ServicePair(); + pair.ref = ref; + pair.service = service; + + result = proxy(null, null, false, ctx, pair); + } + + return result; + } + } Modified: incubator/aries/trunk/jndi/jndi-url/src/main/java/org/apache/aries/jndi/url/ServiceRegistryContext.java URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jndi/jndi-url/src/main/java/org/apache/aries/jndi/url/ServiceRegistryContext.java?rev=919842&r1=919841&r2=919842&view=diff ============================================================================== --- incubator/aries/trunk/jndi/jndi-url/src/main/java/org/apache/aries/jndi/url/ServiceRegistryContext.java (original) +++ incubator/aries/trunk/jndi/jndi-url/src/main/java/org/apache/aries/jndi/url/ServiceRegistryContext.java Sat Mar 6 20:20:24 2010 @@ -186,7 +186,7 @@ result = ServiceHelper.getBundleContext(env); } else if ((OsgiName.SERVICE_PATH.equals(pathFragment) && OsgiName.OSGI_SCHEME.equals(schemeName)) || (OsgiName.SERVICES_PATH.equals(pathFragment) && OsgiName.ARIES_SCHEME.equals(schemeName))) { - result = ServiceHelper.getService(validName.getInterface(), validName.getFilter(), serviceName, true, env); + result = ServiceHelper.getService(validName.getInterface(), validName.getFilter(), serviceName, null, true, env); } else if (OsgiName.SERVICE_LIST_PATH.equals(pathFragment)) { result = new ServiceRegistryListContext(env, validName); } else { Modified: incubator/aries/trunk/jndi/jndi-url/src/main/java/org/apache/aries/jndi/url/ServiceRegistryListContext.java URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jndi/jndi-url/src/main/java/org/apache/aries/jndi/url/ServiceRegistryListContext.java?rev=919842&r1=919841&r2=919842&view=diff ============================================================================== --- incubator/aries/trunk/jndi/jndi-url/src/main/java/org/apache/aries/jndi/url/ServiceRegistryListContext.java (original) +++ incubator/aries/trunk/jndi/jndi-url/src/main/java/org/apache/aries/jndi/url/ServiceRegistryListContext.java Sat Mar 6 20:20:24 2010 @@ -21,25 +21,91 @@ import java.util.HashMap; import java.util.Hashtable; import java.util.Map; +import java.util.NoSuchElementException; import javax.naming.Binding; import javax.naming.Context; import javax.naming.Name; import javax.naming.NameClassPair; +import javax.naming.NameNotFoundException; import javax.naming.NameParser; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.OperationNotSupportedException; +import org.apache.aries.jndi.services.ServiceHelper; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; + public class ServiceRegistryListContext implements Context { + private static final String ARIES_SERVICES = "aries:services/"; private Map env; /** The name parser for the service registry name space */ private NameParser parser = new OsgiNameParser(); + /** The osgi lookup name **/ + private OsgiName parentName; + + private interface ThingManager + { + public T get(BundleContext ctx, ServiceReference ref); + public void release(BundleContext ctx, ServiceReference ref); + } + + private class ServiceNamingEnumeration implements NamingEnumeration + { + private BundleContext ctx; + private ServiceReference[] refs; + private int position = 0; + private ThingManager mgr; + private T last; + + private ServiceNamingEnumeration(BundleContext context, ServiceReference[] theRefs, ThingManager manager) + { + ctx = context; + refs = theRefs; + mgr = manager; + } + + public void close() throws NamingException + { + mgr.release(ctx, refs[position - 1]); + last = null; + } + + public boolean hasMore() throws NamingException + { + return hasMore(); + } + + public T next() throws NamingException + { + return nextElement(); + } + + public boolean hasMoreElements() + { + return position < refs.length; + } + + public T nextElement() + { + if (!!!hasMoreElements()) throw new NoSuchElementException(); + + if (position > 0) mgr.release(ctx, refs[position - 1]); + + last = mgr.get(ctx, refs[position++]); + + return last; + } + + } public ServiceRegistryListContext(Map env, OsgiName validName) { this.env = new HashMap(env); + parentName = validName; } public Object addToEnvironment(String propName, Object propVal) throws NamingException @@ -65,16 +131,32 @@ public Name composeName(Name name, Name prefix) throws NamingException { - // TODO Auto-generated method stub - return null; + String result = prefix + "/" + name; + + String ns = ARIES_SERVICES; + + if (result.startsWith(ns)) { + ns = ""; + } + + return parser.parse(ns + result); } public String composeName(String name, String prefix) throws NamingException { - // TODO Auto-generated method stub - return null; - } + String result = prefix + "/" + name; + String ns = ARIES_SERVICES; + + if (result.startsWith(ns)) { + ns = ""; + } + + parser.parse(ns + result); + + return result; + } + public Context createSubcontext(Name name) throws NamingException { throw new OperationNotSupportedException(); @@ -104,8 +186,7 @@ public String getNameInNamespace() throws NamingException { - // TODO Auto-generated method stub - return null; + throw new OperationNotSupportedException(); } public NameParser getNameParser(Name name) throws NamingException @@ -125,8 +206,30 @@ public NamingEnumeration list(String name) throws NamingException { - // TODO Auto-generated method stub - return null; + if (!!!"".equals(name)) throw new NameNotFoundException(name); + + final BundleContext ctx = ServiceHelper.getBundleContext(env); + final ServiceReference[] refs = ServiceHelper.getServiceReferences(parentName.getInterface(), parentName.getFilter(), parentName.getServiceName(), env); + + return new ServiceNamingEnumeration(ctx, refs, new ThingManager() { + public NameClassPair get(BundleContext ctx, ServiceReference ref) + { + String serviceId = String.valueOf(ref.getProperty(Constants.SERVICE_ID)); + String className = null; + Object service = ctx.getService(ref); + if (service != null) { + className = service.getClass().getName(); + } + + ctx.ungetService(ref); + + return new NameClassPair(serviceId, className, true); + } + + public void release(BundleContext ctx, ServiceReference ref) + { + } + }); } public NamingEnumeration listBindings(Name name) throws NamingException @@ -136,8 +239,26 @@ public NamingEnumeration listBindings(String name) throws NamingException { - // TODO Auto-generated method stub - return null; + if (!!!"".equals(name)) throw new NameNotFoundException(name); + + final BundleContext ctx = ServiceHelper.getBundleContext(env); + final ServiceReference[] refs = ServiceHelper.getServiceReferences(parentName.getInterface(), parentName.getFilter(), parentName.getServiceName(), env); + + return new ServiceNamingEnumeration(ctx, refs, new ThingManager() { + public Binding get(BundleContext ctx, ServiceReference ref) + { + String serviceId = String.valueOf(ref.getProperty(Constants.SERVICE_ID)); + + Object service = ServiceHelper.getService(ctx, ref); + + return new Binding(serviceId, service, true); + } + + public void release(BundleContext ctx, ServiceReference ref) + { + ctx.ungetService(ref); + } + }); } public Object lookup(Name name) throws NamingException @@ -147,7 +268,15 @@ public Object lookup(String name) throws NamingException { - return null; + Object result = null; + + result = ServiceHelper.getService(parentName.getInterface(), parentName.getFilter(), parentName.getServiceName(), name, false, env); + + if (result == null) { + throw new NameNotFoundException(name.toString()); + } + + return result; } public Object lookupLink(Name name) throws NamingException Modified: incubator/aries/trunk/jndi/jndi-url/src/test/java/org/apache/aries/jndi/url/ServiceRegistryContextTest.java URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jndi/jndi-url/src/test/java/org/apache/aries/jndi/url/ServiceRegistryContextTest.java?rev=919842&r1=919841&r2=919842&view=diff ============================================================================== --- incubator/aries/trunk/jndi/jndi-url/src/test/java/org/apache/aries/jndi/url/ServiceRegistryContextTest.java (original) +++ incubator/aries/trunk/jndi/jndi-url/src/test/java/org/apache/aries/jndi/url/ServiceRegistryContextTest.java Sat Mar 6 20:20:24 2010 @@ -22,6 +22,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.lang.reflect.Field; import java.util.Hashtable; @@ -48,6 +49,7 @@ import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; +import org.osgi.framework.ServiceException; import org.osgi.framework.ServiceFactory; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; @@ -314,8 +316,168 @@ Skeleton.getSkeleton(t).assertCalledExactNumberOfTimes(new MethodCall(Runnable.class, "run"), 1); Skeleton.getSkeleton(t2).assertCalledExactNumberOfTimes(new MethodCall(Runnable.class, "run"), 1); - } + } + + @Test + public void checkServiceListLookup() throws NamingException + { + BundleMock mock = new BundleMock("scooby.doo", new Properties()); + + Thread.currentThread().setContextClassLoader(mock.getClassLoader()); + + InitialContext ctx = new InitialContext(); + + String className = Runnable.class.getName(); + + Runnable t = Skeleton.newMock(Runnable.class); + + // we don't want the default service + reg.unregister(); + + ServiceRegistration reg = bc.registerService(className, t, null); + ServiceRegistration reg2 = bc.registerService("java.lang.Thread", new Thread(), null); + + Context ctx2 = (Context) ctx.lookup("osgi:servicelist/java.lang.Runnable"); + + Runnable r = (Runnable) ctx2.lookup(String.valueOf(reg.getReference().getProperty(Constants.SERVICE_ID))); + r.run(); + + Skeleton.getSkeleton(t).assertCalled(new MethodCall(Runnable.class, "run")); + + reg.unregister(); + + try { + r.run(); + fail("Should have received a ServiceException"); + } catch (ServiceException e) { + assertEquals("service exception has the wrong type", ServiceException.UNREGISTERED, e.getType()); + } + + try { + ctx2.lookup(String.valueOf(reg2.getReference().getProperty(Constants.SERVICE_ID))); + fail("Expected a NameNotFoundException"); + } catch (NameNotFoundException e) { + } + } + + @Test + public void checkServiceListList() throws NamingException + { + BundleMock mock = new BundleMock("scooby.doo", new Properties()); + + Thread.currentThread().setContextClassLoader(mock.getClassLoader()); + + InitialContext ctx = new InitialContext(); + + String className = Runnable.class.getName(); + + Runnable t = Skeleton.newMock(Runnable.class); + + // we don't want the default service + reg.unregister(); + + ServiceRegistration reg = bc.registerService(className, t, null); + ServiceRegistration reg2 = bc.registerService(className, new Thread(), null); + + NamingEnumeration ne = ctx.list("osgi:servicelist/" + className); + + assertTrue(ne.hasMoreElements()); + + NameClassPair ncp = ne.nextElement(); + + assertEquals(String.valueOf(reg.getReference().getProperty(Constants.SERVICE_ID)), ncp.getName()); + assertTrue("Class name not correct. Was: " + ncp.getClassName(), ncp.getClassName().contains("Proxy")); + + assertTrue(ne.hasMoreElements()); + + ncp = ne.nextElement(); + + assertEquals(String.valueOf(reg2.getReference().getProperty(Constants.SERVICE_ID)), ncp.getName()); + assertEquals("Class name not correct.", Thread.class.getName(), ncp.getClassName()); + + assertFalse(ne.hasMoreElements()); + } + + @Test + public void checkServiceListListBindings() throws NamingException + { + BundleMock mock = new BundleMock("scooby.doo", new Properties()); + + Thread.currentThread().setContextClassLoader(mock.getClassLoader()); + + InitialContext ctx = new InitialContext(); + + String className = Runnable.class.getName(); + + MethodCall run = new MethodCall(Runnable.class, "run"); + + Runnable t = Skeleton.newMock(Runnable.class); + Runnable t2 = Skeleton.newMock(Runnable.class); + + // we don't want the default service + reg.unregister(); + + ServiceRegistration reg = bc.registerService(className, t, null); + ServiceRegistration reg2 = bc.registerService(className, t2, null); + + NamingEnumeration ne = ctx.listBindings("osgi:servicelist/" + className); + + assertTrue(ne.hasMoreElements()); + + Binding bnd = ne.nextElement(); + + assertEquals(String.valueOf(reg.getReference().getProperty(Constants.SERVICE_ID)), bnd.getName()); + assertTrue("Class name not correct. Was: " + bnd.getClassName(), bnd.getClassName().contains("Proxy")); + + Runnable r = (Runnable) bnd.getObject(); + + assertNotNull(r); + + r.run(); + + Skeleton.getSkeleton(t).assertCalledExactNumberOfTimes(run, 1); + Skeleton.getSkeleton(t2).assertNotCalled(run); + + assertTrue(ne.hasMoreElements()); + + bnd = ne.nextElement(); + + assertEquals(String.valueOf(reg2.getReference().getProperty(Constants.SERVICE_ID)), bnd.getName()); + assertTrue("Class name not correct. Was: " + bnd.getClassName(), bnd.getClassName().contains("Proxy")); + + r = (Runnable) bnd.getObject(); + + assertNotNull(r); + + r.run(); + + Skeleton.getSkeleton(t).assertCalledExactNumberOfTimes(run, 1); + Skeleton.getSkeleton(t2).assertCalledExactNumberOfTimes(run, 1); + + assertFalse(ne.hasMoreElements()); + } + + @Test(expected=ServiceException.class) + public void checkProxyWhenServiceGoes() throws ServiceException, NamingException + { + BundleMock mock = new BundleMock("scooby.doo", new Properties()); + + Thread.currentThread().setContextClassLoader(mock.getClassLoader()); + + InitialContext ctx = new InitialContext(); + + Runnable r = (Runnable) ctx.lookup("osgi:service/java.lang.Runnable"); + + r.run(); + + Skeleton.getSkeleton(service).assertCalled(new MethodCall(Runnable.class, "run")); + + reg.unregister(); + + r.run(); + } + @Test public void checkServiceOrderObserved() throws NamingException {