git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@423854 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/pom.xml b/org.apache.felix.mosgi.jmx.rmiconnector/pom.xml
new file mode 100644
index 0000000..f1983d6
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/pom.xml
@@ -0,0 +1,75 @@
+<project>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix</artifactId>
+ <version>0.8.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>osgi-bundle</packaging>
+ <name>MOSGi JMX rmiconnector</name>
+ <artifactId>org.apache.felix.mosgi.jmx.rmiconnector</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>${pom.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>${pom.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.apache.felix.mosgi.jmx.agent</artifactId>
+ <version>${pom.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.apache.felix.mosgi.jmx.registry</artifactId>
+ <version>${pom.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <version>${pom.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix.plugins</groupId>
+ <artifactId>maven-osgi-plugin</artifactId>
+ <version>${pom.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <osgiManifest>
+ <bundleName>MOSGi JMX-MX4J RMI Connector</bundleName>
+ <bundleDescription>MOSGi JMX-MX4J RMI Connector</bundleDescription>
+ <bundleActivator>auto-detect</bundleActivator>
+ <bundleDocUrl>http://oscar-osgi.sf.net/obr2/${pom.artifactId}/</bundleDocUrl>
+ <bundleUrl>http://oscar-osgi.sf.net/obr2/${pom.artifactId}/${pom.artifactId}-${pom.version}.jar</bundleUrl>
+ <bundleSource>http://oscar-osgi.sf.net/obr2/${pom.artifactId}/${pom.artifactId}-${pom.version}-src.jar</bundleSource>
+ <bundleSymbolicName>${pom.artifactId}</bundleSymbolicName>
+ <exportPackage>
+ org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.provider.rmi;specification-version="1.0.0"
+ </exportPackage>
+ <importPackage>
+ org.osgi.service.log;specification-version="1.0.0",
+ org.osgi.framework;specification-version="1.0.0",
+ org.apache.felix.mosgi.jmx.registry.mx4j.tools.naming;specification-version="1.0.0",
+ org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.provider.rmi;specification-version="1.0.0",
+ javax.management;specification-version="1.0.0",
+ javax.management.remote;specification-version="1.0.0"
+ </importPackage>
+ </osgiManifest>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/RmiConnectorActivator.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/RmiConnectorActivator.java
new file mode 100644
index 0000000..694c109
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/RmiConnectorActivator.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceEvent;
+
+import org.osgi.service.log.LogService;
+
+import org.apache.felix.framework.cache.BundleCache;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+
+import org.apache.felix.mosgi.jmx.registry.mx4j.tools.naming.NamingServiceIfc;
+
+import java.net.InetAddress;
+
+public class RmiConnectorActivator implements BundleActivator, ServiceListener{
+ public static BundleContext bc;
+
+ private ObjectName connectorServerName=new ObjectName("RmiConnector:name=RMIConnector");
+
+ private JMXConnectorServer connectorServer;
+ private MBeanServer mbs;
+ private NamingServiceIfc nsi;
+ private ServiceReference mBeanServerSR, namingServiceIfcSR;
+
+ private String version=null;
+
+ public RmiConnectorActivator() throws javax.management.MalformedObjectNameException {}
+
+ ////////////////////////////////////////////////////
+ // BundleActivator //
+ ////////////////////////////////////////////////////
+ public void start(BundleContext bc) throws Exception{
+ this.version=(String)bc.getBundle().getHeaders().get(Constants.BUNDLE_VERSION);
+ RmiConnectorActivator.bc=bc;
+ this.mBeanServerSR=bc.getServiceReference(MBeanServer.class.getName());
+ this.namingServiceIfcSR=bc.getServiceReference(NamingServiceIfc.class.getName());
+ RmiConnectorActivator. bc.addServiceListener(this, "(|(objectClass="+NamingServiceIfc.class.getName()+")"+"(objectClass="+MBeanServer.class.getName()+"))");
+ if (this.mBeanServerSR!=null && this.namingServiceIfcSR!=null){
+ this.startRmiConnector();
+ } else {
+ if (this.mBeanServerSR == null){
+ RmiConnectorActivator.log(LogService.LOG_WARNING,"No JMX Agent found",null);
+ }else if (this.namingServiceIfcSR==null){
+ RmiConnectorActivator.log(LogService.LOG_WARNING,"No RMI Registry found", null);
+ }
+ }
+ }
+
+ public void stop(BundleContext bc) throws Exception {
+ this.stopRmiConnector();
+ RmiConnectorActivator.bc=null;
+ }
+
+ //////////////////////////////////////////////////////
+ // ServiceListener //
+ //////////////////////////////////////////////////////
+ public void serviceChanged(ServiceEvent serviceevent) {
+ ServiceReference servicereference= serviceevent.getServiceReference();
+ String [] ast=(String[])(servicereference.getProperty("objectClass"));
+ String as=ast[0];
+ switch (serviceevent.getType()) {
+ case ServiceEvent.REGISTERED :
+ if (as.equals(NamingServiceIfc.class.getName())){
+ this.namingServiceIfcSR=servicereference;
+ }else if (as.equals(MBeanServer.class.getName())){
+ this.mBeanServerSR=servicereference;
+ }
+ if (this.namingServiceIfcSR!=null && this.mBeanServerSR!=null){
+ try{
+ this.startRmiConnector();
+ }catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+ break;
+ case ServiceEvent.UNREGISTERING :
+ try{
+ this.stopRmiConnector();
+ }catch (Exception e){
+ e.printStackTrace();
+ }
+ break;
+ }
+ }
+
+ public static void log(int prio, String message, Throwable t){
+ if (RmiConnectorActivator.bc!=null){
+ ServiceReference logSR=RmiConnectorActivator.bc.getServiceReference(LogService.class.getName());
+ if (logSR!=null){
+ ((LogService)RmiConnectorActivator.bc.getService(logSR)).log(prio, message, t);
+ }else{
+ System.out.println("No Log Service");
+ }
+ }else{
+ System.out.println(RmiConnectorActivator.class.getName()+": No bundleContext");
+ }
+ }
+
+ private void startRmiConnector() throws Exception{
+ String profile=bc.getProperty(BundleCache.CACHE_PROFILE_PROP);
+ if (profile==null){
+ profile=System.getProperty(BundleCache.CACHE_PROFILE_PROP);
+ }
+ String rmiPort=bc.getProperty("insa.jmxconsole.rmiport."+profile);
+ if (rmiPort==null){
+ rmiPort="1099";
+ }
+ String url="service:jmx:rmi:///jndi/rmi://"+ InetAddress.getLocalHost().getHostAddress()+":"+rmiPort+"/"+profile;
+ RmiConnectorActivator.log(LogService.LOG_INFO, "insa.jmx.rmiconnector.url ==> "+url, null);
+
+ RmiConnectorActivator.log(LogService.LOG_INFO, "Starting JMX Rmi Connector "+version,null);
+ this.mbs=(MBeanServer)bc.getService(this.mBeanServerSR);
+ this.nsi=(NamingServiceIfc)bc.getService(this.namingServiceIfcSR);
+ JMXServiceURL address=new JMXServiceURL(url);
+/*
+ Map environment = new HashMap();
+ environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
+ environment.put(Context.PROVIDER_URL, "rmi://localhost:"+rmiPort);
+ environment.put(JMXConnectorServerFactory.PROTOCOL_PROVIDER_CLASS_LOADER,this.getClass().getClassLoader());
+ */
+
+/* Loggin
+Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
+java.util.logging.ConsoleHandler ch=new java.util.logging.ConsoleHandler();
+ch.setLevel(java.util.logging.Level.FINEST);
+java.util.logging.Logger.getLogger("javax.management.remote.misc").setLevel(java.util.logging.Level.FINEST);
+java.util.logging.Logger.getLogger("javax.management.remote.rmi").setLevel(java.util.logging.Level.FINEST);
+java.util.logging.Logger.getLogger("javax.management.remote.rmi").addHandler(ch);
+java.util.logging.Logger.getLogger("javax.management.remote.misc").addHandler(ch);
+*/
+
+ /*
+ java.util.Map env = new java.util.HashMap();
+ env.put(JMXConnectorServerFactory.PROTOCOL_PROVIDER_CLASS_LOADER, this.getClass().getClassLoader());
+ env.put("jmx.remote.protocol.provider.pkgs", "mx4j.remote.provider");
+ */
+
+ this.connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(address, null, this.mbs);
+
+ RmiConnectorActivator.log(LogService.LOG_DEBUG, "===> "+this.connectorServer, null);
+ RmiConnectorActivator.log(LogService.LOG_DEBUG, "======> "+this.connectorServer.getMBeanServer(), null);
+
+// this.mbs.registerMBean(this.connectorServer, this.connectorServerName);
+// this.connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(address, null, java.lang.management.ManagementFactory.getPlatformMBeanServer());
+ this.connectorServer.start();
+
+ RmiConnectorActivator.log(LogService.LOG_INFO, "JMX Rmi Connector started "+version,null);
+ }
+
+ private void stopRmiConnector() throws Exception {
+ RmiConnectorActivator.log(LogService.LOG_INFO, "Stopping JMX Rmi connector "+version,null);
+ if (this.connectorServer!=null){
+ this.connectorServer.stop();
+ this.connectorServer=null;
+ }
+
+ if (this.mbs!=null){
+//SFR this.mbs.unregisterMBean(this.connectorServerName);
+ this.mbs=null;
+ }
+
+ this.nsi=null;
+
+ if (this.mBeanServerSR!=null){
+ RmiConnectorActivator.bc.ungetService(this.mBeanServerSR);
+ this.mBeanServerSR=null;
+ }
+ if (this.namingServiceIfcSR!=null){
+ RmiConnectorActivator.bc.ungetService(this.namingServiceIfcSR);
+ this.namingServiceIfcSR=null;
+ }
+ this.connectorServerName=null;
+ RmiConnectorActivator.log(LogService.LOG_INFO, "Rmi Connector stopped "+version,null);
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/AbstractHeartBeat.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/AbstractHeartBeat.java
new file mode 100644
index 0000000..ff5edd3
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/AbstractHeartBeat.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ *
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public abstract class AbstractHeartBeat implements HeartBeat, Runnable
+{
+ private final ConnectionNotificationEmitter emitter;
+ private long pulsePeriod;
+ private int maxRetries;
+ private Thread thread;
+ private volatile boolean stopped;
+
+ protected AbstractHeartBeat(ConnectionNotificationEmitter emitter, Map environment)
+ {
+ this.emitter = emitter;
+ if (environment != null)
+ {
+ try
+ {
+ pulsePeriod = ((Long)environment.get(MX4JRemoteConstants.CONNECTION_HEARTBEAT_PERIOD)).longValue();
+ }
+ catch (Exception ignored)
+ {
+ }
+ try
+ {
+ maxRetries = ((Integer)environment.get(MX4JRemoteConstants.CONNECTION_HEARTBEAT_RETRIES)).intValue();
+ }
+ catch (Exception ignored)
+ {
+ }
+ }
+ if (pulsePeriod <= 0) pulsePeriod = 5000;
+ if (maxRetries <= 0) maxRetries = 3;
+ }
+
+ protected abstract void pulse() throws IOException;
+
+ public void start() throws IOException
+ {
+ thread = new Thread(this, "Connection HeartBeat");
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+ public void stop() throws IOException
+ {
+ if (stopped) return;
+ stopped = true;
+ thread.interrupt();
+ }
+
+ public void run()
+ {
+ int retries = 0;
+ while (!stopped && !thread.isInterrupted())
+ {
+ try
+ {
+ Thread.sleep(pulsePeriod);
+
+ try
+ {
+ pulse();
+ retries = 0;
+ }
+ catch (IOException x)
+ {
+ if (retries++ == maxRetries)
+ {
+ // The connection has died
+ sendConnectionNotificationFailed();
+ // And go on
+ }
+ }
+ }
+ catch (InterruptedException x)
+ {
+ Thread.currentThread().interrupt();
+ return;
+ }
+ }
+ }
+
+ protected void sendConnectionNotificationFailed()
+ {
+ emitter.sendConnectionNotificationFailed();
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/ClientProxy.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/ClientProxy.java
new file mode 100644
index 0000000..08a8887
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/ClientProxy.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.management.MBeanServerConnection;
+
+/**
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class ClientProxy implements InvocationHandler
+{
+ private final MBeanServerConnection target;
+
+ protected ClientProxy(MBeanServerConnection target)
+ {
+ this.target = target;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
+ {
+ try
+ {
+ return method.invoke(target, args);
+ }
+ catch (InvocationTargetException x)
+ {
+ throw x.getTargetException();
+ }
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/ConnectionNotificationEmitter.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/ConnectionNotificationEmitter.java
new file mode 100644
index 0000000..ced5560
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/ConnectionNotificationEmitter.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote;
+
+import java.io.IOException;
+
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.remote.JMXConnectionNotification;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.rmi.RMIConnector;
+
+/**
+ *
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class ConnectionNotificationEmitter extends NotificationBroadcasterSupport
+{
+ private static long sequenceNumber;
+
+ private JMXConnector connector;
+
+ public ConnectionNotificationEmitter(JMXConnector connector)
+ {
+ this.connector = connector;
+ }
+
+ private long getNextNotificationNumber()
+ {
+ synchronized (RMIConnector.class)
+ {
+ return sequenceNumber++;
+ }
+ }
+
+ private String getConnectionId()
+ {
+ try
+ {
+ return connector.getConnectionId();
+ }
+ catch (IOException x)
+ {
+ return null;
+ }
+ }
+
+ public void sendConnectionNotificationOpened()
+ {
+ JMXConnectionNotification notification = new JMXConnectionNotification(JMXConnectionNotification.OPENED, connector, getConnectionId(), getNextNotificationNumber(), "Connection opened", null);
+ sendNotification(notification);
+ }
+
+ public void sendConnectionNotificationClosed()
+ {
+ JMXConnectionNotification notification = new JMXConnectionNotification(JMXConnectionNotification.CLOSED, connector, getConnectionId(), getNextNotificationNumber(), "Connection closed", null);
+ sendNotification(notification);
+ }
+
+ public void sendConnectionNotificationFailed()
+ {
+ JMXConnectionNotification notification = new JMXConnectionNotification(JMXConnectionNotification.FAILED, connector, getConnectionId(), getNextNotificationNumber(), "Connection failed", null);
+ sendNotification(notification);
+ }
+
+ public void sendConnectionNotificationLost(long howMany)
+ {
+ JMXConnectionNotification notification = new JMXConnectionNotification(JMXConnectionNotification.NOTIFS_LOST, connector, getConnectionId(), getNextNotificationNumber(), "Some notification (" + howMany + ") was lost", null);
+ sendNotification(notification);
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/ConnectionResolver.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/ConnectionResolver.java
new file mode 100644
index 0000000..6d73a80
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/ConnectionResolver.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.management.remote.JMXServiceURL;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.MX4JRemoteConstants;
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.ProviderHelper;
+
+import org.osgi.service.log.LogService;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.RmiConnectorActivator;
+
+/**
+ * ConnectionResolver handles the details of creating connections for different protocols.
+ * Subclasses for the specific protocol are found using a mechanism very similar to the
+ * one specified by {@link javax.management.remote.JMXConnectorFactory}. Here a subclass
+ * has a fully qualified name specified like this:
+ * <package>.resolver.<protocol>.<PROTOCOL>Resolver, for example
+ * {@link mx4j.remote.resolver.rmi.RMIResolver}
+ * This class is used from both the client and the server.
+ * The former uses it to lookup stubs or connections to the server side; the latter uses it
+ * to create server instances and make them availale to clients, for example via JNDI.
+ *
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public abstract class ConnectionResolver extends ProviderHelper
+{
+ /**
+ * Returns a subclass of ConnectionResolver for the specified protocol.
+ */
+ public static ConnectionResolver getInstance(String proto) {
+ String protocol = normalizeProtocol(proto);
+ String resolverPackages = findResolverPackageList();
+//sfr return loadResolver(resolverPackages, protocol, Thread.currentThread().getContextClassLoader());
+ return loadResolver(resolverPackages, protocol, ConnectionResolver.class.getClassLoader());
+ }
+
+ private static String findResolverPackageList() {
+ String packages = findSystemPackageList(MX4JRemoteConstants.PROTOCOL_RESOLVER_PACKAGES);
+ if (packages == null)
+ packages = MX4JRemoteConstants.RESOLVER_PACKAGES;
+ else
+ packages += MX4JRemoteConstants.RESOLVER_PACKAGES_SEPARATOR + MX4JRemoteConstants.RESOLVER_PACKAGES;
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Resolver packages list is: " + packages, null);
+ return packages;
+ }
+
+ private static ConnectionResolver loadResolver(String packages, String protocol, ClassLoader loader) {
+ StringTokenizer tokenizer = new StringTokenizer(packages, MX4JRemoteConstants.RESOLVER_PACKAGES_SEPARATOR);
+ while (tokenizer.hasMoreTokens()) {
+ String pkg = tokenizer.nextToken().trim();
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Resolver package: " + pkg,null);
+ if (pkg.length() == 0) continue;
+
+ String className = protocol.toUpperCase() + MX4JRemoteConstants.RESOLVER_CLASS;
+ String resolverClassName = constructClassName(pkg, protocol, className);
+
+ Class resolverClass = null;
+ try
+ {
+ resolverClass = loadClass(resolverClassName, loader);
+ }
+ catch (ClassNotFoundException x)
+ {
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Resolver class " + resolverClassName + " not found, continuing with next package",null);
+ continue;
+ }
+ catch (Exception x)
+ {
+ RmiConnectorActivator.log(LogService.LOG_WARNING, "Cannot load resolver class " + resolverClassName, x);
+ return null;
+ }
+
+ try
+ {
+ return (ConnectionResolver)resolverClass.newInstance();
+ }
+ catch (Exception x)
+ {
+ RmiConnectorActivator.log(LogService.LOG_WARNING,"Cannot instantiate resolver class " + resolverClassName, x);
+ return null;
+ }
+ }
+
+ // Nothing found
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Could not find resolver for protocol " + protocol + " in package list '" + packages + "'", null);
+ return null;
+ }
+
+ /**
+ * Looks up a connection to the server side as specified in the given JMXServiceURL.
+ * This method is used by {@link javax.management.remote.JMXConnector}s.
+ */
+ public abstract Object lookupClient(JMXServiceURL url, Map environment) throws IOException;
+
+ /**
+ * Connects the client returned by {@link #lookupClient} to the server side
+ */
+ public abstract Object bindClient(Object client, Map environment) throws IOException;
+
+ /**
+ * Creates an instance of the server as specified in the given JMXServiceURL.
+ * It is only a factory method, it should just return a fresh instance of the server;
+ * other methods are responsible to make it available to clients (for example exporting it).
+ * This method is used by {@link javax.management.remote.JMXConnectorServer}s.
+ * @see #bindServer
+ */
+ public abstract Object createServer(JMXServiceURL url, Map environment) throws IOException;
+
+ /**
+ * Binds the server created by {@link #createServer} to a place specified in the JMXServiceURL.
+ * @return a new JMXServiceURL that specifies where the server has been bound to.
+ * @see #unbindServer
+ */
+ public abstract JMXServiceURL bindServer(Object server, JMXServiceURL url, Map environment) throws IOException;
+
+ /**
+ * Unbinds the server created by {@link #createServer} from the place specified in the JMXServiceURL.
+ * @see #bindServer
+ */
+ public abstract void unbindServer(Object server, JMXServiceURL address, Map environment) throws IOException;
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/DefaultRemoteNotificationServerHandler.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/DefaultRemoteNotificationServerHandler.java
new file mode 100644
index 0000000..2c7943c
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/DefaultRemoteNotificationServerHandler.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.management.Notification;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.NotificationFilter;
+import javax.management.remote.NotificationResult;
+import javax.management.remote.TargetedNotification;
+
+import org.osgi.service.log.LogService;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.RmiConnectorActivator;
+
+
+/**
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class DefaultRemoteNotificationServerHandler implements RemoteNotificationServerHandler
+{
+ private static int listenerID;
+
+ private final NotificationListener listener;
+ private final Map tuples = new HashMap();
+ private final NotificationBuffer buffer;
+
+ public DefaultRemoteNotificationServerHandler(Map environment)
+ {
+ listener = new ServerListener();
+ buffer = new NotificationBuffer(environment);
+ }
+
+ public Integer generateListenerID(ObjectName name, NotificationFilter filter)
+ {
+ synchronized (DefaultRemoteNotificationServerHandler.class)
+ {
+ return new Integer(++listenerID);
+ }
+ }
+
+ public NotificationListener getServerNotificationListener()
+ {
+ return listener;
+ }
+
+ public void addNotificationListener(Integer id, NotificationTuple tuple)
+ {
+ synchronized (tuples)
+ {
+ tuples.put(id, tuple);
+ }
+ }
+
+ public void removeNotificationListener(Integer id)
+ {
+ synchronized (tuples)
+ {
+ tuples.remove(id);
+ }
+ }
+
+ public NotificationTuple getNotificationListener(Integer id)
+ {
+ synchronized (tuples)
+ {
+ return (NotificationTuple)tuples.get(id);
+ }
+ }
+
+ public NotificationResult fetchNotifications(long sequenceNumber, int maxNotifications, long timeout)
+ {
+ return buffer.getNotifications(sequenceNumber, maxNotifications, timeout);
+ }
+
+ /**
+ * Called when there are no notifications to send to the client.
+ * It is guaranteed that no notification can be added before this method waits on the given lock.
+ * It should wait on the given lock for the specified timeout, and return true
+ * to send notifications (if no notifications arrived, an empty notification array
+ * will be returned to the client), or false if no notifications should be sent to
+ * the client.
+ * @param lock The object on which {@link #wait} should be called
+ * @param timeout The amount of time to wait (guaranteed to be strictly greater than 0)
+ */
+ protected boolean waitForNotifications(Object lock, long timeout)
+ {
+ synchronized (lock)
+ {
+ try
+ {
+ lock.wait(timeout);
+ }
+ catch (InterruptedException x)
+ {
+ Thread.currentThread().interrupt();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * This method filters the given notification array and returns a possibly smaller array containing
+ * only notifications that passed successfully the filtering.
+ * Default behavior is no filtering, but subclasses may choose to change this bahavior.
+ * For example, for RMI, one can assure that all notifications are truly serializable, and log those
+ * that are not.
+ */
+ protected TargetedNotification[] filterNotifications(TargetedNotification[] notifications)
+ {
+ return notifications;
+ }
+
+ private void addNotification(Integer id, Notification notification)
+ {
+ buffer.add(new TargetedNotification(notification, id));
+ }
+
+ public class ServerListener implements NotificationListener
+ {
+ public void handleNotification(Notification notification, Object handback)
+ {
+ Integer id = (Integer)handback;
+ addNotification(id, notification);
+ }
+ }
+
+ public class NotificationBuffer
+ {
+ private final List buffer = new LinkedList();
+ private int maxCapacity;
+ private int purgeDistance;
+ private long firstSequence;
+ private long lastSequence;
+ private long lowestExpectedSequence;
+
+ public NotificationBuffer(Map environment)
+ {
+ if (environment != null)
+ {
+ try
+ {
+ maxCapacity = ((Integer)environment.get(MX4JRemoteConstants.NOTIFICATION_BUFFER_CAPACITY)).intValue();
+ }
+ catch (Exception ignored)
+ {
+ }
+
+ try
+ {
+ purgeDistance = ((Integer)environment.get(MX4JRemoteConstants.NOTIFICATION_PURGE_DISTANCE)).intValue();
+ }
+ catch (Exception ignored)
+ {
+ }
+ }
+ if (maxCapacity <= 0) maxCapacity = 1024;
+ if (purgeDistance <= 0) purgeDistance = 128;
+ }
+
+ public int getSize()
+ {
+ synchronized (buffer)
+ {
+ return buffer.size();
+ }
+ }
+
+ public void add(TargetedNotification notification)
+ {
+ synchronized (buffer)
+ {
+ if (buffer.size() == maxCapacity)
+ {
+ RmiConnectorActivator.log(LogService.LOG_DEBUG, "Notification buffer full: " + this, null);
+ removeRange(0, 1);
+ }
+ buffer.add(notification);
+ ++lastSequence;
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Notification added to buffer: " + this, null);
+ buffer.notifyAll();
+ }
+ }
+
+ private void removeRange(int start, int end)
+ {
+ synchronized (buffer)
+ {
+ buffer.subList(start, end).clear();
+ firstSequence += end - start;
+ }
+ }
+
+ private long getFirstSequenceNumber()
+ {
+ synchronized (buffer)
+ {
+ return firstSequence;
+ }
+ }
+
+ private long getLastSequenceNumber()
+ {
+ synchronized (buffer)
+ {
+ return lastSequence;
+ }
+ }
+
+ public NotificationResult getNotifications(long sequenceNumber, int maxNotifications, long timeout)
+ {
+ synchronized (buffer)
+ {
+ NotificationResult result = null;
+ int size = 0;
+ if (sequenceNumber < 0)
+ {
+ // We loose the notifications between addNotificationListener() and fetchNotifications(), but c'est la vie.
+ long sequence = getLastSequenceNumber();
+ size = new Long(sequence + 1).intValue();
+ result = new NotificationResult(getFirstSequenceNumber(), sequence, new TargetedNotification[0]);
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"First fetchNotification call: " + this + ", returning " + result, null);
+ }
+ else
+ {
+ int start = new Long(sequenceNumber - getFirstSequenceNumber()).intValue();
+
+ List sublist = null;
+ boolean send = false;
+ while (size == 0)
+ {
+ int end = buffer.size();
+ if (end - start > maxNotifications) end = start + maxNotifications;
+
+ sublist = buffer.subList(start, end);
+ size = sublist.size();
+
+ if (send) break;
+
+ if (size == 0)
+ {
+ if (timeout <= 0) break;
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"No notifications to send, waiting " + timeout + " ms", null);
+
+ // We wait for notifications to arrive. Since we release the lock on the buffer
+ // other threads can modify it. To avoid ConcurrentModificationException we compute
+ // again the sublist
+ send = waitForNotifications(buffer, timeout);
+ }
+ }
+
+ TargetedNotification[] notifications = (TargetedNotification[])sublist.toArray(new TargetedNotification[size]);
+ notifications = filterNotifications(notifications);
+ result = new NotificationResult(getFirstSequenceNumber(), sequenceNumber + size, notifications);
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Non-first fetchNotification call: " + this + ", returning " + result, null);
+
+ purgeNotifications(sequenceNumber, size);
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Purged Notifications: " + this, null);
+ }
+ return result;
+ }
+ }
+
+ private void purgeNotifications(long sequenceNumber, int size)
+ {
+ // Record the lowest expected sequence number sent by the client.
+ // New clients will always have an initial big sequence number
+ // (they're initialized with getLastSequenceNumber()), while old
+ // clients can have lesser sequence numbers.
+ // Here we record the lesser of these sequence numbers, that is the
+ // sequence number of the oldest notification any client may ever ask.
+ // This way we can purge old notifications that have already been
+ // delivered to clients.
+
+ // The worst case is when a client has a long interval between fetchNotifications()
+ // calls, and another client has a short interval. The lowestExpectedSequence will
+ // grow with the second client, until a purge happens, so the first client can
+ // loose notifications. By tuning appropriately the purgeDistance and the interval
+ // between fetchNotifications() calls, it should never happen.
+
+ synchronized (buffer)
+ {
+ if (sequenceNumber <= lowestExpectedSequence)
+ {
+ long lowest = Math.min(lowestExpectedSequence, sequenceNumber);
+
+ if (lowest - getFirstSequenceNumber() > purgeDistance)
+ {
+ // Purge only half of the old notifications, for safety
+ int purgeSize = purgeDistance >> 1;
+ removeRange(0, purgeSize);
+ }
+
+ lowestExpectedSequence = sequenceNumber + size;
+ }
+ }
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("NotificationBuffer@");
+ buffer.append(Integer.toHexString(hashCode())).append("[");
+ buffer.append("first=").append(getFirstSequenceNumber()).append(", ");
+ buffer.append("last=").append(getLastSequenceNumber()).append(", ");
+ buffer.append("size=").append(getSize()).append(", ");
+ buffer.append("lowest expected=").append(lowestExpectedSequence).append(", ");
+ buffer.append("maxCapacity=").append(maxCapacity).append(", ");
+ buffer.append("purgeDistance=").append(purgeDistance).append("]");
+ return buffer.toString();
+ }
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/HeartBeat.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/HeartBeat.java
new file mode 100644
index 0000000..23f2a22
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/HeartBeat.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote;
+
+import java.io.IOException;
+
+/**
+ *
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public interface HeartBeat
+{
+ public void start() throws IOException;
+ public void stop() throws IOException;
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/MX4JRemoteConstants.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/MX4JRemoteConstants.java
new file mode 100644
index 0000000..ae42d2b
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/MX4JRemoteConstants.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote;
+
+/**
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public interface MX4JRemoteConstants
+{
+ /**
+ * A vertical bar '|' as mandated by the spec
+ */
+ public static final String PROVIDER_PACKAGES_SEPARATOR = "|";
+ /**
+ * MX4J provider packages list for JMXConnector and JMXConnectorServer factories
+ */
+ public static final String PROVIDER_PACKAGES = "org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.provider" + PROVIDER_PACKAGES_SEPARATOR + "org.apache.felix.mosgi.jmx.rmiconnector.mx4j.tools.remote.provider";
+ /**
+ * The string 'ClientProvider' as mandated by the spec
+ */
+ public static final String CLIENT_PROVIDER_CLASS = "ClientProvider";
+ /**
+ * The string 'ServerProvider' as mandated by the spec
+ */
+ public static final String SERVER_PROVIDER_CLASS = "ServerProvider";
+
+
+ /**
+ * The key that specifies resolver packages, very much like
+ * {@link javax.management.remote.JMXConnectorFactory#PROTOCOL_PROVIDER_PACKAGES}
+ */
+ public static final String PROTOCOL_RESOLVER_PACKAGES = "mx4j.remote.resolver.pkgs";
+ /**
+ * A vertical bar '|'
+ */
+ public static final String RESOLVER_PACKAGES_SEPARATOR = PROVIDER_PACKAGES_SEPARATOR;
+ /**
+ * MX4J provider packages list for {@link mx4j.remote.ConnectionResolver} subclasses
+ */
+ public static final String RESOLVER_PACKAGES = "org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.resolver" + RESOLVER_PACKAGES_SEPARATOR + "org.apache.felix.mosgi.jmx.rmiconnector.mx4j.tools.remote.resolver";
+ /**
+ * The string 'Resolver'
+ */
+ public static final String RESOLVER_CLASS = "Resolver";
+
+ /**
+ * The reference implementation uses this property to specify the notification fetch timeout (in ms).
+ * MX4J will use the same for compatibility. DO NOT CHANGE IT unless the reference implementation changes it.
+ */
+ public static final String FETCH_NOTIFICATIONS_TIMEOUT = "jmx.remote.x.client.fetch.timeout";
+ /**
+ * The reference implementation uses this property to specify the maximum number of notification to fetch.
+ * MX4J will use the same for compatibility. DO NOT CHANGE IT unless the reference implementation changes it.
+ */
+ public static final String FETCH_NOTIFICATIONS_MAX_NUMBER = "jmx.remote.x.client.max.notifications";
+ /**
+ * The reference implementation uses this property to specify the notification buffer size.
+ * MX4J will use the same for compatibility. DO NOT CHANGE IT unless the reference implementation changes it.
+ */
+ public static final String NOTIFICATION_BUFFER_CAPACITY = "jmx.remote.x.buffer.size";
+ /**
+ * MX4J's implementation uses this property to specify the distance between the lowest expected notification
+ * sequence number (sent by the client via fetchNotifications()) and the minimum sequence number of the
+ * notification buffer. When this difference is greater than the value of this property, old notifications
+ * are eliminated from the notification buffer
+ */
+ public static final String NOTIFICATION_PURGE_DISTANCE = "jmx.remote.x.notification.purge.distance";
+ /**
+ * MX4J's implementation uses this property to specify the amount of time (in ms) the client should sleep
+ * between notification fetches. A value of 0 means there will be no sleep (fetches will be done one
+ * after the other).
+ */
+ public static final String FETCH_NOTIFICATIONS_SLEEP = "jmx.remote.x.notification.fetch.sleep";
+
+ /**
+ * MX4J's implementation uses this property to specify the period (in ms) of the heartbeat pulse for
+ * {@link javax.management.remote.JMXConnector JMXConnectors} that use heartbeat to check if the
+ * connection with {@link javax.management.remote.JMXConnectorServer JMXConnectorServers} is still alive.
+ * @see #CONNECTION_HEARTBEAT_RETRIES
+ */
+ public static final String CONNECTION_HEARTBEAT_PERIOD = "jmx.remote.x.connection.heartbeat.period";
+ /**
+ * MX4J's implementation uses this property to specify the number of retries of heartbeat pulses before
+ * declaring the connection between a {@link javax.management.remote.JMXConnector JMXConnector} and a
+ * {@link javax.management.remote.JMXConnectorServer JMXConnectorServer} failed, at which a
+ * {@link javax.management.remote.JMXConnectionNotification notification failed} is emitted.
+ * @see #CONNECTION_HEARTBEAT_PERIOD
+ */
+ public static final String CONNECTION_HEARTBEAT_RETRIES = "jmx.remote.x.connection.heartbeat.retries";
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/MX4JRemoteUtils.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/MX4JRemoteUtils.java
new file mode 100644
index 0000000..396a307
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/MX4JRemoteUtils.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Constructor;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.DomainCombiner;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.ProtectionDomain;
+import javax.security.auth.AuthPermission;
+import javax.security.auth.Policy;
+import javax.security.auth.Subject;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.management.remote.SubjectDelegationPermission;
+
+/**
+ *
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.2 $
+ */
+public class MX4JRemoteUtils
+{
+ private static int connectionNumber;
+
+ /**
+ * Returns a copy of the given Map that does not contain non-serializable entries
+ */
+ public static Map removeNonSerializableEntries(Map map)
+ {
+ Map newMap = new HashMap(map.size());
+ for (Iterator i = map.entrySet().iterator(); i.hasNext();)
+ {
+ Map.Entry entry = (Map.Entry)i.next();
+ if (isSerializable(entry)) newMap.put(entry.getKey(), entry.getValue());
+ }
+ return newMap;
+ }
+
+ private static boolean isSerializable(Object object)
+ {
+ if (object instanceof Map.Entry) return isSerializable(((Map.Entry)object).getKey()) && isSerializable(((Map.Entry)object).getValue());
+ if (object == null) return true;
+ if (object instanceof String) return true;
+ if (object instanceof Number) return true;
+ if (!(object instanceof Serializable)) return false;
+
+ return isTrulySerializable(object);
+ }
+
+ public static boolean isTrulySerializable(Object object)
+ {
+ // Give up and serialize the object
+ try
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(object);
+ oos.close();
+ return true;
+ }
+ catch (IOException ignored)
+ {
+ }
+ return false;
+ }
+
+ public static String createConnectionID(String protocol, String callerAddress, int callerPort, Subject subject)
+ {
+ // See JSR 160 specification at javax/management/remote/package-summary.html
+
+ StringBuffer buffer = new StringBuffer(protocol);
+ buffer.append(':');
+ if (callerAddress != null) buffer.append("//").append(callerAddress);
+ if (callerPort >= 0) buffer.append(':').append(callerPort);
+ buffer.append(' ');
+
+ if (subject != null)
+ {
+ Set principals = subject.getPrincipals();
+ for (Iterator i = principals.iterator(); i.hasNext();)
+ {
+ Principal principal = (Principal)i.next();
+ String name = principal.getName();
+ name = name.replace(' ', '_');
+ buffer.append(name);
+ if (i.hasNext()) buffer.append(';');
+ }
+ }
+ buffer.append(' ');
+
+ buffer.append("0x").append(Integer.toHexString(getNextConnectionNumber()));
+
+ return buffer.toString();
+ }
+
+ private static synchronized int getNextConnectionNumber()
+ {
+ return ++connectionNumber;
+ }
+
+ public static Object subjectInvoke(Subject subject, Subject delegate, AccessControlContext context, PrivilegedExceptionAction action) throws Exception
+ {
+ if (delegate != null)
+ {
+ if (subject == null) throw new SecurityException("There is no authenticated subject to delegate to");
+ checkSubjectDelegationPermission(delegate, getSubjectContext(subject, context));
+ }
+
+ if (subject == null)
+ {
+ if (context == null) return action.run();
+ try
+ {
+ return AccessController.doPrivileged(action, context);
+ }
+ catch (PrivilegedActionException x)
+ {
+ throw x.getException();
+ }
+ }
+
+ try
+ {
+ AccessControlContext subjectContext = getSubjectContext(subject, context);
+ return Subject.doAsPrivileged(subject, action, subjectContext);
+ }
+ catch (PrivilegedActionException x)
+ {
+ throw x.getException();
+ }
+ }
+
+ private static void checkSubjectDelegationPermission(final Subject delegate, AccessControlContext context) throws SecurityException
+ {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm != null)
+ {
+ AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ StringBuffer buffer = new StringBuffer();
+ Set principals = delegate.getPrincipals();
+ for (Iterator i = principals.iterator(); i.hasNext();)
+ {
+ Principal principal = (Principal)i.next();
+ buffer.setLength(0);
+ String permission = buffer.append(principal.getClass().getName()).append(".").append(principal.getName()).toString();
+ sm.checkPermission(new SubjectDelegationPermission(permission));
+ }
+ return null;
+ }
+ }, context);
+ }
+ }
+
+ /**
+ * Returns a suitable AccessControlContext that restricts access in a {@link Subject#doAsPrivileged} call
+ * based on the current JAAS authorization policy, and combined with the given context.
+ *
+ * This is needed because the server stack frames in a call to a JMXConnectorServer are,
+ * for example for RMI, like this:
+ * <pre>
+ * java.lang.Thread.run()
+ * [rmi runtime classes]
+ * javax.management.remote.rmi.RMIConnectionImpl
+ * [mx4j JSR 160 implementation code]
+ * javax.security.auth.Subject.doAsPrivileged()
+ * [mx4j JSR 160 implementation code]
+ * [mx4j JSR 3 implementation code]
+ * </pre>
+ * All protection domains in this stack frames have AllPermission, normally, and the Subject.doAsPrivileged()
+ * call stops the checks very early. <br>
+ *
+ * So we need a restricting context (created at the start() of the connector server), and furthermore we need
+ * to combine the restricting context with a "special" context that does not have the same location as the
+ * JSR 3 and 160 classes and implementation (in particular will have a null location). <br>
+ * The "injection" of this synthetic ProtectionDomain allows to give AllPermission to the JSR 3 and 160 classes
+ * and implementation, but still have the possibility to specify a JAAS policy with MBeanPermissions in this way:
+ * <pre>
+ * grant principal javax.management.remote.JMXPrincipal "mx4j"
+ * {
+ * permission javax.management.MBeanPermission "*", "getAttribute";
+ * };
+ * </pre>
+ */
+ private static AccessControlContext getSubjectContext(final Subject subject, final AccessControlContext context)
+ {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm == null)
+ {
+ return context;
+ }
+ else
+ {
+ return (AccessControlContext)AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ InjectingDomainCombiner combiner = new InjectingDomainCombiner(subject);
+ AccessControlContext acc = new AccessControlContext(context, combiner);
+ AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ // Check this permission, that is required anyway, to combine the domains
+ sm.checkPermission(new AuthPermission("doAsPrivileged"));
+ return null;
+ }
+ }, acc);
+ ProtectionDomain[] combined = combiner.getCombinedDomains();
+ return new AccessControlContext(combined);
+ }
+ });
+ }
+ }
+
+ private static class InjectingDomainCombiner implements DomainCombiner
+ {
+ private static Constructor domainConstructor;
+
+ static
+ {
+ try
+ {
+ domainConstructor = ProtectionDomain.class.getConstructor(new Class[]{CodeSource.class, PermissionCollection.class, ClassLoader.class, Principal[].class});
+ }
+ catch (Exception x)
+ {
+ }
+ }
+
+ private ProtectionDomain domain;
+ private ProtectionDomain[] combined;
+
+ public InjectingDomainCombiner(Subject subject)
+ {
+ if (domainConstructor != null)
+ {
+ Principal[] principals = (Principal[])subject.getPrincipals().toArray(new Principal[0]);
+ try
+ {
+ domain = (ProtectionDomain)domainConstructor.newInstance(new Object[]{new CodeSource(null, (java.security.cert.Certificate[])null), null, null, principals});
+ }
+ catch (Exception x)
+ {
+ }
+ }
+
+ if (domain == null)
+ {
+ // This is done for JDK 1.3 compatibility.
+ domain = new SubjectProtectionDomain(new CodeSource(null, (java.security.cert.Certificate[])null), subject);
+ }
+ }
+
+ public ProtectionDomain[] combine(ProtectionDomain[] current, ProtectionDomain[] assigned)
+ {
+ int length = current.length;
+ ProtectionDomain[] result = null;
+ if (assigned == null || assigned.length == 0)
+ {
+ result = new ProtectionDomain[length + 1];
+ System.arraycopy(current, 0, result, 0, length);
+ }
+ else
+ {
+ result = new ProtectionDomain[length + assigned.length + 1];
+ System.arraycopy(current, 0, result, 0, length);
+ System.arraycopy(assigned, 0, result, length, assigned.length);
+ }
+ result[result.length - 1] = domain;
+ this.combined = result;
+ return result;
+ }
+
+ public ProtectionDomain[] getCombinedDomains()
+ {
+ return combined;
+ }
+
+ private static class SubjectProtectionDomain extends ProtectionDomain
+ {
+ private final Subject subject;
+
+ public SubjectProtectionDomain(CodeSource codesource, Subject subject)
+ {
+ super(codesource, null);
+ this.subject = subject;
+ }
+
+ public boolean implies(Permission permission)
+ {
+ Policy policy = (Policy)AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ return Policy.getPolicy();
+ }
+ });
+ PermissionCollection permissions = policy.getPermissions(subject, getCodeSource());
+ return permissions.implies(permission);
+ }
+ }
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/NotificationTuple.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/NotificationTuple.java
new file mode 100644
index 0000000..2998dbe
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/NotificationTuple.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote;
+
+import javax.management.NotificationFilter;
+import javax.management.Notification;
+import javax.management.ObjectName;
+import javax.management.NotificationListener;
+
+/**
+ *
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class NotificationTuple
+{
+ private static final NotificationFilter NO_FILTER = new NotificationFilter()
+ {
+ public boolean isNotificationEnabled(Notification notification)
+ {
+ return true;
+ }
+
+ public String toString()
+ {
+ return "no filter";
+ }
+ };
+ private static final Object NO_HANDBACK = new Object()
+ {
+ public String toString()
+ {
+ return "no handback";
+ }
+ };
+
+ private final ObjectName observed;
+ private final NotificationListener listener;
+ private final NotificationFilter filter;
+ private final Object handback;
+ private boolean invokeFilter;
+
+ public NotificationTuple(ObjectName observed, NotificationListener listener)
+ {
+ this(observed, listener, NO_FILTER, NO_HANDBACK);
+ }
+
+ public NotificationTuple(ObjectName observed, NotificationListener listener, NotificationFilter filter, Object handback)
+ {
+ this.observed = observed;
+ this.listener = listener;
+ this.filter = filter;
+ this.handback = handback;
+ this.invokeFilter = false;
+ }
+
+ public NotificationListener getNotificationListener()
+ {
+ return listener;
+ }
+
+ public Object getHandback()
+ {
+ if (handback == NO_HANDBACK) return null;
+ return handback;
+ }
+
+ public NotificationFilter getNotificationFilter()
+ {
+ if (filter == NO_FILTER) return null;
+ return filter;
+ }
+
+ public void setInvokeFilter(boolean invoke)
+ {
+ this.invokeFilter = invoke;
+ }
+
+ public boolean getInvokeFilter()
+ {
+ if (!invokeFilter) return false;
+ NotificationFilter filter = getNotificationFilter();
+ if (filter == null) return false;
+ return true;
+ }
+
+ public boolean equals(Object obj)
+ {
+ if (this == obj) return true;
+ if (!(obj instanceof NotificationTuple)) return false;
+
+ final NotificationTuple other = (NotificationTuple)obj;
+
+ if (!observed.equals(other.observed)) return false;
+ if (!listener.equals(other.listener)) return false;
+
+ // Special treatment for special filter
+ if (filter == NO_FILTER) return true;
+ if (other.filter == NO_FILTER) return true;
+
+ if (filter != null ? !filter.equals(other.filter) : other.filter != null) return false;
+ if (handback != null ? !handback.equals(other.handback) : other.handback != null) return false;
+
+ return true;
+ }
+
+ public int hashCode()
+ {
+ int result;
+ result = observed.hashCode();
+ result = 29 * result + listener.hashCode();
+ result = 29 * result + (filter != null ? filter.hashCode() : 0);
+ result = 29 * result + (handback != null ? handback.hashCode() : 0);
+ return result;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("NotificationTuple [");
+ buffer.append(observed).append(", ");
+ buffer.append(listener).append(", ");
+ buffer.append(filter).append(", ");
+ buffer.append(handback).append("]");
+ return buffer.toString();
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/ProviderFactory.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/ProviderFactory.java
new file mode 100644
index 0000000..78cce6b
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/ProviderFactory.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorProvider;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXConnectorServerProvider;
+import javax.management.remote.JMXProviderException;
+import javax.management.remote.JMXServiceURL;
+
+import org.osgi.service.log.LogService;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.RmiConnectorActivator;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.MX4JRemoteConstants;
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.ProviderHelper;
+
+/**
+ *
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.2 $
+ */
+public class ProviderFactory extends ProviderHelper
+{
+ public static JMXConnectorProvider newJMXConnectorProvider(JMXServiceURL url, Map env) throws IOException
+ {
+ // Yes, throw NPE if url is null (spec compliant)
+ String protocol = normalizeProtocol(url.getProtocol());
+ String providerPackages = findProviderPackageList(env, JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES);
+ ClassLoader classLoader = findProviderClassLoader(env, JMXConnectorFactory.PROTOCOL_PROVIDER_CLASS_LOADER);
+ JMXConnectorProvider provider = (JMXConnectorProvider)loadProvider(providerPackages, protocol, MX4JRemoteConstants.CLIENT_PROVIDER_CLASS, classLoader);
+ return provider;
+ }
+
+ public static JMXConnectorServerProvider newJMXConnectorServerProvider(JMXServiceURL url, Map env) throws IOException
+ {
+ // Yes, throw NPE if url is null (spec compliant)
+ String protocol = normalizeProtocol(url.getProtocol());
+ String providerPackages = findProviderPackageList(env, JMXConnectorServerFactory.PROTOCOL_PROVIDER_PACKAGES);
+ ClassLoader classLoader = findProviderClassLoader(env, JMXConnectorFactory.PROTOCOL_PROVIDER_CLASS_LOADER);
+ JMXConnectorServerProvider provider = (JMXConnectorServerProvider)loadProvider(providerPackages, protocol, MX4JRemoteConstants.SERVER_PROVIDER_CLASS, classLoader);
+ return provider;
+ }
+
+ private static String findEnvironmentProviderPackageList(Map environment, String key) throws JMXProviderException
+ {
+ String providerPackages = null;
+ if (environment != null)
+ {
+ Object pkgs = environment.get(key);
+ RmiConnectorActivator.log(LogService.LOG_DEBUG, "Provider packages in the environment: " + pkgs, null);
+ if (pkgs != null && !(pkgs instanceof String)) throw new JMXProviderException("Provider package list must be a string");
+ providerPackages = (String)pkgs;
+ }
+ return providerPackages;
+ }
+
+ private static String findProviderPackageList(Map environment, final String providerPkgsKey) throws JMXProviderException
+ {
+ // 1. Look in the environment
+ // 2. Look for system property
+ // 3. Use implementation's provider
+
+ String providerPackages = findEnvironmentProviderPackageList(environment, providerPkgsKey);
+
+ if (providerPackages == null)
+ {
+ providerPackages = findSystemPackageList(providerPkgsKey);
+ }
+
+ if (providerPackages != null && providerPackages.trim().length() == 0) throw new JMXProviderException("Provider package list cannot be an empty string");
+
+ if (providerPackages == null)
+ providerPackages = MX4JRemoteConstants.PROVIDER_PACKAGES;
+ else
+ providerPackages += MX4JRemoteConstants.PROVIDER_PACKAGES_SEPARATOR + MX4JRemoteConstants.PROVIDER_PACKAGES;
+
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Provider packages list is: " + providerPackages,null);
+
+ return providerPackages;
+ }
+
+ private static ClassLoader findProviderClassLoader(Map environment, String providerLoaderKey)
+ {
+
+ ClassLoader classLoader = null;
+ if (environment != null)
+ {
+ Object loader = environment.get(providerLoaderKey);
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Provider classloader in the environment: " + loader, null);
+ if (loader != null && !(loader instanceof ClassLoader)) throw new IllegalArgumentException("Provider classloader is not a ClassLoader");
+ classLoader = (ClassLoader)loader;
+ }
+
+ if (classLoader == null)
+ {
+ //classLoader = Thread.currentThread().getContextClassLoader();
+ classLoader = ProviderFactory.class.getClassLoader();
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Provider classloader (was null) in the environment: " + classLoader, null);
+ }
+
+ // Add the classloader as required by the spec
+ environment.put(JMXConnectorFactory.PROTOCOL_PROVIDER_CLASS_LOADER, classLoader);
+ RmiConnectorActivator.log(LogService.LOG_WARNING,"Provider classloader added to the environment", null);
+
+ return classLoader;
+ }
+
+ private static Object loadProvider(String packages, String protocol, String className, ClassLoader loader) throws JMXProviderException, MalformedURLException
+ {
+ StringTokenizer tokenizer = new StringTokenizer(packages, MX4JRemoteConstants.PROVIDER_PACKAGES_SEPARATOR);
+ while (tokenizer.hasMoreTokens())
+ {
+ String pkg = tokenizer.nextToken().trim();
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Provider package: " + pkg, null);
+
+ // The spec states the package cannot be empty
+ if (pkg.length() == 0) throw new JMXProviderException("Empty package list not allowed: " + packages);
+
+ String providerClassName = constructClassName(pkg, protocol, className);
+
+ Class providerClass = null;
+ try
+ {
+ providerClass = loadClass(providerClassName, loader);
+ }
+ catch (ClassNotFoundException x)
+ {
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Provider class " + providerClassName + " not found, continuing with next package",null);
+ continue;
+ }
+ catch (Exception x)
+ {
+ RmiConnectorActivator.log(LogService.LOG_WARNING,"Cannot load provider class " + providerClassName, x);
+ throw new JMXProviderException("Cannot load provider class " + providerClassName, x);
+ }
+
+ try
+ {
+ return providerClass.newInstance();
+ }
+ catch (Exception x)
+ {
+ RmiConnectorActivator.log(LogService.LOG_WARNING,"Cannot instantiate provider class " + providerClassName, x);
+ throw new JMXProviderException("Cannot instantiate provider class " + providerClassName, x);
+ }
+ }
+
+ // Nothing found
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Could not find provider for protocol " + protocol + " in package list '" + packages + "'", null);
+ throw new MalformedURLException("Could not find provider for protocol " + protocol + " in package list '" + packages + "'");
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/ProviderHelper.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/ProviderHelper.java
new file mode 100644
index 0000000..ab27a96
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/ProviderHelper.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.osgi.service.log.LogService;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.RmiConnectorActivator;
+
+/**
+ *
+ * @version $Revision: 1.2 $
+ */
+public abstract class ProviderHelper
+{
+ protected static String normalizeProtocol(String protocol)
+ {
+ // Replace special chars as required by the spec
+ String normalized = protocol.replace('+', '.');
+ normalized = normalized.replace('-', '_');
+ RmiConnectorActivator.log(LogService.LOG_INFO, "Normalizing protocol: " + protocol + " --> " + normalized, null);
+ return normalized;
+ }
+
+ protected static String findSystemPackageList(final String key)
+ {
+ String providerPackages = (String)AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ return System.getProperty(key);
+ }
+ });
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Packages in the system property '" + key + "': " + providerPackages,null);
+ return providerPackages;
+ }
+
+ protected static Class loadClass(String className, ClassLoader loader) throws ClassNotFoundException
+ {
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Loading class: " + className +" From "+loader,null);
+ if (loader == null){
+ loader= ProviderHelper.class.getClassLoader();
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"a new loader "+loader,null);
+
+ //Thread.currentThread().getContextClassLoader();
+ }
+ return loader.loadClass(className);
+ }
+
+ protected static String constructClassName(String packageName, String protocol, String className)
+ {
+ return new StringBuffer(packageName).append(".").append(protocol).append(".").append(className).toString();
+ }
+
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/RemoteNotificationClientHandler.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/RemoteNotificationClientHandler.java
new file mode 100644
index 0000000..5e29b63
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/RemoteNotificationClientHandler.java
@@ -0,0 +1,400 @@
+
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.management.Notification;
+import javax.management.NotificationListener;
+import javax.management.NotificationFilter;
+import javax.management.remote.NotificationResult;
+import javax.management.remote.TargetedNotification;
+
+import org.osgi.service.log.LogService;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.RmiConnectorActivator;
+
+public class RemoteNotificationClientHandler {
+ private static int fetcherID;
+ private static int delivererID;
+
+ public interface NotificationHandler
+ {
+ public NotificationResult fetchNotifications(long sequenceNumber, int maxNumber, long timeout) throws IOException;
+ public void sendNotificationsLost(long howMany);
+ }
+
+ private final Map tuples = new HashMap();
+ private NotificationFetcherThread fetcherThread;
+ private NotificationDelivererThread delivererThread;
+
+ public RemoteNotificationClientHandler(NotificationHandler fetcher, Map environment)
+ {
+ this.fetcherThread = new NotificationFetcherThread(fetcher, environment);
+ this.delivererThread = new NotificationDelivererThread();
+ }
+
+ private boolean isActive()
+ {
+ return fetcherThread.isActive();
+ }
+
+ private void start()
+ {
+ delivererThread.start();
+ fetcherThread.start();
+ }
+
+ private void stop()
+ {
+ fetcherThread.stop();
+ delivererThread.stop();
+ }
+
+ private synchronized static int getFetcherID()
+ {
+ return ++fetcherID;
+ }
+
+ private synchronized static int getDelivererID()
+ {
+ return ++delivererID;
+ }
+
+ public boolean contains(NotificationTuple tuple)
+ {
+ synchronized (tuples)
+ {
+ return tuples.containsValue(tuple);
+ }
+ }
+
+ public void addNotificationListener(Integer id, NotificationTuple tuple)
+ {
+ if (!isActive()) start();
+
+ synchronized (tuples)
+ {
+ tuples.put(id, tuple);
+ }
+
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Adding remote NotificationListener " + tuple,null);
+ }
+
+ public Integer[] getNotificationListeners(NotificationTuple tuple)
+ {
+ synchronized (tuples)
+ {
+ ArrayList ids = new ArrayList();
+ for (Iterator i = tuples.entrySet().iterator(); i.hasNext();)
+ {
+ Map.Entry entry = (Map.Entry)i.next();
+ if (entry.getValue().equals(tuple)) ids.add(entry.getKey());
+ }
+ if (ids.size() > 0) return (Integer[])ids.toArray(new Integer[ids.size()]);
+ }
+ return null;
+ }
+
+ public Integer getNotificationListener(NotificationTuple tuple)
+ {
+ synchronized (tuples)
+ {
+ for (Iterator i = tuples.entrySet().iterator(); i.hasNext();)
+ {
+ Map.Entry entry = (Map.Entry)i.next();
+ if (entry.getValue().equals(tuple)) return (Integer)entry.getKey();
+ }
+ }
+ return null;
+ }
+
+ public void removeNotificationListeners(Integer[] ids)
+ {
+ NotificationTuple tuple = null;
+ boolean stop = false;
+ synchronized (tuples)
+ {
+ for (int i = 0; i < ids.length; ++i)
+ {
+ Integer id = ids[i];
+ tuple = (NotificationTuple)tuples.remove(id);
+ }
+ stop = tuples.size() == 0;
+ }
+ if (stop) stop();
+
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Removing remote NotificationListener " + tuple,null);
+ }
+
+ private void deliverNotifications(TargetedNotification[] notifications)
+ {
+ delivererThread.addNotifications(notifications);
+ }
+
+ private void sendNotification(TargetedNotification notification)
+ {
+ NotificationTuple tuple = null;
+ synchronized (tuples)
+ {
+ tuple = (NotificationTuple)tuples.get(notification.getListenerID());
+ }
+
+ // It may be possible that a notification arrived after the client already removed the listener
+ if (tuple == null) return;
+
+ Notification notif = notification.getNotification();
+
+
+ if (tuple.getInvokeFilter())
+ {
+ // Invoke the filter on client side
+ NotificationFilter filter = tuple.getNotificationFilter();
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Filtering notification " + notif + ", filter = " + filter,null);
+ if (filter != null)
+ {
+ try
+ {
+ boolean deliver = filter.isNotificationEnabled(notif);
+ if (!deliver) return;
+ }
+ catch (RuntimeException x)
+ {
+ RmiConnectorActivator.log(LogService.LOG_WARNING,"RuntimeException caught from isNotificationEnabled, filter = " + filter, x);
+ // And go on quietly
+ }
+ }
+ }
+
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Sending Notification " + notif + ", listener info is " + tuple,null);
+
+ NotificationListener listener = tuple.getNotificationListener();
+
+ try
+ {
+ listener.handleNotification(notif, tuple.getHandback());
+ }
+ catch (RuntimeException x)
+ {
+ RmiConnectorActivator.log(LogService.LOG_WARNING,"RuntimeException caught from handleNotification, listener = " + listener, x);
+ // And return quietly
+ }
+ }
+
+ private class NotificationFetcherThread implements Runnable {
+ private final NotificationHandler handler;
+ private long sequenceNumber;
+ private volatile boolean active;
+ private Thread thread;
+ private long timeout;
+ private int maxNumber;
+ private long sleep;
+
+ public NotificationFetcherThread(NotificationHandler fetcher, Map environment)
+ {
+ this.handler = fetcher;
+
+ // Default server timeout is one minute
+ timeout = 60 * 1000;
+ // At most 25 notifications at time
+ maxNumber = 25;
+ // By default we don't sleep and we call the server again.
+ sleep = 0;
+ if (environment != null)
+ {
+ try
+ {
+ timeout = ((Long)environment.get(MX4JRemoteConstants.FETCH_NOTIFICATIONS_TIMEOUT)).longValue();
+ }
+ catch (Exception ignored)
+ {
+ }
+ try
+ {
+ maxNumber = ((Integer)environment.get(MX4JRemoteConstants.FETCH_NOTIFICATIONS_MAX_NUMBER)).intValue();
+ }
+ catch (Exception ignored)
+ {
+ }
+ try
+ {
+ sleep = ((Integer)environment.get(MX4JRemoteConstants.FETCH_NOTIFICATIONS_SLEEP)).intValue();
+ }
+ catch (Exception ignored)
+ {
+ }
+ }
+ }
+
+ private synchronized long getSequenceNumber()
+ {
+ return sequenceNumber;
+ }
+
+ private synchronized void setSequenceNumber(long sequenceNumber)
+ {
+ this.sequenceNumber = sequenceNumber;
+ }
+
+ public boolean isActive()
+ {
+ return active;
+ }
+
+ public synchronized void start()
+ {
+ active = true;
+ // Initialized to a negative value for the first fetchNotification call
+ sequenceNumber = -1;
+ thread = new Thread(this, "Notification Fetcher #" + getFetcherID());
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+ public synchronized void stop()
+ {
+ active = false;
+ thread.interrupt();
+ }
+
+ public void run()
+ {
+
+ while (isActive() && !thread.isInterrupted())
+ {
+ try
+ {
+ long sequence = getSequenceNumber();
+ NotificationResult result = handler.fetchNotifications(sequence, maxNumber, timeout);
+ RmiConnectorActivator.log(LogService.LOG_WARNING,"Fetched Notifications: " + result, null);
+
+ long sleepTime = sleep;
+ if (result != null)
+ {
+ long nextSequence = result.getNextSequenceNumber();
+ TargetedNotification[] targeted = result.getTargetedNotifications();
+ int targetedLength = targeted == null ? 0 : targeted.length;
+ boolean notifsFilteredByServer = nextSequence - sequence != targetedLength;
+ boolean notifsLostByServer = sequence >= 0 && result.getEarliestSequenceNumber() > sequence;
+ if (notifsFilteredByServer)
+ {
+ // We lost some notification
+ handler.sendNotificationsLost(nextSequence - sequence - targetedLength);
+ }
+ if (notifsLostByServer)
+ {
+ // We lost some notification
+ handler.sendNotificationsLost(result.getEarliestSequenceNumber() - sequence);
+ }
+
+ setSequenceNumber(nextSequence);
+ deliverNotifications(targeted);
+
+ // If we got a maxNumber of notifications, probably the server has more to send, don't sleep
+ if (targeted != null && targeted.length == maxNumber) sleepTime = 0;
+ }
+
+ if (sleepTime > 0) Thread.sleep(sleepTime);
+ }
+ catch (IOException x)
+ {
+ RmiConnectorActivator.log(LogService.LOG_INFO,"Caught IOException from fetchNotifications", x);
+ // And try again
+ }
+ catch (InterruptedException x)
+ {
+ active = false;
+ Thread.currentThread().interrupt();
+ break;
+ }
+ }
+ }
+ }
+
+ private class NotificationDelivererThread implements Runnable
+ {
+ private final List notificationQueue = new LinkedList();
+ private volatile boolean active;
+ private Thread thread;
+
+ public void addNotifications(TargetedNotification[] notifications)
+ {
+ if (notifications == null || notifications.length == 0) return;
+
+ List notifs = Arrays.asList(notifications);
+
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Enqueuing notifications for delivery: " + notifs,null);
+
+ synchronized (notificationQueue)
+ {
+ notificationQueue.addAll(notifs);
+ notificationQueue.notifyAll();
+ }
+ }
+
+ public boolean isActive()
+ {
+ return active;
+ }
+
+ public synchronized void start()
+ {
+ active = true;
+ notificationQueue.clear();
+ thread = new Thread(this, "Notification Deliverer #" + getDelivererID());
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+ public synchronized void stop()
+ {
+ active = false;
+ thread.interrupt();
+ }
+
+ public void run()
+ {
+ while (isActive() && !thread.isInterrupted())
+ {
+ try
+ {
+ TargetedNotification notification = null;
+ synchronized (notificationQueue)
+ {
+ while (notificationQueue.isEmpty()) notificationQueue.wait();
+ notification = (TargetedNotification)notificationQueue.remove(0);
+ }
+ sendNotification(notification);
+ }
+ catch (InterruptedException x)
+ {
+ active = false;
+ Thread.currentThread().interrupt();
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/RemoteNotificationServerHandler.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/RemoteNotificationServerHandler.java
new file mode 100644
index 0000000..effff75
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/RemoteNotificationServerHandler.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote;
+
+import javax.management.ObjectName;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.remote.NotificationResult;
+
+/**
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public interface RemoteNotificationServerHandler
+{
+ public Integer generateListenerID(ObjectName name, NotificationFilter filter);
+
+ public NotificationListener getServerNotificationListener();
+
+ public void addNotificationListener(Integer id, NotificationTuple tuple);
+
+ public void removeNotificationListener(Integer id);
+
+ public NotificationTuple getNotificationListener(Integer id);
+
+ public NotificationResult fetchNotifications(long sequenceNumber, int maxNotifications, long timeout);
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/provider/RMIClientProvider.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/provider/RMIClientProvider.java
new file mode 100644
index 0000000..ec2e800
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/provider/RMIClientProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.provider;
+
+import java.util.Map;
+import java.io.IOException;
+
+import javax.management.remote.JMXConnectorProvider;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.rmi.RMIConnector;
+
+/**
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public abstract class RMIClientProvider implements JMXConnectorProvider
+{
+ public JMXConnector newJMXConnector(JMXServiceURL url, Map environment) throws IOException
+ {
+ return new RMIConnector(url, environment);
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/provider/RMIServerProvider.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/provider/RMIServerProvider.java
new file mode 100644
index 0000000..15d4d8a
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/provider/RMIServerProvider.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.provider;
+
+import java.util.Map;
+import java.io.IOException;
+
+import javax.management.remote.JMXConnectorServerProvider;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.rmi.RMIConnectorServer;
+import javax.management.MBeanServer;
+
+/**
+ *
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public abstract class RMIServerProvider implements JMXConnectorServerProvider
+{
+ public JMXConnectorServer newJMXConnectorServer(JMXServiceURL url, Map environment, MBeanServer server) throws IOException
+ {
+ return new RMIConnectorServer(url, environment, server);
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/provider/rmi/ServerProvider.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/provider/rmi/ServerProvider.java
new file mode 100644
index 0000000..d1a20cb
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/provider/rmi/ServerProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.provider.rmi;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.provider.RMIServerProvider;
+
+/**
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class ServerProvider extends RMIServerProvider
+{
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/resolver/rmi/RMIResolver.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/resolver/rmi/RMIResolver.java
new file mode 100644
index 0000000..cfcbbea
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/resolver/rmi/RMIResolver.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.resolver.rmi;
+
+import java.io.IOException;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ObjectInputStream;
+import java.net.MalformedURLException;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RMIServerSocketFactory;
+import java.rmi.Remote;
+import java.util.Hashtable;
+import java.util.Map;
+
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.rmi.RMIConnectorServer;
+import javax.management.remote.rmi.RMIJRMPServerImpl;
+import javax.management.remote.rmi.RMIServer;
+import javax.management.remote.rmi.RMIServerImpl;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.osgi.service.log.LogService;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.RmiConnectorActivator;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.ConnectionResolver;
+import org.apache.felix.mosgi.jmx.agent.mx4j.util.Base64Codec;
+
+/**
+ * Resolver for RMI/JRMP protocol.
+ *
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class RMIResolver extends ConnectionResolver
+{
+ private static final String JNDI_CONTEXT = "/jndi/";
+ private static final String STUB_CONTEXT = "/stub/";
+
+
+//********************************************************************************************************************//
+// CLIENT METHODS
+
+
+ public Object lookupClient(JMXServiceURL url, Map environment) throws IOException
+ {
+ return lookupRMIServerStub(url, environment);
+ }
+
+ public Object bindClient(Object client, Map environment) throws IOException
+ {
+ // JRMP does not need anything special
+ return client;
+ }
+
+ protected RMIServer lookupRMIServerStub(JMXServiceURL url, Map environment) throws IOException
+ {
+ String path = url.getURLPath();
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"JMXServiceURL for lookup is: '" + url + "'", null);
+
+ if (path != null)
+ {
+ if (path.startsWith(JNDI_CONTEXT))
+ {
+ return lookupStubInJNDI(url, environment);
+ }
+
+ return decodeStub(url, environment);
+ }
+
+ throw new MalformedURLException("Unsupported lookup " + url);
+ }
+
+ private RMIServer lookupStubInJNDI(JMXServiceURL url, Map environment) throws IOException
+ {
+
+ String path = url.getURLPath();
+ String name = path.substring(JNDI_CONTEXT.length());
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Looking up RMI stub in JNDI under name " + name, null);
+
+ InitialContext ctx = null;
+ try
+ {
+ ctx = new InitialContext(new Hashtable(environment));
+ Object stub = ctx.lookup(name);
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Found RMI stub in JNDI " + stub, null);
+ return narrowRMIServerStub(stub);
+ }
+ catch (NamingException x)
+ {
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Cannot lookup RMI stub in JNDI", x);
+ throw new IOException(x.toString());
+ }
+ finally
+ {
+ try
+ {
+ if (ctx != null) ctx.close();
+ }
+ catch (NamingException x)
+ {
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Cannot close InitialContext", x);
+ }
+ }
+ }
+
+ protected RMIServer narrowRMIServerStub(Object stub)
+ {
+ return (RMIServer)stub;
+ }
+
+ protected RMIServer decodeStub(JMXServiceURL url, Map environment) throws IOException
+ {
+ String path = url.getURLPath();
+ if (path.startsWith(STUB_CONTEXT))
+ {
+ byte[] encoded = path.substring(STUB_CONTEXT.length()).getBytes();
+ if (!Base64Codec.isArrayByteBase64(encoded)) throw new IOException("Encoded stub form is not a valid Base64 sequence: " + url);
+ byte[] decoded = Base64Codec.decodeBase64(encoded);
+ ByteArrayInputStream bais = new ByteArrayInputStream(decoded);
+ ObjectInputStream ois = null;
+ try
+ {
+ ois = new ObjectInputStream(bais);
+ return (RMIServer)ois.readObject();
+ }
+ catch (ClassNotFoundException x)
+ {
+ throw new IOException("Cannot decode stub from " + url + ": " + x);
+ }
+ finally
+ {
+ if (ois != null) ois.close();
+ }
+ }
+ throw new MalformedURLException("Unsupported binding: " + url);
+ }
+
+
+//********************************************************************************************************************//
+// SERVER METHODS
+
+
+ public Object createServer(JMXServiceURL url, Map environment) throws IOException
+ {
+ return createRMIServer(url, environment);
+ }
+
+ protected RMIServerImpl createRMIServer(JMXServiceURL url, Map environment) throws IOException
+ {
+ int port = url.getPort();
+ RMIClientSocketFactory clientFactory = (RMIClientSocketFactory)environment.get(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE);
+ RMIServerSocketFactory serverFactory = (RMIServerSocketFactory)environment.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE);
+ return new RMIJRMPServerImpl(port, clientFactory, serverFactory, environment);
+ }
+
+ public JMXServiceURL bindServer(Object server, JMXServiceURL url, Map environment) throws IOException
+ {
+ // See javax/management/remote/rmi/package-summary.html
+
+ RMIServerImpl rmiServer = (RMIServerImpl)server;
+
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"JMXServiceURL for binding is: '" + url + "'",null);
+
+ if (isEncodedForm(url))
+ {
+ String path = encodeStub(rmiServer, environment);
+ return new JMXServiceURL(url.getProtocol(), url.getHost(), url.getPort(), path);
+ }
+
+ String jndiURL = parseJNDIForm(url);
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"JMXServiceURL path for binding is: '" + jndiURL + "'", null);
+
+ InitialContext ctx = null;
+ try
+ {
+ ctx = new InitialContext(new Hashtable(environment));
+ boolean rebind = Boolean.valueOf((String)environment.get(RMIConnectorServer.JNDI_REBIND_ATTRIBUTE)).booleanValue();
+ if (rebind)
+ ctx.rebind(jndiURL, rmiServer.toStub());
+ else
+ ctx.bind(jndiURL, rmiServer.toStub());
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Bound " + rmiServer + " to " + jndiURL, null);
+ return url;
+ }
+ catch (NamingException x)
+ {
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Cannot bind server " + rmiServer + " to " + jndiURL, x);
+ throw new IOException(x.toString());
+ }
+ finally
+ {
+ try
+ {
+ if (ctx != null) ctx.close();
+ }
+ catch (NamingException x)
+ {
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Cannot close InitialContext", x);
+ }
+ }
+ }
+
+ protected String encodeStub(RMIServerImpl rmiServer, Map environment) throws IOException
+ {
+ Remote stub = rmiServer.toStub();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = null;
+ try
+ {
+ oos = new ObjectOutputStream(baos);
+ oos.writeObject(stub);
+ }
+ finally
+ {
+ if (oos != null) oos.close();
+ }
+ byte[] bytes = baos.toByteArray();
+ byte[] encoded = Base64Codec.encodeBase64(bytes);
+ // Since the bytes are base 64 bytes, the encoding in creating the string is not important: any will work
+ return STUB_CONTEXT + new String(encoded);
+ }
+
+ protected boolean isEncodedForm(JMXServiceURL url)
+ {
+ String path = url.getURLPath();
+ if (path == null || path.length() == 0 || path.equals("/") || path.startsWith(STUB_CONTEXT)) return true;
+ return false;
+ }
+
+ private String parseJNDIForm(JMXServiceURL url) throws MalformedURLException
+ {
+ String path = url.getURLPath();
+ if (path.startsWith(JNDI_CONTEXT))
+ {
+ String jndiURL = path.substring(JNDI_CONTEXT.length());
+ if (jndiURL == null || jndiURL.length() == 0) throw new MalformedURLException("No JNDI URL specified: " + url);
+ return jndiURL;
+ }
+ throw new MalformedURLException("Unsupported binding: " + url);
+ }
+
+ public void unbindServer(Object server, JMXServiceURL url, Map environment) throws IOException
+ {
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"JMXServiceURL for unbinding is: '" + url + "'", null);
+ // The server was not bound to JNDI (the stub was encoded), just return
+ if (isEncodedForm(url))
+ {
+ destroyServer(server, environment);
+ return;
+ }
+
+ String jndiURL = parseJNDIForm(url);
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"JMXServiceURL path for binding is: '" + jndiURL + "'",null);
+
+ InitialContext ctx = null;
+ try
+ {
+ ctx = new InitialContext(new Hashtable(environment));
+ ctx.unbind(jndiURL);
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Unbound " + server + " from " + jndiURL, null);
+ }
+ catch (NamingException x)
+ {
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Cannot unbind server " + server + " to " + jndiURL, x);
+ throw new IOException(x.toString());
+ }
+ finally
+ {
+ try
+ {
+ if (ctx != null) ctx.close();
+ }
+ catch (NamingException x)
+ {
+ RmiConnectorActivator.log(LogService.LOG_DEBUG,"Cannot close InitialContext", x);
+ }
+ }
+ }
+
+ protected void destroyServer(Object server, Map environment) throws IOException
+ {
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/ClientExceptionCatcher.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/ClientExceptionCatcher.java
new file mode 100644
index 0000000..d908b4c
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/ClientExceptionCatcher.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.rmi;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.rmi.NoSuchObjectException;
+import java.io.IOException;
+
+import javax.management.MBeanServerConnection;
+import javax.management.remote.JMXServerErrorException;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.ClientProxy;
+
+/**
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class ClientExceptionCatcher extends ClientProxy
+{
+ private ClientExceptionCatcher(MBeanServerConnection target)
+ {
+ super(target);
+ }
+
+ public static MBeanServerConnection newInstance(MBeanServerConnection target)
+ {
+ ClientExceptionCatcher handler = new ClientExceptionCatcher(target);
+ return (MBeanServerConnection)Proxy.newProxyInstance(handler.getClass().getClassLoader(), new Class[]{MBeanServerConnection.class}, handler);
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
+ {
+ try
+ {
+ return super.invoke(proxy, method, args);
+ }
+ catch (NoSuchObjectException x)
+ {
+ // The connection has been already closed by the server
+ throw new IOException("Connection closed by the server");
+ }
+ catch (Exception x)
+ {
+ throw x;
+ }
+ catch (Error x)
+ {
+ throw new JMXServerErrorException("Error thrown during invocation", x);
+ }
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/ClientInvoker.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/ClientInvoker.java
new file mode 100644
index 0000000..ae40401
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/ClientInvoker.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.rmi;
+
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.rmi.MarshalledObject;
+import java.util.Map;
+import java.util.Set;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.IntrospectionException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServerConnection;
+import javax.management.NotCompliantMBeanException;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import javax.management.QueryExp;
+import javax.management.ReflectionException;
+import javax.management.remote.rmi.RMIConnection;
+import javax.security.auth.Subject;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.NotificationTuple;
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.RemoteNotificationClientHandler;
+
+/**
+ * An MBeanServerConnection that "converts" the MBeanServerConnection calls to {@link RMIConnection} calls,
+ * performing wrapping of parameters and/or the needed actions.
+ *
+ * @see mx4j.remote.rmi.RMIConnectionInvoker
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Brian Scully</a>
+ * @version $Revision: 1.2 $
+ */
+public class ClientInvoker implements MBeanServerConnection
+{
+ private final RMIConnection connection;
+ private final Subject delegate;
+ private final RemoteNotificationClientHandler notificationHandler;
+
+ public ClientInvoker(RMIConnection rmiConnection, RemoteNotificationClientHandler notificationHandler, Subject delegate, Map environment)
+ {
+ this.connection = rmiConnection;
+ this.delegate = delegate;
+ this.notificationHandler = notificationHandler;
+ }
+
+ public void addNotificationListener(ObjectName observed, NotificationListener listener, NotificationFilter filter, Object handback)
+ throws InstanceNotFoundException, IOException
+ {
+ NotificationTuple tuple = new NotificationTuple(observed, listener, filter, handback);
+ if (notificationHandler.contains(tuple)) return;
+
+ MarshalledObject f = null;
+ try
+ {
+ f = RMIMarshaller.marshal(filter);
+ }
+ catch (NotSerializableException x)
+ {
+ // Invoke the filter on client side
+ tuple.setInvokeFilter(true);
+ }
+ Integer[] ids = connection.addNotificationListeners(new ObjectName[] {observed}, new MarshalledObject[] {f}, new Subject[] {delegate});
+ notificationHandler.addNotificationListener(ids[0], tuple);
+ }
+
+ public void removeNotificationListener(ObjectName observed, NotificationListener listener)
+ throws InstanceNotFoundException, ListenerNotFoundException, IOException
+ {
+ Integer[] ids = notificationHandler.getNotificationListeners(new NotificationTuple(observed, listener));
+ if (ids == null) throw new ListenerNotFoundException("Could not find listener " + listener);
+ connection.removeNotificationListeners(observed, ids, delegate);
+ notificationHandler.removeNotificationListeners(ids);
+ }
+
+ public void removeNotificationListener(ObjectName observed, NotificationListener listener, NotificationFilter filter, Object handback)
+ throws InstanceNotFoundException, ListenerNotFoundException, IOException
+ {
+ Integer id = notificationHandler.getNotificationListener(new NotificationTuple(observed, listener, filter, handback));
+ if (id == null) throw new ListenerNotFoundException("Could not find listener " + listener + " with filter " + filter + " and handback " + handback);
+ Integer[] ids = new Integer[] {id};
+ connection.removeNotificationListeners(observed, ids, delegate);
+ notificationHandler.removeNotificationListeners(ids);
+ }
+
+ public void addNotificationListener(ObjectName observed, ObjectName listener, NotificationFilter filter, Object handback)
+ throws InstanceNotFoundException, IOException
+ {
+ MarshalledObject f = RMIMarshaller.marshal(filter);
+ MarshalledObject h = RMIMarshaller.marshal(handback);
+ connection.addNotificationListener(observed, listener, f, h, delegate);
+ }
+
+ public void removeNotificationListener(ObjectName observed, ObjectName listener)
+ throws InstanceNotFoundException, ListenerNotFoundException, IOException
+ {
+ connection.removeNotificationListener(observed, listener, delegate);
+ }
+
+ public void removeNotificationListener(ObjectName observed, ObjectName listener, NotificationFilter filter, Object handback)
+ throws InstanceNotFoundException, ListenerNotFoundException, IOException
+ {
+ MarshalledObject f = RMIMarshaller.marshal(filter);
+ MarshalledObject h = RMIMarshaller.marshal(handback);
+ connection.removeNotificationListener(observed, listener, f, h, delegate);
+ }
+
+ public MBeanInfo getMBeanInfo(ObjectName objectName)
+ throws InstanceNotFoundException, IntrospectionException, ReflectionException, IOException
+ {
+ return connection.getMBeanInfo(objectName, delegate);
+ }
+
+ public boolean isInstanceOf(ObjectName objectName, String className)
+ throws InstanceNotFoundException, IOException
+ {
+ return connection.isInstanceOf(objectName, className, delegate);
+ }
+
+ public String[] getDomains()
+ throws IOException
+ {
+ return connection.getDomains(delegate);
+ }
+
+ public String getDefaultDomain()
+ throws IOException
+ {
+ return connection.getDefaultDomain(delegate);
+ }
+
+ public ObjectInstance createMBean(String className, ObjectName objectName)
+ throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, IOException
+ {
+ return connection.createMBean(className, objectName, delegate);
+ }
+
+ public ObjectInstance createMBean(String className, ObjectName objectName, Object[] args, String[] parameters)
+ throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, IOException
+ {
+ MarshalledObject arguments = RMIMarshaller.marshal(args);
+ return connection.createMBean(className, objectName, arguments, parameters, delegate);
+ }
+
+ public ObjectInstance createMBean(String className, ObjectName objectName, ObjectName loaderName)
+ throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, InstanceNotFoundException, IOException
+ {
+ return connection.createMBean(className, objectName, loaderName, delegate);
+ }
+
+ public ObjectInstance createMBean(String className, ObjectName objectName, ObjectName loaderName, Object[] args, String[] parameters)
+ throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, InstanceNotFoundException, IOException
+ {
+ MarshalledObject arguments = RMIMarshaller.marshal(args);
+ return connection.createMBean(className, objectName, loaderName, arguments, parameters, delegate);
+ }
+
+ public void unregisterMBean(ObjectName objectName)
+ throws InstanceNotFoundException, MBeanRegistrationException, IOException
+ {
+ connection.unregisterMBean(objectName, delegate);
+ }
+
+ public Object getAttribute(ObjectName objectName, String attribute)
+ throws MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException, IOException
+ {
+ return connection.getAttribute(objectName, attribute, delegate);
+ }
+
+ public void setAttribute(ObjectName objectName, Attribute attribute)
+ throws InstanceNotFoundException, AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException, IOException
+ {
+ MarshalledObject attrib = RMIMarshaller.marshal(attribute);
+ connection.setAttribute(objectName, attrib, delegate);
+ }
+
+ public AttributeList getAttributes(ObjectName objectName, String[] attributes)
+ throws InstanceNotFoundException, ReflectionException, IOException
+ {
+ return connection.getAttributes(objectName, attributes, delegate);
+ }
+
+ public AttributeList setAttributes(ObjectName objectName, AttributeList attributes)
+ throws InstanceNotFoundException, ReflectionException, IOException
+ {
+ MarshalledObject attribs = RMIMarshaller.marshal(attributes);
+ return connection.setAttributes(objectName, attribs, delegate);
+ }
+
+ public Object invoke(ObjectName objectName, String methodName, Object[] args, String[] parameters)
+ throws InstanceNotFoundException, MBeanException, ReflectionException, IOException
+ {
+ MarshalledObject arguments = RMIMarshaller.marshal(args);
+ return connection.invoke(objectName, methodName, arguments, parameters, delegate);
+ }
+
+ public Integer getMBeanCount()
+ throws IOException
+ {
+ return connection.getMBeanCount(delegate);
+ }
+
+ public boolean isRegistered(ObjectName objectName)
+ throws IOException
+ {
+ return connection.isRegistered(objectName, delegate);
+ }
+
+ public ObjectInstance getObjectInstance(ObjectName objectName)
+ throws InstanceNotFoundException, IOException
+ {
+ return connection.getObjectInstance(objectName, delegate);
+ }
+
+ public Set queryMBeans(ObjectName patternName, QueryExp filter)
+ throws IOException
+ {
+ MarshalledObject query = RMIMarshaller.marshal(filter);
+ return connection.queryMBeans(patternName, query, delegate);
+ }
+
+ public Set queryNames(ObjectName patternName, QueryExp filter)
+ throws IOException
+ {
+ MarshalledObject query = RMIMarshaller.marshal(filter);
+ return connection.queryNames(patternName, query, delegate);
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/ClientUnmarshaller.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/ClientUnmarshaller.java
new file mode 100644
index 0000000..c18ef3a
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/ClientUnmarshaller.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.rmi;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.management.MBeanServerConnection;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.ClientProxy;
+
+/**
+ * An MBeanServerConnection proxy that performs the setting of the appropriate context classloader
+ * to allow classloading of classes sent by the server but not known to the client, in methods like
+ * {@link MBeanServerConnection#getAttribute}, {@link MBeanServerConnection#invoke} and so on.
+ *
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class ClientUnmarshaller extends ClientProxy
+{
+ private final ClassLoader classLoader;
+
+ private ClientUnmarshaller(MBeanServerConnection target, ClassLoader loader)
+ {
+ super(target);
+ this.classLoader = loader;
+ }
+
+ public static MBeanServerConnection newInstance(MBeanServerConnection target, ClassLoader loader)
+ {
+ ClientUnmarshaller handler = new ClientUnmarshaller(target, loader);
+ return (MBeanServerConnection)Proxy.newProxyInstance(handler.getClass().getClassLoader(), new Class[]{MBeanServerConnection.class}, handler);
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
+ {
+ if (classLoader == null)
+ {
+ return chain(proxy, method, args);
+ }
+ else
+ {
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+ try
+ {
+ setContextClassLoader(classLoader);
+ return chain(proxy, method, args);
+ }
+ finally
+ {
+ setContextClassLoader(old);
+ }
+ }
+ }
+
+ private Object chain(Object proxy, Method method, Object[] args) throws Throwable
+ {
+ return super.invoke(proxy, method, args);
+ }
+
+ private void setContextClassLoader(final ClassLoader loader)
+ {
+ AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ Thread.currentThread().setContextClassLoader(loader);
+ return null;
+ }
+ });
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIConnectionInvoker.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIConnectionInvoker.java
new file mode 100644
index 0000000..d6f9ebd
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIConnectionInvoker.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.rmi;
+
+import java.io.IOException;
+import java.rmi.MarshalledObject;
+import java.security.SecureClassLoader;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Set;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.IntrospectionException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServer;
+import javax.management.NotCompliantMBeanException;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import javax.management.QueryExp;
+import javax.management.ReflectionException;
+import javax.management.loading.ClassLoaderRepository;
+import javax.management.remote.NotificationResult;
+import javax.management.remote.rmi.RMIConnection;
+import javax.security.auth.Subject;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.NotificationTuple;
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.RemoteNotificationServerHandler;
+
+/**
+ * An RMIConnection that "converts" remote calls to {@link MBeanServer} calls,
+ * performing unwrapping of parameters and/or the needed actions.
+ *
+ * @see mx4j.remote.rmi.ClientInvoker
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @author <a href="mailto:btscully@users.sourceforge.net">Brian Scully</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class RMIConnectionInvoker implements RMIConnection
+{
+ private final MBeanServer server;
+ private final ClassLoader defaultLoader;
+ private final RemoteNotificationServerHandler notificationHandler;
+
+ public RMIConnectionInvoker(MBeanServer server, ClassLoader defaultLoader, Map environment)
+ {
+ this.server = server;
+ this.defaultLoader = defaultLoader;
+ // TODO: here we hardcoded the handler for notifications. Maybe worth to make it pluggable ?
+ this.notificationHandler = new RMIRemoteNotificationServerHandler(environment);
+ }
+
+ public String getConnectionId() throws IOException
+ {
+ throw new Error("getConnectionId() must not be propagated along the invocation chain");
+ }
+
+ public void close() throws IOException
+ {
+ throw new Error("close() must not be propagated along the invocation chain");
+ }
+
+ public ObjectInstance createMBean(String className, ObjectName name, Subject delegate)
+ throws ReflectionException,
+ InstanceAlreadyExistsException,
+ MBeanRegistrationException,
+ MBeanException,
+ NotCompliantMBeanException,
+ IOException
+ {
+ return server.createMBean(className, name);
+ }
+
+ public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName, Subject delegate)
+ throws ReflectionException,
+ InstanceAlreadyExistsException,
+ MBeanRegistrationException,
+ MBeanException,
+ NotCompliantMBeanException,
+ InstanceNotFoundException,
+ IOException
+ {
+ return server.createMBean(className, name, loaderName);
+ }
+
+ public ObjectInstance createMBean(String className, ObjectName name, MarshalledObject params, String[] signature, Subject delegate)
+ throws ReflectionException,
+ InstanceAlreadyExistsException,
+ MBeanRegistrationException,
+ MBeanException,
+ NotCompliantMBeanException,
+ IOException
+ {
+ Object[] args = (Object[])RMIMarshaller.unmarshal(params, new RepositoryClassLoader(server.getClassLoaderRepository()), defaultLoader);
+ return server.createMBean(className, name, args, signature);
+ }
+
+ public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName, MarshalledObject params, String[] signature, Subject delegate)
+ throws ReflectionException,
+ InstanceAlreadyExistsException,
+ MBeanRegistrationException,
+ MBeanException,
+ NotCompliantMBeanException,
+ InstanceNotFoundException,
+ IOException
+ {
+ Object[] args = (Object[])RMIMarshaller.unmarshal(params, server.getClassLoader(loaderName), defaultLoader);
+ return server.createMBean(className, name, loaderName, args, signature);
+ }
+
+ public void unregisterMBean(ObjectName name, Subject delegate) throws InstanceNotFoundException, MBeanRegistrationException, IOException
+ {
+ server.unregisterMBean(name);
+ }
+
+ public ObjectInstance getObjectInstance(ObjectName name, Subject delegate) throws InstanceNotFoundException, IOException
+ {
+ return server.getObjectInstance(name);
+ }
+
+ public Set queryMBeans(ObjectName name, MarshalledObject query, Subject delegate) throws IOException
+ {
+ QueryExp filter = (QueryExp)RMIMarshaller.unmarshal(query, null, defaultLoader);
+ return server.queryMBeans(name, filter);
+ }
+
+ public Set queryNames(ObjectName name, MarshalledObject query, Subject delegate) throws IOException
+ {
+ QueryExp filter = (QueryExp)RMIMarshaller.unmarshal(query, null, defaultLoader);
+ return server.queryNames(name, filter);
+ }
+
+ public boolean isRegistered(ObjectName name, Subject delegate) throws IOException
+ {
+ return server.isRegistered(name);
+ }
+
+ public Integer getMBeanCount(Subject delegate) throws IOException
+ {
+ return server.getMBeanCount();
+ }
+
+ public Object getAttribute(ObjectName name, String attribute, Subject delegate)
+ throws MBeanException,
+ AttributeNotFoundException,
+ InstanceNotFoundException,
+ ReflectionException,
+ IOException
+ {
+ return server.getAttribute(name, attribute);
+ }
+
+ public AttributeList getAttributes(ObjectName name, String[] attributes, Subject delegate)
+ throws InstanceNotFoundException, ReflectionException, IOException
+ {
+ return server.getAttributes(name, attributes);
+ }
+
+ public void setAttribute(ObjectName name, MarshalledObject attribute, Subject delegate)
+ throws InstanceNotFoundException,
+ AttributeNotFoundException,
+ InvalidAttributeValueException,
+ MBeanException,
+ ReflectionException,
+ IOException
+ {
+ Attribute attrib = (Attribute)RMIMarshaller.unmarshal(attribute, server.getClassLoaderFor(name), defaultLoader);
+ server.setAttribute(name, attrib);
+ }
+
+ public AttributeList setAttributes(ObjectName name, MarshalledObject attributes, Subject delegate)
+ throws InstanceNotFoundException,
+ ReflectionException,
+ IOException
+ {
+ AttributeList attribs = (AttributeList)RMIMarshaller.unmarshal(attributes, server.getClassLoaderFor(name), defaultLoader);
+ return server.setAttributes(name, attribs);
+ }
+
+ public Object invoke(ObjectName name, String operationName, MarshalledObject params, String[] signature, Subject delegate)
+ throws InstanceNotFoundException,
+ MBeanException,
+ ReflectionException,
+ IOException
+ {
+ Object[] args = (Object[])RMIMarshaller.unmarshal(params, server.getClassLoaderFor(name), defaultLoader);
+ return server.invoke(name, operationName, args, signature);
+ }
+
+ public String getDefaultDomain(Subject delegate) throws IOException
+ {
+ return server.getDefaultDomain();
+ }
+
+ public String[] getDomains(Subject delegate) throws IOException
+ {
+ return server.getDomains();
+ }
+
+ public MBeanInfo getMBeanInfo(ObjectName name, Subject delegate) throws InstanceNotFoundException, IntrospectionException, ReflectionException, IOException
+ {
+ return server.getMBeanInfo(name);
+ }
+
+ public boolean isInstanceOf(ObjectName name, String className, Subject delegate) throws InstanceNotFoundException, IOException
+ {
+ return server.isInstanceOf(name, className);
+ }
+
+ public void addNotificationListener(ObjectName name, ObjectName listener, MarshalledObject filter, MarshalledObject handback, Subject delegate)
+ throws InstanceNotFoundException, IOException
+ {
+ NotificationFilter f = (NotificationFilter)RMIMarshaller.unmarshal(filter, server.getClassLoaderFor(name), defaultLoader);
+ Object h = RMIMarshaller.unmarshal(handback, server.getClassLoaderFor(name), defaultLoader);
+ server.addNotificationListener(name, listener, f, h);
+ }
+
+ public void removeNotificationListener(ObjectName name, ObjectName listener, Subject delegate)
+ throws InstanceNotFoundException, ListenerNotFoundException, IOException
+ {
+ server.removeNotificationListener(name, listener);
+ }
+
+ public void removeNotificationListener(ObjectName name, ObjectName listener, MarshalledObject filter, MarshalledObject handback, Subject delegate)
+ throws InstanceNotFoundException, ListenerNotFoundException, IOException
+ {
+ NotificationFilter f = (NotificationFilter)RMIMarshaller.unmarshal(filter, server.getClassLoaderFor(name), defaultLoader);
+ Object h = RMIMarshaller.unmarshal(handback, server.getClassLoaderFor(name), defaultLoader);
+ server.removeNotificationListener(name, listener, f, h);
+ }
+
+ public Integer[] addNotificationListeners(ObjectName[] names, MarshalledObject[] filters, Subject[] delegates) throws InstanceNotFoundException, IOException
+ {
+ ArrayList ids = new ArrayList();
+ for (int i = 0; i < names.length; ++i)
+ {
+ ObjectName name = names[i];
+ MarshalledObject filter = filters[i];
+ NotificationFilter f = (NotificationFilter)RMIMarshaller.unmarshal(filter, server.getClassLoaderFor(name), defaultLoader);
+ Integer id = notificationHandler.generateListenerID(name, f);
+ NotificationListener listener = notificationHandler.getServerNotificationListener();
+ server.addNotificationListener(name, listener, f, id);
+ notificationHandler.addNotificationListener(id, new NotificationTuple(name, listener, f, id));
+ ids.add(id);
+ }
+ return (Integer[])ids.toArray(new Integer[ids.size()]);
+ }
+
+ public void removeNotificationListeners(ObjectName name, Integer[] listenerIDs, Subject delegate) throws InstanceNotFoundException, ListenerNotFoundException, IOException
+ {
+ for (int i = 0; i < listenerIDs.length; ++i)
+ {
+ Integer id = listenerIDs[i];
+ NotificationTuple tuple = notificationHandler.getNotificationListener(id);
+ server.removeNotificationListener(name, tuple.getNotificationListener(), tuple.getNotificationFilter(), tuple.getHandback());
+ notificationHandler.removeNotificationListener(id);
+ }
+ }
+
+ public NotificationResult fetchNotifications(long clientSequenceNumber, int maxNotifications, long timeout) throws IOException
+ {
+ return notificationHandler.fetchNotifications(clientSequenceNumber, maxNotifications, timeout);
+ }
+
+ private static class RepositoryClassLoader extends SecureClassLoader
+ {
+ private final ClassLoaderRepository repository;
+
+ private RepositoryClassLoader(ClassLoaderRepository repository)
+ {
+ this.repository = repository;
+ }
+
+ public Class loadClass(String name) throws ClassNotFoundException
+ {
+ return repository.loadClass(name);
+ }
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIConnectionProxy.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIConnectionProxy.java
new file mode 100644
index 0000000..a130876
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIConnectionProxy.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.rmi;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.management.remote.rmi.RMIConnection;
+
+/**
+ * Base class for RMIConnection dynamic proxies.
+ *
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class RMIConnectionProxy implements InvocationHandler
+{
+ private RMIConnection nested;
+
+ protected RMIConnectionProxy(RMIConnection nested)
+ {
+ this.nested = nested;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
+ {
+ try
+ {
+ return method.invoke(nested, args);
+ }
+ catch (InvocationTargetException x)
+ {
+ throw x.getTargetException();
+ }
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIConnectionSubjectInvoker.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIConnectionSubjectInvoker.java
new file mode 100644
index 0000000..d4d85f9
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIConnectionSubjectInvoker.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.rmi;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.rmi.MarshalledObject;
+import java.security.PrivilegedExceptionAction;
+import java.security.AccessControlContext;
+import java.util.ArrayList;
+
+import javax.management.ObjectName;
+import javax.management.remote.JMXServerErrorException;
+import javax.management.remote.rmi.RMIConnection;
+import javax.security.auth.Subject;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.MX4JRemoteUtils;
+
+/**
+ * An RMIConnection proxy that wraps the call into a {@link Subject#doAsPrivileged} invocation,
+ * in order to execute the code under subject-based security, and to perform subject delegation.
+ *
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class RMIConnectionSubjectInvoker extends RMIConnectionProxy
+{
+ public static RMIConnection newInstance(RMIConnection nested, Subject subject, AccessControlContext context)
+ {
+ RMIConnectionSubjectInvoker handler = new RMIConnectionSubjectInvoker(nested, subject, context);
+ return (RMIConnection)Proxy.newProxyInstance(handler.getClass().getClassLoader(), new Class[] {RMIConnection.class}, handler);
+ }
+
+ private final Subject subject;
+ private final AccessControlContext context;
+
+ private RMIConnectionSubjectInvoker(RMIConnection nested, Subject subject, AccessControlContext context)
+ {
+ super(nested);
+ this.subject = subject;
+ this.context = context;
+ }
+
+ public Object invoke(final Object proxy, final Method method, final Object[] args)
+ throws Throwable
+ {
+ String methodName = method.getName();
+ if ("fetchNotifications".equals(methodName)) return chain(proxy, method, args);
+
+ if ("addNotificationListeners".equals(methodName))
+ {
+ Subject[] delegates = (Subject[])args[args.length - 1];
+ if (delegates == null || delegates.length == 0) return chain(proxy, method, args);
+
+ if (delegates.length == 1) return subjectInvoke(proxy, method, args, delegates[0]);
+
+ ArrayList ids = new ArrayList();
+ for (int i = 0; i < delegates.length; ++i)
+ {
+ ObjectName name = ((ObjectName[])args[0])[i];
+ MarshalledObject filter = ((MarshalledObject[])args[1])[i];
+ Subject delegate = delegates[i];
+ Object[] newArgs = new Object[] {new ObjectName[] {name}, new MarshalledObject[] {filter}, new Subject[] {delegate}};
+ Integer id = ((Integer[])subjectInvoke(proxy, method, newArgs, delegate))[0];
+ ids.add(id);
+ }
+ return (Integer[])ids.toArray(new Integer[ids.size()]);
+ }
+
+ // For all other methods, the subject is always the last argument
+ Subject delegate = (Subject)args[args.length - 1];
+
+ return subjectInvoke(proxy, method, args, delegate);
+ }
+
+ private Object subjectInvoke(final Object proxy, final Method method, final Object[] args, Subject delegate) throws Exception
+ {
+ return MX4JRemoteUtils.subjectInvoke(subject, delegate, context, new PrivilegedExceptionAction()
+ {
+ public Object run() throws Exception
+ {
+ return chain(proxy, method, args);
+ }
+ });
+ }
+
+ private Object chain(Object proxy, Method method, Object[] args) throws Exception
+ {
+ try
+ {
+ return super.invoke(proxy, method, args);
+ }
+ catch (Throwable x)
+ {
+ if (x instanceof Exception) throw (Exception)x;
+ throw new JMXServerErrorException("Error thrown during invocation", (Error)x);
+ }
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIHeartBeat.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIHeartBeat.java
new file mode 100644
index 0000000..9d539ad
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIHeartBeat.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.rmi;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.management.remote.rmi.RMIConnection;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.AbstractHeartBeat;
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.ConnectionNotificationEmitter;
+
+/**
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class RMIHeartBeat extends AbstractHeartBeat
+{
+ private final RMIConnection connection;
+
+ public RMIHeartBeat(RMIConnection connection, ConnectionNotificationEmitter emitter, Map environment)
+ {
+ super(emitter, environment);
+ this.connection = connection;
+ }
+
+ protected void pulse() throws IOException
+ {
+ connection.getDefaultDomain(null);
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIMarshaller.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIMarshaller.java
new file mode 100644
index 0000000..7cbf5b1
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIMarshaller.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) MX4J.
+ * All rights reserved.
+ *
+ * This software is distributed under the terms of the MX4J License version 1.0.
+ * See the terms of the MX4J License in the documentation provided with this software.
+ */
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.rmi;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.rmi.MarshalledObject;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+
+/**
+ * Marshaller/Unmarshaller for RMI's MarshalledObjects.
+ *
+ * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+class RMIMarshaller
+{
+ private static Method unmarshal;
+
+ /**
+ * MarshalledObject.get() loads the object it contains by using the first user-defined classloader it can find
+ * in the stack frames of the call.
+ * In a normal usage of JSR 160, this classloader is the one that loaded this class, most probably
+ * the system classloader.
+ * If the class cannot be found with that loader, then the RMI semantic is tried: first the thread context
+ * classloader, then dynamic code download (if there is a security manager).
+ * Here we load the Marshaller class using an URLClassLoader that is only able to load classes from the URL
+ * where it loaded this class, thus it cannot see other classes in the system classloader.
+ * This URLClassLoader then becomes the first user-defined classloader in the stack frames, but it will fail
+ * to load anything else, thus allowing MarshalledObject.get() to use the thread context classloader.
+ */
+ static
+ {
+ try
+ {
+ AccessController.doPrivileged(new PrivilegedExceptionAction()
+ {
+ public Object run() throws Exception
+ {
+ URL url = RMIMarshaller.class.getProtectionDomain().getCodeSource().getLocation();
+ // TODO: is it enough to use the parent, or maybe better use null as parent classloader ?
+ URLClassLoader loader = new URLClassLoader(new URL[] {url}, RMIMarshaller.class.getClassLoader().getParent());
+ Class marshaller = loader.loadClass(Marshaller.class.getName());
+ unmarshal = marshaller.getMethod("unmarshal", new Class[] {MarshalledObject.class});
+ return null;
+ }
+ });
+ }
+ catch (PrivilegedActionException x)
+ {
+ throw new Error(x.toString());
+ }
+ }
+
+ /**
+ * Returns a MarshalledObject obtained by marshalling the given object.
+ */
+ public static MarshalledObject marshal(Object object) throws IOException
+ {
+ if (object == null) return null;
+ return new MarshalledObject(object);
+ }
+
+ /**
+ * Returns the unmarshalled object obtained unmarshalling the given MarshalledObject,
+ * using as context classloader first the given mbeanLoader, if not null, then with the given defaultLoader.
+ */
+ public static Object unmarshal(MarshalledObject object, ClassLoader mbeanLoader, ClassLoader defaultLoader) throws IOException
+ {
+ if (object == null) return null;
+ if (mbeanLoader == null) return unmarshal(object, defaultLoader);
+ return unmarshal(object, new MarshallerClassLoader(mbeanLoader, defaultLoader));
+ }
+
+ private static Object unmarshal(MarshalledObject object, ClassLoader loader) throws IOException
+ {
+ if (loader != null)
+ {
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+ try
+ {
+ setContextClassLoader(loader);
+ return unmarshal(object);
+ }
+ catch (IOException x)
+ {
+ throw x;
+ }
+ catch (ClassNotFoundException ignored)
+ {
+ }
+ finally
+ {
+ setContextClassLoader(old);
+ }
+ }
+ throw new IOException("Cannot unmarshal " + object);
+ }
+
+ private static Object unmarshal(MarshalledObject marshalled) throws IOException, ClassNotFoundException
+ {
+ try
+ {
+ return unmarshal.invoke(null, new Object[]{marshalled});
+ }
+ catch (InvocationTargetException x)
+ {
+ Throwable t = x.getTargetException();
+ if (t instanceof IOException) throw (IOException)t;
+ if (t instanceof ClassNotFoundException) throw (ClassNotFoundException)t;
+ throw new IOException(t.toString());
+ }
+ catch (Exception x)
+ {
+ throw new IOException(x.toString());
+ }
+ }
+
+ private static void setContextClassLoader(final ClassLoader loader)
+ {
+ AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ Thread.currentThread().setContextClassLoader(loader);
+ return null;
+ }
+ });
+ }
+
+ public static class Marshaller
+ {
+ public static Object unmarshal(MarshalledObject obj) throws IOException, ClassNotFoundException
+ {
+ return obj.get();
+ }
+ }
+
+ private static class MarshallerClassLoader extends ClassLoader
+ {
+ private final ClassLoader defaultLoader;
+
+ private MarshallerClassLoader(ClassLoader mbeanLoader, ClassLoader defaultLoader)
+ {
+ super(mbeanLoader);
+ this.defaultLoader = defaultLoader;
+ }
+
+ protected Class findClass(String name) throws ClassNotFoundException
+ {
+ return defaultLoader.loadClass(name);
+ }
+
+ protected URL findResource(String name)
+ {
+ return defaultLoader.getResource(name);
+ }
+ }
+}
diff --git a/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIRemoteNotificationServerHandler.java b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIRemoteNotificationServerHandler.java
new file mode 100644
index 0000000..8d68bbc
--- /dev/null
+++ b/org.apache.felix.mosgi.jmx.rmiconnector/src/main/java/org/apache/felix/mosgi/jmx/rmiconnector/mx4j/remote/rmi/RMIRemoteNotificationServerHandler.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.rmi;
+
+import java.util.Map;
+import java.util.ArrayList;
+
+import javax.management.remote.TargetedNotification;
+
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.DefaultRemoteNotificationServerHandler;
+import org.apache.felix.mosgi.jmx.rmiconnector.mx4j.remote.MX4JRemoteUtils;
+
+import org.osgi.service.log.LogService;
+import org.apache.felix.mosgi.jmx.rmiconnector.RmiConnectorActivator;
+
+/**
+ *
+ * @version $Revision: 1.1.1.1 $
+ */
+class RMIRemoteNotificationServerHandler extends DefaultRemoteNotificationServerHandler
+{
+ RMIRemoteNotificationServerHandler(Map environment)
+ {
+ super(environment);
+ }
+
+ protected TargetedNotification[] filterNotifications(TargetedNotification[] notifications)
+ {
+ ArrayList list = new ArrayList();
+ for (int i = 0; i < notifications.length; ++i)
+ {
+ TargetedNotification notification = notifications[i];
+ if (MX4JRemoteUtils.isTrulySerializable(notification))
+ {
+ list.add(notification);
+ }
+ else
+ {
+ RmiConnectorActivator.log(LogService.LOG_INFO,"Cannot send notification " + notification + " to the client: it is not serializable", null);
+ }
+ }
+ return (TargetedNotification[])list.toArray(new TargetedNotification[list.size()]);
+ }
+}