http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/provider/tcp/pom.xml ---------------------------------------------------------------------- diff --git a/provider/tcp/pom.xml b/provider/tcp/pom.xml new file mode 100644 index 0000000..2edfb3b --- /dev/null +++ b/provider/tcp/pom.xml @@ -0,0 +1,28 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.aries.rsa</groupId> + <artifactId>parent</artifactId> + <version>1.8-SNAPSHOT</version> + <relativePath>../../parent/pom.xml</relativePath> + </parent> + + <groupId>org.apache.aries.rsa.provider</groupId> + <artifactId>tcp</artifactId> + <packaging>bundle</packaging> + <name>Aries Remote Service Admin provider TCP</name> + <description>Provider for Java Serialization over TCP</description> + + <properties> + <topDirectoryLocation>../..</topDirectoryLocation> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.cxf.dosgi</groupId> + <artifactId>cxf-dosgi-ri-provider-api</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + +</project>
http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/Activator.java ---------------------------------------------------------------------- diff --git a/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/Activator.java b/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/Activator.java new file mode 100644 index 0000000..128c3d8 --- /dev/null +++ b/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/Activator.java @@ -0,0 +1,45 @@ +/** + * 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.rsa.provider.tcp; + +import java.util.Dictionary; +import java.util.Hashtable; + +import org.apache.cxf.dosgi.dsw.api.DistributionProvider; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.service.remoteserviceadmin.RemoteConstants; + +public class Activator implements BundleActivator { + + @Override + public void start(BundleContext context) throws Exception { + DistributionProvider provider = new TCPProvider(); + Dictionary<String, Object> props = new Hashtable<>(); + props.put(RemoteConstants.REMOTE_INTENTS_SUPPORTED, new String[]{}); + props.put(RemoteConstants.REMOTE_CONFIGS_SUPPORTED, provider.getSupportedTypes()); + context.registerService(DistributionProvider.class, provider, props); + } + + @Override + public void stop(BundleContext context) throws Exception { + // unregister happens automatically + } + +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/LoaderObjectInputStream.java ---------------------------------------------------------------------- diff --git a/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/LoaderObjectInputStream.java b/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/LoaderObjectInputStream.java new file mode 100644 index 0000000..ac60950 --- /dev/null +++ b/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/LoaderObjectInputStream.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.aries.rsa.provider.tcp; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; + +public class LoaderObjectInputStream extends ObjectInputStream { + + private ClassLoader loader; + + public LoaderObjectInputStream(InputStream in, ClassLoader loader) throws IOException { + super(in); + this.loader = loader; + } + + @Override + protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + try { + return loader.loadClass(desc.getName()); + } catch (ClassNotFoundException e) { + return super.resolveClass(desc); + } + } +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/LocalHostUtil.java ---------------------------------------------------------------------- diff --git a/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/LocalHostUtil.java b/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/LocalHostUtil.java new file mode 100644 index 0000000..3f40bd8 --- /dev/null +++ b/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/LocalHostUtil.java @@ -0,0 +1,92 @@ +/** + * 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.rsa.provider.tcp; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +/** + * Utility methods to get the local address even on a linux host. + */ +public final class LocalHostUtil { + + private LocalHostUtil() { + // Util Class + } + + /** + * Returns an InetAddress representing the address of the localhost. Every + * attempt is made to find an address for this host that is not the loopback + * address. If no other address can be found, the loopback will be returned. + * + * @return InetAddress the address of localhost + * @throws UnknownHostException if there is a problem determining the address + */ + public static InetAddress getLocalHost() throws UnknownHostException { + InetAddress localHost = InetAddress.getLocalHost(); + if (!localHost.isLoopbackAddress()) { + return localHost; + } + InetAddress[] addrs = getAllLocalUsingNetworkInterface(); + for (InetAddress addr : addrs) { + if (!addr.isLoopbackAddress() && !addr.getHostAddress().contains(":")) { + return addr; + } + } + return localHost; + } + + /** + * Utility method that delegates to the methods of NetworkInterface to + * determine addresses for this machine. + * + * @return all addresses found from the NetworkInterfaces + * @throws UnknownHostException if there is a problem determining addresses + */ + private static InetAddress[] getAllLocalUsingNetworkInterface() throws UnknownHostException { + try { + List<InetAddress> addresses = new ArrayList<InetAddress>(); + Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); + while (e.hasMoreElements()) { + NetworkInterface ni = e.nextElement(); + for (Enumeration<InetAddress> e2 = ni.getInetAddresses(); e2.hasMoreElements();) { + addresses.add(e2.nextElement()); + } + } + return addresses.toArray(new InetAddress[] {}); + } catch (SocketException ex) { + throw new UnknownHostException("127.0.0.1"); + } + } + + public static String getLocalIp() { + String localIP; + try { + localIP = getLocalHost().getHostAddress(); + } catch (Exception e) { + localIP = "localhost"; + } + return localIP; + } +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TCPProvider.java ---------------------------------------------------------------------- diff --git a/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TCPProvider.java b/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TCPProvider.java new file mode 100644 index 0000000..07c5a05 --- /dev/null +++ b/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TCPProvider.java @@ -0,0 +1,67 @@ +/** + * 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.rsa.provider.tcp; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.net.URI; +import java.util.Map; + +import org.apache.cxf.dosgi.dsw.api.DistributionProvider; +import org.apache.cxf.dosgi.dsw.api.Endpoint; +import org.apache.cxf.dosgi.dsw.api.IntentUnsatisfiedException; +import org.osgi.framework.BundleContext; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.RemoteConstants; + +@SuppressWarnings("rawtypes") +public class TCPProvider implements DistributionProvider { + + private static final String TCP_CONFIG_TYPE = "aries.tcp"; + + @Override + public String[] getSupportedTypes() { + return new String[] {TCP_CONFIG_TYPE}; + } + + @Override + public Endpoint exportService(Object serviceO, + BundleContext serviceContext, + Map<String, Object> effectiveProperties, + Class[] exportedInterfaces) { + effectiveProperties.put(RemoteConstants.SERVICE_IMPORTED_CONFIGS, getSupportedTypes()); + return new TcpEndpoint(serviceO, effectiveProperties); + } + + @Override + public Object importEndpoint(ClassLoader cl, + BundleContext consumerContext, + Class[] interfaces, + EndpointDescription endpoint) + throws IntentUnsatisfiedException { + try { + URI address = new URI(endpoint.getId()); + InvocationHandler handler = new TcpInvocationHandler(cl, address.getHost(), address.getPort()); + return Proxy.newProxyInstance(cl, interfaces, handler); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TCPServer.java ---------------------------------------------------------------------- diff --git a/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TCPServer.java b/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TCPServer.java new file mode 100644 index 0000000..e70731d --- /dev/null +++ b/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TCPServer.java @@ -0,0 +1,118 @@ +/** + * 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.rsa.provider.tcp; + +import java.io.Closeable; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TCPServer implements Closeable, Runnable { + private Logger log = LoggerFactory.getLogger(TCPServer.class); + private ServerSocket serverSocket; + private Object service; + private boolean running; + private ExecutorService executor; + + public TCPServer(Object service, String localip, Integer port, int numThreads) { + this.service = service; + try { + this.serverSocket = new ServerSocket(port); + } catch (IOException e) { + throw new RuntimeException(e); + } + this.running = true; + this.executor = Executors.newCachedThreadPool(); + for (int c = 0; c < numThreads; c++) { + this.executor.execute(this); + } + } + + int getPort() { + return this.serverSocket.getLocalPort(); + } + + public void run() { + ClassLoader serviceCL = service.getClass().getClassLoader(); + while (running) { + try ( + Socket socket = this.serverSocket.accept(); + ObjectInputStream ois = new LoaderObjectInputStream(socket.getInputStream(), serviceCL); + ObjectOutputStream objectOutput = new ObjectOutputStream(socket.getOutputStream()) + ) { + String methodName = (String)ois.readObject(); + Object[] args = (Object[])ois.readObject(); + Object result = invoke(methodName, args); + objectOutput.writeObject(result); + } catch (SocketException e) { + running = false; + } catch (Exception e) { + log.warn("Error processing service call.", e); + } + } + } + + private Object invoke(String methodName, Object[] args) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + Class<?>[] parameterTypesAr = getTypes(args); + Method method = service.getClass().getMethod(methodName, parameterTypesAr); + try { + return method.invoke(service, args); + } catch (Throwable e) { + return e; + } + } + + private Class<?>[] getTypes(Object[] args) { + List<Class<?>> parameterTypes = new ArrayList<>(); + if (args != null) { + for (Object arg : args) { + parameterTypes.add(arg.getClass()); + } + } + Class<?>[] parameterTypesAr = parameterTypes.toArray(new Class[]{}); + return parameterTypesAr; + } + + @Override + public void close() throws IOException { + this.serverSocket.close(); + this.running = false; + this.executor.shutdown(); + try { + this.executor.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + } + this.executor.shutdownNow(); + } + +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TcpEndpoint.java ---------------------------------------------------------------------- diff --git a/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TcpEndpoint.java b/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TcpEndpoint.java new file mode 100644 index 0000000..5bc9d7a --- /dev/null +++ b/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TcpEndpoint.java @@ -0,0 +1,58 @@ +/** + * 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.rsa.provider.tcp; + +import java.io.IOException; +import java.util.Map; + +import org.apache.cxf.dosgi.dsw.api.Endpoint; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.RemoteConstants; + +public class TcpEndpoint implements Endpoint { + private EndpointDescription epd; + private TCPServer tcpServer; + + public TcpEndpoint(Object service, Map<String, Object> effectiveProperties) { + Integer port = getInt(effectiveProperties, "port", 0); + String localip = LocalHostUtil.getLocalIp(); + int numThreads = getInt(effectiveProperties, "numThreads", 10); + tcpServer = new TCPServer(service, localip, port, numThreads); + effectiveProperties.put(RemoteConstants.ENDPOINT_ID, "tcp://" + localip + ":" + tcpServer.getPort()); + effectiveProperties.put(RemoteConstants.SERVICE_EXPORTED_CONFIGS, ""); + this.epd = new EndpointDescription(effectiveProperties); + } + + + private Integer getInt(Map<String, Object> effectiveProperties, String key, int defaultValue) { + String value = (String)effectiveProperties.get(key); + return value != null ? Integer.parseInt(value) : defaultValue; + } + + @Override + public EndpointDescription description() { + return this.epd; + } + + + @Override + public void close() throws IOException { + tcpServer.close(); + } +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TcpInvocationHandler.java ---------------------------------------------------------------------- diff --git a/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TcpInvocationHandler.java b/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TcpInvocationHandler.java new file mode 100644 index 0000000..cdbf8ec --- /dev/null +++ b/provider/tcp/src/main/java/org/apache/aries/rsa/provider/tcp/TcpInvocationHandler.java @@ -0,0 +1,68 @@ +/** + * 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.rsa.provider.tcp; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.net.Socket; +import java.net.UnknownHostException; + +public class TcpInvocationHandler implements InvocationHandler { + private String host; + private int port; + private ClassLoader cl; + + public TcpInvocationHandler(ClassLoader cl, String host, int port) + throws UnknownHostException, IOException { + this.cl = cl; + this.host = host; + this.port = port; + + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + try ( + Socket socket = new Socket(this.host, this.port); + ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()) + ) { + out.writeObject(method.getName()); + out.writeObject(args); + out.flush(); + return parseResult(socket); + } catch (Exception e) { + throw new RuntimeException("Error calling " + host + ":" + port + " method: " + method.getName(), e); + } + } + + private Object parseResult(Socket socket) throws IOException, ClassNotFoundException, Throwable { + try (ObjectInputStream in = new LoaderObjectInputStream(socket.getInputStream(), cl)) { + Object result = in.readObject(); + if (result instanceof Throwable) { + throw (Throwable)result; + } else { + return result; + } + } + } + +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/provider/tcp/src/test/java/org/apache/aries/rsa/provider/tcp/TcpProviderTest.java ---------------------------------------------------------------------- diff --git a/provider/tcp/src/test/java/org/apache/aries/rsa/provider/tcp/TcpProviderTest.java b/provider/tcp/src/test/java/org/apache/aries/rsa/provider/tcp/TcpProviderTest.java new file mode 100644 index 0000000..583c5e1 --- /dev/null +++ b/provider/tcp/src/test/java/org/apache/aries/rsa/provider/tcp/TcpProviderTest.java @@ -0,0 +1,107 @@ +/** + * 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.rsa.provider.tcp; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.apache.aries.rsa.provider.tcp.myservice.MyService; +import org.apache.aries.rsa.provider.tcp.myservice.MyServiceImpl; +import org.apache.cxf.dosgi.dsw.api.Endpoint; +import org.apache.cxf.dosgi.dsw.api.EndpointHelper; +import org.easymock.EasyMock; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.osgi.framework.BundleContext; + +public class TcpProviderTest { + + private static final int NUM_CALLS = 100; + private MyService myServiceProxy; + private Endpoint ep; + + @Before + public void createServerAndProxy() { + Class<?>[] exportedInterfaces = new Class[] {MyService.class}; + TCPProvider provider = new TCPProvider(); + Map<String, Object> props = new HashMap<String, Object>(); + EndpointHelper.addObjectClass(props, exportedInterfaces); + MyService myService = new MyServiceImpl(); + BundleContext bc = EasyMock.mock(BundleContext.class); + ep = provider.exportService(myService, bc, props, exportedInterfaces); + myServiceProxy = (MyService)provider.importEndpoint(MyService.class.getClassLoader(), + bc, + exportedInterfaces, + ep.description()); + } + + @Test + public void testPerf() throws IOException, InterruptedException { + runPerfTest(myServiceProxy); + String msg = "test"; + String result = myServiceProxy.echo(msg); + Assert.assertEquals(msg, result); + } + + @Test + public void testCall() throws IOException, InterruptedException { + myServiceProxy.call("test"); + } + + @Test + public void testCallOneway() throws IOException, InterruptedException { + myServiceProxy.callOneWay("test"); + } + + @After + public void close() throws IOException { + ep.close(); + } + + private void runPerfTest(final MyService myServiceProxy2) throws InterruptedException { + StringBuilder msg = new StringBuilder(); + for (int c = 0; c < 1000; c++) { + msg.append("testing123"); + } + final String msg2 = msg.toString(); + ExecutorService executor = Executors.newFixedThreadPool(10); + Runnable task = new Runnable() { + + @Override + public void run() { + String result = myServiceProxy2.echo(msg2); + Assert.assertEquals(msg2, result); + } + }; + long start = System.currentTimeMillis(); + for (int c = 0; c < NUM_CALLS; c++) { + executor.execute(task); + } + executor.shutdown(); + executor.awaitTermination(100, TimeUnit.SECONDS); + long tps = NUM_CALLS * 1000 / (System.currentTimeMillis() - start); + System.out.println(tps + " tps"); + } +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/provider/tcp/src/test/java/org/apache/aries/rsa/provider/tcp/myservice/MyService.java ---------------------------------------------------------------------- diff --git a/provider/tcp/src/test/java/org/apache/aries/rsa/provider/tcp/myservice/MyService.java b/provider/tcp/src/test/java/org/apache/aries/rsa/provider/tcp/myservice/MyService.java new file mode 100644 index 0000000..e9d56bf --- /dev/null +++ b/provider/tcp/src/test/java/org/apache/aries/rsa/provider/tcp/myservice/MyService.java @@ -0,0 +1,30 @@ +/** + * 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.rsa.provider.tcp.myservice; + +import javax.jws.Oneway; + +public interface MyService { + String echo(String msg); + void call(String msg); + + // Oneway not yet supported + @Oneway + void callOneWay(String msg); +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/provider/tcp/src/test/java/org/apache/aries/rsa/provider/tcp/myservice/MyServiceImpl.java ---------------------------------------------------------------------- diff --git a/provider/tcp/src/test/java/org/apache/aries/rsa/provider/tcp/myservice/MyServiceImpl.java b/provider/tcp/src/test/java/org/apache/aries/rsa/provider/tcp/myservice/MyServiceImpl.java new file mode 100644 index 0000000..5f469ed --- /dev/null +++ b/provider/tcp/src/test/java/org/apache/aries/rsa/provider/tcp/myservice/MyServiceImpl.java @@ -0,0 +1,36 @@ +/** + * 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.rsa.provider.tcp.myservice; + +public class MyServiceImpl implements MyService { + + @Override + public String echo(String msg) { + return msg; + } + + @Override + public void call(String msg) { + } + + @Override + public void callOneWay(String msg) { + } + +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/repository/pom.xml ---------------------------------------------------------------------- diff --git a/repository/pom.xml b/repository/pom.xml new file mode 100644 index 0000000..06c85aa --- /dev/null +++ b/repository/pom.xml @@ -0,0 +1,83 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.aries.rsa</groupId> + <artifactId>parent</artifactId> + <version>1.8-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> + </parent> + <artifactId>repository</artifactId> + <packaging>pom</packaging> + <properties> + <local.index.policy>ALLOWED</local.index.policy> + </properties> + <profiles> + <profile> + <id>RunningInCI</id> + <activation> + <property> + <name>running.in.ci</name> + <value>true</value> + </property> + </activation> + <properties> + <local.url.policy>FORBIDDEN</local.url.policy> + </properties> + </profile> + <profile> + <id>apache-release</id> + <properties> + <local.url.policy>FORBIDDEN</local.url.policy> + </properties> + </profile> + </profiles> + <build> + <plugins> + <plugin> + <groupId>biz.aQute.bnd</groupId> + <artifactId>bnd-indexer-maven-plugin</artifactId> + <version>${bnd.version}</version> + <configuration> + <localURLs>${local.index.policy}</localURLs> + <includeTransitive>true</includeTransitive> + </configuration> + <executions> + <execution> + <id>index</id> + <goals> + <goal>index</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + <dependencies> + <dependency> + <groupId>org.apache.aries.rsa</groupId> + <artifactId>rsa</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.aries.rsa.provider</groupId> + <artifactId>tcp</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.aries.rsa.discovery</groupId> + <artifactId>zookeeper</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.aries.rsa.discovery</groupId> + <artifactId>zookeeper-server</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.aries.rsa.discovery</groupId> + <artifactId>zookeeper-server-config</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> +</project> http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa-api/bnd.bnd ---------------------------------------------------------------------- diff --git a/rsa-api/bnd.bnd b/rsa-api/bnd.bnd new file mode 100644 index 0000000..d756067 --- /dev/null +++ b/rsa-api/bnd.bnd @@ -0,0 +1,6 @@ +Import-Package:\ + org.osgi.service.remoteserviceadmin,\ + org.osgi.service.event +Export-Package:\ + org.osgi.service.remoteserviceadmin,\ + org.osgi.service.event http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa-api/pom.xml ---------------------------------------------------------------------- diff --git a/rsa-api/pom.xml b/rsa-api/pom.xml new file mode 100644 index 0000000..e0dd296 --- /dev/null +++ b/rsa-api/pom.xml @@ -0,0 +1,40 @@ +<?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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.aries.rsa</groupId> + <artifactId>parent</artifactId> + <version>1.8-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> + </parent> + + <artifactId>rsa-api</artifactId> + <packaging>bundle</packaging> + <name>Aries Remote Service Admin Compendium APIs</name> + + <description> + The minimal set of OSGi Compendium APIs required by DOSGi. + This bundle should be deployed instead of the full OSGi Compendium bundle to prevent issues + caused by multiple exports of other OSGi APIs that are not used by DOSGi. See DOSGI-208. + </description> + +</project> http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/bnd.bnd ---------------------------------------------------------------------- diff --git a/rsa/bnd.bnd b/rsa/bnd.bnd new file mode 100644 index 0000000..d32545d --- /dev/null +++ b/rsa/bnd.bnd @@ -0,0 +1,5 @@ +Bundle-Activator: org.apache.cxf.dosgi.dsw.service.Activator +Export-Package: \ + org.apache.cxf.dosgi.dsw.api,\ + org.apache.cxf.dosgi.dsw.service,\ + org.osgi.service.remoteserviceadmin http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/pom.xml ---------------------------------------------------------------------- diff --git a/rsa/pom.xml b/rsa/pom.xml new file mode 100644 index 0000000..b220283 --- /dev/null +++ b/rsa/pom.xml @@ -0,0 +1,27 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.aries.rsa</groupId> + <artifactId>parent</artifactId> + <version>1.8-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> + </parent> + <artifactId>core</artifactId> + <packaging>bundle</packaging> + <name>Aries Remote Service Admin Core</name> + <description>The Remote Service Admin as described in the OSGi Remote Service Admin specification</description> + + <properties> + <topDirectoryLocation>..</topDirectoryLocation> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.aries.rsa</groupId> + <artifactId>spi</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/Activator.java ---------------------------------------------------------------------- diff --git a/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/Activator.java b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/Activator.java new file mode 100644 index 0000000..4c4d7ad --- /dev/null +++ b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/Activator.java @@ -0,0 +1,37 @@ +/** + * 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.cxf.dosgi.dsw.service; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +public class Activator implements BundleActivator { + + private DistributionProviderTracker tracker; + + public void start(BundleContext bundlecontext) throws Exception { + tracker = new DistributionProviderTracker(bundlecontext); + tracker.open(); + } + + public void stop(BundleContext context) throws Exception { + tracker.close(); + } + +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ClientServiceFactory.java ---------------------------------------------------------------------- diff --git a/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ClientServiceFactory.java b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ClientServiceFactory.java new file mode 100644 index 0000000..7c292db --- /dev/null +++ b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ClientServiceFactory.java @@ -0,0 +1,110 @@ +/** + * 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.cxf.dosgi.dsw.service; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.cxf.dosgi.dsw.api.DistributionProvider; +import org.apache.cxf.dosgi.dsw.api.IntentUnsatisfiedException; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceFactory; +import org.osgi.framework.ServiceRegistration; +import org.osgi.framework.wiring.BundleWiring; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@SuppressWarnings("rawtypes") +public class ClientServiceFactory implements ServiceFactory { + + private static final Logger LOG = LoggerFactory.getLogger(ClientServiceFactory.class); + + private EndpointDescription endpoint; + private DistributionProvider handler; + private ImportRegistrationImpl importRegistration; + + private boolean closeable; + private int serviceCounter; + + public ClientServiceFactory(EndpointDescription endpoint, + DistributionProvider handler, ImportRegistrationImpl ir) { + this.endpoint = endpoint; + this.handler = handler; + this.importRegistration = ir; + } + + public Object getService(final Bundle requestingBundle, final ServiceRegistration sreg) { + List<String> interfaceNames = endpoint.getInterfaces(); + final BundleContext consumerContext = requestingBundle.getBundleContext(); + final ClassLoader consumerLoader = requestingBundle.adapt(BundleWiring.class).getClassLoader(); + try { + LOG.debug("getService() from serviceFactory for {}", interfaceNames); + final List<Class<?>> interfaces = new ArrayList<Class<?>>(); + for (String ifaceName : interfaceNames) { + interfaces.add(consumerLoader.loadClass(ifaceName)); + } + Object proxy = AccessController.doPrivileged(new PrivilegedAction<Object>() { + public Object run() { + Class<?>[] ifAr = interfaces.toArray(new Class[]{}); + return handler.importEndpoint(consumerLoader, consumerContext, ifAr, endpoint); + } + }); + + synchronized (this) { + serviceCounter++; + } + return proxy; + } catch (IntentUnsatisfiedException iue) { + LOG.info("Did not create proxy for {} because intent {} could not be satisfied", + interfaceNames, iue.getIntent()); + } catch (Exception e) { + LOG.warn("Problem creating a remote proxy for {}", interfaceNames, e); + } + return null; + } + + public void ungetService(Bundle requestingBundle, ServiceRegistration sreg, Object serviceObject) { + String[] interfaces = (String[])sreg.getReference().getProperty(org.osgi.framework.Constants.OBJECTCLASS); + LOG.info("Releasing a client object, interfaces: {}", Arrays.toString(interfaces)); + + synchronized (this) { + serviceCounter--; + LOG.debug("Services still provided by this ServiceFactory: {}", serviceCounter); + closeIfUnused(); + } + } + + public void setCloseable(boolean closeable) { + synchronized (this) { + this.closeable = closeable; + closeIfUnused(); + } + } + + private synchronized void closeIfUnused() { + if (serviceCounter <= 0 && closeable) { + importRegistration.closeAll(); + } + } +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/DistributionProviderTracker.java ---------------------------------------------------------------------- diff --git a/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/DistributionProviderTracker.java b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/DistributionProviderTracker.java new file mode 100644 index 0000000..675fcc6 --- /dev/null +++ b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/DistributionProviderTracker.java @@ -0,0 +1,73 @@ +/** + * 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.cxf.dosgi.dsw.service; + +import java.util.Dictionary; +import java.util.Hashtable; + +import org.apache.cxf.dosgi.dsw.api.DistributionProvider; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.remoteserviceadmin.RemoteServiceAdmin; +import org.osgi.util.tracker.ServiceTracker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@SuppressWarnings("rawtypes") +public class DistributionProviderTracker extends ServiceTracker<DistributionProvider, ServiceRegistration> { + private static final Logger LOG = LoggerFactory.getLogger(Activator.class); + + public DistributionProviderTracker(BundleContext context) { + super(context, DistributionProvider.class, null); + } + + @Override + public ServiceRegistration addingService(ServiceReference<DistributionProvider> reference) { + LOG.debug("RemoteServiceAdmin Implementation is starting up"); + DistributionProvider provider = context.getService(reference); + BundleContext apiContext = getAPIContext(); + RemoteServiceAdminCore rsaCore = new RemoteServiceAdminCore(context, + apiContext, + provider); + RemoteServiceadminFactory rsaf = new RemoteServiceadminFactory(rsaCore); + Dictionary<String, Object> props = new Hashtable<String, Object>(); + props.put("remote.intents.supported", reference.getProperty("remote.intents.supported")); + props.put("remote.configs.supported", reference.getProperty("remote.configs.supported")); + LOG.info("Registering RemoteServiceAdmin for provider " + provider.getClass().getName()); + return context.registerService(RemoteServiceAdmin.class.getName(), rsaf, props); + } + + protected BundleContext getAPIContext() { + Bundle apiBundle = FrameworkUtil.getBundle(DistributionProvider.class); + BundleContext apiContext = apiBundle.getBundleContext(); + return apiContext; + } + + @Override + public void removedService(ServiceReference<DistributionProvider> reference, + ServiceRegistration reg) { + LOG.debug("RemoteServiceAdmin Implementation is shutting down now"); + reg.unregister(); + super.removedService(reference, reg); + } + +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/EventAdminHelper.java ---------------------------------------------------------------------- diff --git a/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/EventAdminHelper.java b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/EventAdminHelper.java new file mode 100644 index 0000000..4868efa --- /dev/null +++ b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/EventAdminHelper.java @@ -0,0 +1,151 @@ +/** + * 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.cxf.dosgi.dsw.service; + +import java.util.HashMap; +import java.util.Map; + +import org.osgi.framework.BundleContext; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.Version; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventAdmin; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.RemoteServiceAdminEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EventAdminHelper { + + private static final Logger LOG = LoggerFactory.getLogger(EventAdminHelper.class); + + private BundleContext bctx; + + public EventAdminHelper(BundleContext bc) { + bctx = bc; + } + + private Event createEvent(Map<String, Object> props, String type) { + String topic = "org/osgi/service/remoteserviceadmin/" + type; + props.put("bundle", bctx.getBundle()); + props.put("bundle.id", bctx.getBundle().getBundleId()); + props.put("bundle.symbolicname", bctx.getBundle().getSymbolicName()); + + String version = (String)bctx.getBundle().getHeaders().get("Bundle-Version"); + Version v = version != null ? new Version(version) : Version.emptyVersion; + setIfNotNull(props, "bundle.version", v); + + return new Event(topic, props); + } + + public void notifyEventAdmin(RemoteServiceAdminEvent rsae) { + String topic = remoteServiceAdminEventTypeToString(rsae.getType()); + + Map<String, Object> props = new HashMap<String, Object>(); + setIfNotNull(props, "cause", rsae.getException()); + + EndpointDescription endpoint = null; + if (rsae.getImportReference() != null) { + endpoint = ((ImportRegistrationImpl)rsae.getImportReference()).getImportedEndpointAlways(); + setIfNotNull(props, "import.registration", endpoint); + } else if (rsae.getExportReference() != null) { + endpoint = rsae.getExportReference().getExportedEndpoint(); + setIfNotNull(props, "export.registration", endpoint); + } + + if (endpoint != null) { + setIfNotNull(props, "service.remote.id", endpoint.getServiceId()); + setIfNotNull(props, "service.remote.uuid", endpoint.getFrameworkUUID()); + setIfNotNull(props, "service.remote.uri", endpoint.getId()); + setIfNotNull(props, "objectClass", endpoint.getInterfaces().toArray()); + setIfNotNull(props, "service.imported.configs", endpoint.getConfigurationTypes()); + } + props.put("timestamp", System.currentTimeMillis()); + props.put("event", rsae); + + Event event = createEvent(props, topic); + notifyEventAdmins(topic, event); + } + + @SuppressWarnings({ + "rawtypes", "unchecked" + }) + private void notifyEventAdmins(String topic, Event event) { + ServiceReference[] refs = null; + try { + refs = bctx.getAllServiceReferences(EventAdmin.class.getName(), null); + } catch (InvalidSyntaxException e) { + LOG.error("Failed to get EventAdmin: " + e.getMessage(), e); + } + + if (refs != null) { + LOG.debug("Publishing event to {} EventAdmins; Topic:[{}]", refs.length, topic); + for (ServiceReference serviceReference : refs) { + EventAdmin eventAdmin = (EventAdmin) bctx.getService(serviceReference); + try { + eventAdmin.postEvent(event); + } finally { + if (eventAdmin != null) { + bctx.ungetService(serviceReference); + } + } + } + } + } + + private <K, V> void setIfNotNull(Map<K, V> map, K key, V val) { + if (val != null) { + map.put(key, val); + } + } + + private static String remoteServiceAdminEventTypeToString(int type) { + String retval; + switch (type) { + case RemoteServiceAdminEvent.EXPORT_ERROR: + retval = "EXPORT_ERROR"; + break; + case RemoteServiceAdminEvent.EXPORT_REGISTRATION: + retval = "EXPORT_REGISTRATION"; + break; + case RemoteServiceAdminEvent.EXPORT_UNREGISTRATION: + retval = "EXPORT_UNREGISTRATION"; + break; + case RemoteServiceAdminEvent.EXPORT_WARNING: + retval = "EXPORT_WARNING"; + break; + case RemoteServiceAdminEvent.IMPORT_ERROR: + retval = "IMPORT_ERROR"; + break; + case RemoteServiceAdminEvent.IMPORT_REGISTRATION: + retval = "IMPORT_REGISTRATION"; + break; + case RemoteServiceAdminEvent.IMPORT_UNREGISTRATION: + retval = "IMPORT_UNREGISTRATION"; + break; + case RemoteServiceAdminEvent.IMPORT_WARNING: + retval = "IMPORT_WARNING"; + break; + default: + retval = "UNKNOWN_EVENT"; + } + return retval; + } +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/EventProducer.java ---------------------------------------------------------------------- diff --git a/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/EventProducer.java b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/EventProducer.java new file mode 100644 index 0000000..26a46ab --- /dev/null +++ b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/EventProducer.java @@ -0,0 +1,114 @@ +/** + * 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.cxf.dosgi.dsw.service; + +import java.util.List; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.service.remoteserviceadmin.ExportRegistration; +import org.osgi.service.remoteserviceadmin.ImportRegistration; +import org.osgi.service.remoteserviceadmin.RemoteServiceAdminEvent; +import org.osgi.service.remoteserviceadmin.RemoteServiceAdminListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EventProducer { + + private static final Logger LOG = LoggerFactory.getLogger(EventProducer.class); + private final BundleContext bctx; + private final EventAdminHelper eaHelper; + + public EventProducer(BundleContext bc) { + bctx = bc; + eaHelper = new EventAdminHelper(bctx); + } + + protected void publishNotification(List<ExportRegistration> erl) { + for (ExportRegistration exportRegistration : erl) { + publishNotification(exportRegistration); + } + } + + protected void publishNotification(ExportRegistration er) { + int type = er.getException() == null + ? RemoteServiceAdminEvent.EXPORT_REGISTRATION + : RemoteServiceAdminEvent.EXPORT_ERROR; + notify(type, null, er); + } + + protected void publishNotification(ImportRegistration ir) { + int type = ir.getException() == null + ? RemoteServiceAdminEvent.IMPORT_REGISTRATION + : RemoteServiceAdminEvent.IMPORT_ERROR; + notify(type, ir, null); + } + + public void notifyRemoval(ExportRegistration er) { + notify(RemoteServiceAdminEvent.EXPORT_UNREGISTRATION, null, er); + } + + public void notifyRemoval(ImportRegistration ir) { + notify(RemoteServiceAdminEvent.IMPORT_UNREGISTRATION, ir, null); + } + + // only one of ir or er must be set, and the other must be null + private void notify(int type, ImportRegistration ir, ExportRegistration er) { + try { + RemoteServiceAdminEvent event = ir != null + ? new RemoteServiceAdminEvent(type, bctx.getBundle(), ir.getImportReference(), ir.getException()) + : new RemoteServiceAdminEvent(type, bctx.getBundle(), er.getExportReference(), er.getException()); + notifyListeners(event); + eaHelper.notifyEventAdmin(event); + } catch (IllegalStateException ise) { + LOG.debug("can't send notifications since bundle context is no longer valid"); + } + } + + @SuppressWarnings({ + "rawtypes", "unchecked" + }) + private void notifyListeners(RemoteServiceAdminEvent rsae) { + try { + ServiceReference[] listenerRefs = bctx.getServiceReferences( + RemoteServiceAdminListener.class.getName(), null); + if (listenerRefs != null) { + for (ServiceReference sref : listenerRefs) { + RemoteServiceAdminListener rsal = (RemoteServiceAdminListener)bctx.getService(sref); + if (rsal != null) { + try { + Bundle bundle = sref.getBundle(); + if (bundle != null) { + LOG.debug("notify RemoteServiceAdminListener {} of bundle {}", + rsal, bundle.getSymbolicName()); + rsal.remoteAdminEvent(rsae); + } + } finally { + bctx.ungetService(sref); + } + } + } + } + } catch (InvalidSyntaxException e) { + LOG.error(e.getMessage(), e); + } + } +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ExportReferenceImpl.java ---------------------------------------------------------------------- diff --git a/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ExportReferenceImpl.java b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ExportReferenceImpl.java new file mode 100644 index 0000000..497aa9c --- /dev/null +++ b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ExportReferenceImpl.java @@ -0,0 +1,77 @@ +/** + * 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.cxf.dosgi.dsw.service; + +import org.osgi.framework.ServiceReference; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.ExportReference; + +@SuppressWarnings("rawtypes") +public class ExportReferenceImpl implements ExportReference { + + private ServiceReference serviceReference; + private EndpointDescription endpoint; + + public ExportReferenceImpl(ServiceReference serviceReference, EndpointDescription endpoint) { + this.serviceReference = serviceReference; + this.endpoint = endpoint; + } + + public ExportReferenceImpl(ExportReference exportReference) { + this(exportReference.getExportedService(), exportReference.getExportedEndpoint()); + } + + public EndpointDescription getExportedEndpoint() { + return endpoint; + } + + public ServiceReference getExportedService() { + return serviceReference; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (endpoint == null ? 0 : endpoint.hashCode()); + result = prime * result + (serviceReference == null ? 0 : serviceReference.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ExportReferenceImpl other = (ExportReferenceImpl) obj; + boolean ed = endpoint == null ? other.endpoint == null + : endpoint.equals(other.endpoint); + boolean sr = serviceReference == null ? other.serviceReference == null + : serviceReference.equals(other.serviceReference); + return ed && sr; + } + + synchronized void close() { + this.endpoint = null; + this.serviceReference = null; + } +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ExportRegistrationImpl.java ---------------------------------------------------------------------- diff --git a/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ExportRegistrationImpl.java b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ExportRegistrationImpl.java new file mode 100644 index 0000000..d80bd40 --- /dev/null +++ b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ExportRegistrationImpl.java @@ -0,0 +1,152 @@ +/** + * 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.cxf.dosgi.dsw.service; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; + +import org.apache.cxf.dosgi.dsw.api.Endpoint; +import org.osgi.framework.ServiceReference; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.ExportReference; +import org.osgi.service.remoteserviceadmin.ExportRegistration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@SuppressWarnings("rawtypes") +public class ExportRegistrationImpl implements ExportRegistration { + + private static final Logger LOG = LoggerFactory.getLogger(ExportRegistrationImpl.class); + + private final RemoteServiceAdminCore rsaCore; + private final ExportReferenceImpl exportReference; + private final Closeable server; + private final Throwable exception; + + private final ExportRegistrationImpl parent; + private int instanceCount; + private volatile boolean closed; + + private ExportRegistrationImpl(ExportRegistrationImpl parent, RemoteServiceAdminCore rsaCore, + ExportReferenceImpl exportReference, Closeable server, Throwable exception) { + this.parent = parent != null ? parent.parent : this; // a parent points to itself + this.parent.addInstance(); + this.rsaCore = rsaCore; + this.exportReference = exportReference; + this.server = server; + this.exception = exception; + } + + // create a clone of the provided ExportRegistrationImpl that is linked to it + public ExportRegistrationImpl(ExportRegistrationImpl parent) { + this(parent, parent.rsaCore, new ExportReferenceImpl(parent.exportReference), + parent.server, parent.exception); + } + + // create a new (parent) instance which was exported successfully with the given server + public ExportRegistrationImpl(ServiceReference sref, Endpoint endpoint, RemoteServiceAdminCore rsaCore) { + this(null, rsaCore, new ExportReferenceImpl(sref, endpoint.description()), endpoint, null); + } + + // create a new (parent) instance which failed to be exported with the given exception + public ExportRegistrationImpl(RemoteServiceAdminCore rsaCore, Throwable exception) { + this(null, rsaCore, null, null, exception); + } + + private void ensureParent() { + if (parent != this) { + throw new IllegalStateException("this method may only be called on the parent"); + } + } + + public ExportReference getExportReference() { + if (exportReference == null) { + throw new IllegalStateException(getException()); + } + return closed ? null : exportReference; + } + + public Throwable getException() { + return closed ? null : exception; + } + + public final void close() { + synchronized (this) { + if (closed) { + return; + } + closed = true; + } + + rsaCore.removeExportRegistration(this); + exportReference.close(); + parent.removeInstance(); + } + + private void addInstance() { + ensureParent(); + synchronized (this) { + instanceCount++; + } + } + + private void removeInstance() { + ensureParent(); + synchronized (this) { + instanceCount--; + if (instanceCount <= 0) { + LOG.debug("really closing ExportRegistration now!"); + + if (server != null) { + try { + server.close(); + } catch (IOException e) { + LOG.warn("Error closing ExportRegistration", e); + } + } + } + } + } + + @Override + public String toString() { + if (closed) { + return "ExportRegistration closed"; + } + EndpointDescription endpoint = getExportReference().getExportedEndpoint(); + ServiceReference serviceReference = getExportReference().getExportedService(); + String r = "EndpointDescription for ServiceReference " + serviceReference; + + r += "\n*** EndpointDescription: ****\n"; + if (endpoint == null) { + r += "---> NULL <---- \n"; + } else { + Set<Map.Entry<String, Object>> props = endpoint.getProperties().entrySet(); + for (Map.Entry<String, Object> entry : props) { + Object value = entry.getValue(); + r += entry.getKey() + " => " + + (value instanceof Object[] ? Arrays.toString((Object[]) value) : value) + "\n"; + } + } + return r; + } +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ImportRegistrationImpl.java ---------------------------------------------------------------------- diff --git a/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ImportRegistrationImpl.java b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ImportRegistrationImpl.java new file mode 100644 index 0000000..2b896db --- /dev/null +++ b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/ImportRegistrationImpl.java @@ -0,0 +1,230 @@ +/** + * 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.cxf.dosgi.dsw.service; + +import java.util.ArrayList; +import java.util.List; + +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.ImportReference; +import org.osgi.service.remoteserviceadmin.ImportRegistration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@SuppressWarnings("rawtypes") +public class ImportRegistrationImpl implements ImportRegistration, ImportReference { + + private static final Logger LOG = LoggerFactory.getLogger(ImportRegistrationImpl.class); + + private volatile Throwable exception; + private volatile ServiceRegistration importedService; // used only in parent + private EndpointDescription endpoint; + private volatile ClientServiceFactory clientServiceFactory; + private RemoteServiceAdminCore rsaCore; + private boolean closed; + private boolean detached; // used only in parent + + private ImportRegistrationImpl parent; + private List<ImportRegistrationImpl> children; // used only in parent + + public ImportRegistrationImpl(Throwable ex) { + exception = ex; + initParent(); + } + + public ImportRegistrationImpl(EndpointDescription endpoint, RemoteServiceAdminCore rsac) { + this.endpoint = endpoint; + this.rsaCore = rsac; + initParent(); + } + + /** + * Creates a clone of the given parent instance. + */ + public ImportRegistrationImpl(ImportRegistrationImpl ir) { + // we always want a link to the parent... + parent = ir.getParent(); + exception = parent.getException(); + endpoint = parent.getImportedEndpointDescription(); + clientServiceFactory = parent.clientServiceFactory; + rsaCore = parent.rsaCore; + + parent.instanceAdded(this); + } + + private void initParent() { + parent = this; + children = new ArrayList<ImportRegistrationImpl>(1); + } + + private void ensureParent() { + if (parent != this) { + throw new IllegalStateException("this method may only be called on the parent"); + } + } + + /** + * Called on parent when a child is added. + * + * @param iri the child + */ + private synchronized void instanceAdded(ImportRegistrationImpl iri) { + ensureParent(); + children.add(iri); + } + + /** + * Called on parent when a child is closed. + * + * @param iri the child + */ + private void instanceClosed(ImportRegistrationImpl iri) { + ensureParent(); + synchronized (this) { + children.remove(iri); + if (!children.isEmpty() || detached || !closed) { + return; + } + detached = true; + } + + LOG.debug("really closing ImportRegistration now"); + + if (importedService != null) { + try { + importedService.unregister(); + } catch (IllegalStateException ise) { + LOG.debug("imported service is already unregistered"); + } + importedService = null; + } + if (clientServiceFactory != null) { + clientServiceFactory.setCloseable(true); + } + } + + public void close() { + LOG.debug("close() called"); + + synchronized (this) { + if (isInvalid()) { + return; + } + closed = true; + } + rsaCore.removeImportRegistration(this); + parent.instanceClosed(this); + } + + /** + * Closes all ImportRegistrations which share the same parent as this one. + */ + public void closeAll() { + if (this == parent) { + LOG.info("closing down all child ImportRegistrations"); + + // we must iterate over a copy of children since close() removes the child + // from the list (which would cause a ConcurrentModificationException) + for (ImportRegistrationImpl ir : copyChildren()) { + ir.close(); + } + this.close(); + } else { + parent.closeAll(); + } + } + + private List<ImportRegistrationImpl> copyChildren() { + synchronized (this) { + return new ArrayList<ImportRegistrationImpl>(children); + } + } + + public EndpointDescription getImportedEndpointDescription() { + return isInvalid() ? null : endpoint; + } + + @Override + public EndpointDescription getImportedEndpoint() { + return getImportedEndpointDescription(); + } + + @Override + public ServiceReference getImportedService() { + return isInvalid() || parent.importedService == null ? null : parent.importedService.getReference(); + } + + @Override + public ImportReference getImportReference() { + return this; + } + + @Override + public Throwable getException() { + return exception; + } + + public void setException(Throwable ex) { + exception = ex; + } + + private synchronized boolean isInvalid() { + return exception != null || closed; + } + + /** + * Sets the {@link ServiceRegistration} representing the locally + * registered {@link ClientServiceFactory} service which provides + * proxies to the remote imported service. It is set only on the parent. + * + * @param sreg the ServiceRegistration + */ + public void setImportedServiceRegistration(ServiceRegistration sreg) { + ensureParent(); + importedService = sreg; + } + + /** + * Sets the {@link ClientServiceFactory} which is the implementation + * of the locally registered service which provides proxies to the + * remote imported service. It is set only on the parent. + * + * @param csf the ClientServiceFactory + */ + public void setClientServiceFactory(ClientServiceFactory csf) { + ensureParent(); + clientServiceFactory = csf; + } + + public ImportRegistrationImpl getParent() { + return parent; + } + + /** + * Returns the imported endpoint even if this + * instance is closed or has an exception. + * + * @return the imported endpoint + */ + public EndpointDescription getImportedEndpointAlways() { + return endpoint; + } +} http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/PackageUtil.java ---------------------------------------------------------------------- diff --git a/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/PackageUtil.java b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/PackageUtil.java new file mode 100644 index 0000000..effcef1 --- /dev/null +++ b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/PackageUtil.java @@ -0,0 +1,85 @@ +/** + * 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.cxf.dosgi.dsw.service; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.packageadmin.ExportedPackage; +import org.osgi.service.packageadmin.PackageAdmin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@SuppressWarnings("deprecation") +public final class PackageUtil { + + public static final Logger LOG = LoggerFactory.getLogger(PackageUtil.class); + + private PackageUtil() { + } + + /** + * Tries to retrieve the version of iClass via the PackageAdmin. + * + * @param iClass tThe interface for which the version should be found + * @param bc any valid BundleContext + * @return the version of the interface or "0.0.0" if no version information could be found or an error + * occurred during the retrieval + */ + public static String getVersion(Class<?> iClass, BundleContext bc) { + ServiceReference<PackageAdmin> paRef = bc.getServiceReference(PackageAdmin.class); + if (paRef != null) { + PackageAdmin pa = bc.getService(paRef); + try { + Bundle b = pa.getBundle(iClass); + if (b == null) { + LOG.info("Unable to find interface version for interface " + iClass.getName() + + ". Falling back to 0.0.0"); + return "0.0.0"; + } + LOG.debug("Interface source bundle: {}", b.getSymbolicName()); + + ExportedPackage[] ep = pa.getExportedPackages(b); + LOG.debug("Exported Packages of the source bundle: {}", (Object)ep); + + String pack = iClass.getPackage().getName(); + LOG.debug("Looking for Package: {}", pack); + if (ep != null) { + for (ExportedPackage p : ep) { + if (p != null + && pack.equals(p.getName())) { + LOG.debug("found package -> Version: {}", p.getVersion()); + return p.getVersion().toString(); + } + } + } + } finally { + if (pa != null) { + bc.ungetService(paRef); + } + } + } else { + LOG.error("Was unable to obtain the package admin service -> can't resolve interface versions"); + } + + LOG.info("Unable to find interface version for interface " + iClass.getName() + + ". Falling back to 0.0.0"); + return "0.0.0"; + } +}
