MBean.java

package com.github.mk23.jmxproxy.core;

import com.github.mk23.jmxproxy.util.History;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializable;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;

import java.io.IOException;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * <p>JMX MBean tracker and serializer.</p>
 *
 * Maintains a map of JMX {@link Attribute} names and a {@link History} of
 * their values.  Implements JsonSerializable interface to convert the
 * stored map into JSON.
 *
 * @see <a href="https://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/JsonSerializable.html">com.fasterxml.jackson.databind.JsonSerializable</a>
 *
 * @since   2018-02-08
 * @author  mk23
 * @version 3.4.0
 */
public class MBean implements JsonSerializable {
    private final int historySize;

    private final Map<String, History<Attribute>> attributes;
    private final ThreadLocal<Integer> limit = new ThreadLocal<Integer>() {
        @Override protected Integer initialValue() {
            return -1;
        }
    };

    /**
     * <p>Default constructor.</p>
     *
     * Creates a map of {@link Attribute} name to {@link History} of associated values.
     */
    public MBean() {
        this.historySize = 1;
        attributes = new HashMap<String, History<Attribute>>();
    }

    /**
     * <p>Default constructor.</p>
     *
     * Creates a map of {@link Attribute} name to {@link History} of associated values.
     *
     * @param historySize number of items to preserve in the associated {@link History}.
     */
    public MBean(final int historySize) {
        this.historySize = historySize;
        attributes = new HashMap<String, History<Attribute>>();
    }

    /**
     * <p>Inserts a new Attribute name to History association.</p>
     *
     * Optionally creates a new {@link History} object and inserts into the map store associating
     * it to the specified attribute name. Appends the specified {@link Attribute} value to the
     * {@link History}.
     *
     * @param attributeName name of the {@link Attribute} used as the map key.
     * @param attributeObject value of the {@link Attribute} added to history.
     */
    public final void addAttribute(final String attributeName, final Object attributeObject) {
        if (!attributes.containsKey(attributeName)) {
            attributes.put(attributeName, new History<Attribute>(historySize));
        }

        attributes.get(attributeName).add(new Attribute(attributeObject));
    }

    /**
     * <p>Sets the thread local history request limit.</p>
     *
     * Set the thread local limit for all {@link History} when serializing to JSON.
     * Because this method returns its object, requesting serialization can be done
     * with a single statement.
     *
     * <p>For example</p>
     *
     * <code>return Response.ok(mbean.setLimit(5)).build();</code>
     *
     * @see <a href="http://docs.oracle.com/javaee/7/api/javax/ws/rs/core/Response.html">javax.ws.rs.core.Response</a>
     *
     * @param bound the number of items to retreive from {@link History} for this thread.
     *
     * @return this mbean object for chaining calls.
     */
    public final MBean setLimit(final Integer bound) {
        limit.set(bound);
        return this;
    }

    /**
     * <p>Tester for attribute name.</p>
     *
     * Checks for requested {@link Attribute} name in the map store.
     *
     * @param attribute name of the {@link Attribute} to look up in the map store.
     *
     * @return true if the {@link Attribute} exists in the map store, false otherwise.
     */
    public final boolean hasAttribute(final String attribute) {
        return attributes.containsKey(attribute);
    }

    /**
     * <p>Getter for attribute names.</p>
     *
     * Extracts and returns the unique {@link Set} of all currently stored {@link Attribute} names.
     *
     * @return {@link Set} of {@link Attribute} name {@link String}s.
     */
    public final Set<String> getAttributes() {
        return attributes.keySet();
    }

    /**
     * <p>Getter for most recent attribute.</p>
     *
     * Fetches the most recent {@link Attribute} value for the specified name from the
     * associated {@link History} in the map store.
     *
     * @param attribute name of the {@link Attribute} to look up in the map store.
     *
     * @return latest {@link Attribute} object from {@link History} if found, null otherwise.
     */
    public final Attribute getAttribute(final String attribute) {
        if (hasAttribute(attribute)) {
            return attributes.get(attribute).getLast();
        }

        return null;
    }

    /**
     * <p>Getter for a subset of historical attribute values.</p>
     *
     * Fetches an array, limited by the requested bound, of most recent {@link Attribute} values
     * for the specified name from the associated {@link History} in the map store.
     *
     * @param attribute name of the {@link Attribute} to look up in the map store.
     * @param bound size of the resulting array or full history if this exceeds capacity or less than 1.
     *
     * @return {@link List} of the latest {@link Attribute} objects from {@link History} if found, empty otherwise.
     */
    public final List<Attribute> getAttributes(final String attribute, final int bound) {
        if (hasAttribute(attribute)) {
            return attributes.get(attribute).get(bound);
        }

        return Collections.emptyList();
    }

    /** {@inheritDoc} */
    @Override
    public final void serialize(
        final JsonGenerator jgen,
        final SerializerProvider sp
    ) throws IOException, JsonProcessingException {
        serializeWithType(jgen, sp, null);
    }

    /** {@inheritDoc} */
    @Override
    public final void serializeWithType(
        final JsonGenerator jgen,
        final SerializerProvider sp,
        final TypeSerializer ts
    ) throws IOException, JsonProcessingException {
        buildJson(jgen);
    }

    private void buildJson(final JsonGenerator jgen) throws IOException, JsonProcessingException {
        int bound = limit.get();

        jgen.writeStartObject();
        for (Map.Entry<String, History<Attribute>> attributeEntry : attributes.entrySet()) {
            if (bound < 0) {
                jgen.writeObjectField(attributeEntry.getKey(), attributeEntry.getValue().getLast());
            } else {
                jgen.writeObjectField(attributeEntry.getKey(), attributeEntry.getValue().get(bound));
            }
        }
        jgen.writeEndObject();
    }
}