/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.brooklyn.entity.software.base.test.jmx;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.MBeanServerInvocationHandler;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationEmitter;
import javax.management.ObjectName;
import javax.management.StandardEmitterMBean;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import mx4j.tools.naming.NamingService;
import mx4j.tools.naming.NamingServiceMBean;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.entity.java.UsesJmx;
import org.apache.brooklyn.feed.jmx.JmxHelper;
import org.apache.brooklyn.test.NetworkingTestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
/**
* Set up a JMX service ready for clients to connect. This consists of an MBean server, a connector server and a naming
* service.
*/
public class JmxService {
private static final Logger logger = LoggerFactory.getLogger(JmxService.class);
private MBeanServer server;
private NamingServiceMBean namingServiceMBean;
private JMXConnectorServer connectorServer;
private String jmxHost;
private int jmxPort;
private String url;
public JmxService() throws Exception {
this("localhost", NetworkingTestUtils.randomPortAround(28000));
// TODO why this message if the constructor is not actually deprecated, and it seems useful?
//logger.warn("use of deprecated default host and port in JmxService");
}
/**
* @deprecated since 0.6.0; either needs abandoning, or updating to support JmxSupport (and JmxmpAgent, etc) */
public JmxService(Entity e) throws Exception {
this(e.getAttribute(Attributes.HOSTNAME) != null ? e.getAttribute(Attributes.HOSTNAME) : "localhost",
e.getAttribute(UsesJmx.JMX_PORT) != null ? e.getAttribute(UsesJmx.JMX_PORT) : null);
}
public JmxService(String jmxHost, Integer jmxPort) throws Exception {
this.jmxHost = jmxHost;
Preconditions.checkNotNull(jmxPort, "JMX_PORT must be set when starting JmxService");
this.jmxPort = jmxPort;
url = JmxHelper.toRmiJmxUrl(jmxHost, jmxPort, jmxPort, "jmxrmi");
try {
JMXServiceURL address = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + jmxHost + ":" + jmxPort + "/jmxrmi");
connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(address, null, null);
server = MBeanServerFactory.createMBeanServer();
ObjectName cntorServerName = ObjectName.getInstance("connectors:protocol=rmi");
server.registerMBean(connectorServer, cntorServerName);
ObjectName naming = new ObjectName("Naming:type=registry");
server.registerMBean(new NamingService(jmxPort), naming);
Object proxy = MBeanServerInvocationHandler.newProxyInstance(server, naming, NamingServiceMBean.class, false);
namingServiceMBean = (NamingServiceMBean) proxy;
try {
namingServiceMBean.start();
} catch (Exception e) {
// may take a bit of time for port to be available, if it had just been used
logger.warn("JmxService couldn't start test mbean ("+e+"); will delay then retry once");
Thread.sleep(1000);
namingServiceMBean.start();
}
connectorServer.start();
logger.info("JMX tester service started at URL {}", address);
} catch (Exception e) {
try {
shutdown();
} catch (Exception e2) {
logger.warn("Error shutting down JmxService, after error during startup; rethrowing original error", e2);
}
throw e;
}
}
public int getJmxPort() {
return jmxPort;
}
public void shutdown() throws IOException {
if (connectorServer != null) connectorServer.stop();
if (namingServiceMBean != null) namingServiceMBean.stop();
if (server != null) MBeanServerFactory.releaseMBeanServer(server);
connectorServer = null;
namingServiceMBean = null;
server = null;
logger.info("JMX tester service stopped ({}:{})", jmxHost, jmxPort);
}
public String getUrl() {
return url;
}
public GeneralisedDynamicMBean registerMBean(String name) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException, NullPointerException {
return registerMBean(ImmutableMap.of(), ImmutableMap.of(), name);
}
/**
* Construct a {@link GeneralisedDynamicMBean} and register it with this MBean server.
*
* @param initialAttributes a {@link Map} of attributes that make up the MBean's initial set of attributes and their * values
* @param name the name of the MBean
* @return the newly created and registered MBean
* @throws NullPointerException
* @throws MalformedObjectNameException
* @throws NotCompliantMBeanException
* @throws MBeanRegistrationException
* @throws InstanceAlreadyExistsException
*/
@SuppressWarnings({ "rawtypes" })
public GeneralisedDynamicMBean registerMBean(Map initialAttributes, String name) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException, NullPointerException {
return registerMBean(initialAttributes, ImmutableMap.of(), name);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public GeneralisedDynamicMBean registerMBean(Map initialAttributes, Map operations, String name) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException, NullPointerException {
GeneralisedDynamicMBean mbean = new GeneralisedDynamicMBean(initialAttributes, operations);
server.registerMBean(mbean, new ObjectName(name));
return mbean;
}
public StandardEmitterMBean registerMBean(List<String> notifications, String name) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException, NullPointerException {
String[] types = (String[]) notifications.toArray(new String[0]);
MBeanNotificationInfo info = new MBeanNotificationInfo(types, Notification.class.getName(), "Notification");
NotificationEmitter emitter = new NotificationBroadcasterSupport(info);
StandardEmitterMBean mbean = new StandardEmitterMBean(emitter, NotificationEmitter.class, emitter);
server.registerMBean(mbean, new ObjectName(name));
return mbean;
}
}