MonitorMBean.java

/**
 * Copyright 2013 Netflix, Inc.
 * <p/>
 * 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
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * 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 com.netflix.servo.jmx;

import com.netflix.servo.monitor.CompositeMonitor;
import com.netflix.servo.monitor.Monitor;
import com.netflix.servo.monitor.NumericMonitor;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import java.util.ArrayList;
import java.util.List;

/**
 * Exposes a {@link com.netflix.servo.monitor.Monitor} as an MBean that can be registered with JMX.
 */
class MonitorMBean implements DynamicMBean {

  /**
   * Create a set of MBeans for a {@link com.netflix.servo.monitor.Monitor}. This method will
   * recursively select all of the sub-monitors if a composite type is used.
   *
   * @param domain  passed in to the object name created to identify the beans
   * @param monitor monitor to expose to jmx
   * @param mapper  the mapper which maps the Monitor to ObjectName
   * @return flattened list of simple monitor mbeans
   */
  public static List<MonitorMBean> createMBeans(String domain, Monitor<?> monitor,
                                                ObjectNameMapper mapper) {
    List<MonitorMBean> mbeans = new ArrayList<>();
    createMBeans(mbeans, domain, monitor, mapper);
    return mbeans;
  }

  private static void createMBeans(List<MonitorMBean> mbeans, String domain, Monitor<?> monitor,
                                   ObjectNameMapper mapper) {
    if (monitor instanceof CompositeMonitor<?>) {
      for (Monitor<?> m : ((CompositeMonitor<?>) monitor).getMonitors()) {
        createMBeans(mbeans, domain, m, mapper);
      }
    } else {
      mbeans.add(new MonitorMBean(domain, monitor, mapper));
    }
  }

  private final Monitor<?> monitor;

  private final ObjectName objectName;

  private final MBeanInfo beanInfo;

  /**
   * Create an MBean for a {@link com.netflix.servo.monitor.Monitor}.
   *
   * @param domain  passed in to the object name created to identify the beans
   * @param monitor monitor to expose to jmx
   * @param mapper  the mapper which maps the monitor to ObjectName
   */
  MonitorMBean(String domain, Monitor<?> monitor, ObjectNameMapper mapper) {
    this.monitor = monitor;
    this.objectName = createObjectName(mapper, domain);
    this.beanInfo = createBeanInfo();
  }

  /**
   * Returns the object name built from the {@link com.netflix.servo.monitor.MonitorConfig}.
   */
  public ObjectName getObjectName() {
    return objectName;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Object getAttribute(String name) throws AttributeNotFoundException {
    return monitor.getValue();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setAttribute(Attribute attribute)
      throws InvalidAttributeValueException, MBeanException, AttributeNotFoundException {
    throw new UnsupportedOperationException("setAttribute is not implemented");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public AttributeList getAttributes(String[] names) {
    AttributeList list = new AttributeList();
    for (String name : names) {
      list.add(new Attribute(name, monitor.getValue()));
    }
    return list;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public AttributeList setAttributes(AttributeList list) {
    throw new UnsupportedOperationException("setAttributes is not implemented");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Object invoke(String name, Object[] args, String[] sig)
      throws MBeanException, ReflectionException {
    throw new UnsupportedOperationException("invoke is not implemented");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public MBeanInfo getMBeanInfo() {
    return beanInfo;
  }

  private ObjectName createObjectName(ObjectNameMapper mapper, String domain) {
    return mapper.createObjectName(domain, monitor);
  }

  private MBeanInfo createBeanInfo() {
    MBeanAttributeInfo[] attrs = new MBeanAttributeInfo[1];
    attrs[0] = createAttributeInfo(monitor);
    return new MBeanInfo(
        this.getClass().getName(),
        "MonitorMBean",
        attrs,
        null,  // constructors
        null,  // operators
        null); // notifications
  }

  private MBeanAttributeInfo createAttributeInfo(Monitor<?> m) {
    final String type = (m instanceof NumericMonitor<?>)
        ? Number.class.getName()
        : String.class.getName();
    return new MBeanAttributeInfo(
        "value",
        type,
        m.getConfig().toString(),
        true,   // isReadable
        false,  // isWritable
        false); // isIs
  }
}