org.eclipse.gyrex.monitoring.metrics.BaseMetric.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.gyrex.monitoring.metrics.BaseMetric.java

Source

/*******************************************************************************
 * Copyright (c) 2008, 2013 Gunnar Wagenknecht and others.
 * All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html.
 *
 * Contributors:
 *     Gunnar Wagenknecht - initial API and implementation
 *******************************************************************************/
package org.eclipse.gyrex.monitoring.metrics;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.commons.lang.CharSet;
import org.apache.commons.lang.StringUtils;

/**
 * Base class for a metric.
 * <p>
 * Every metric can be identified by an {@link #getId() id}. It's recommended to
 * follow Java package naming for ids.
 * </p>
 * <p>
 * Typically, metrics are thread safe in terms of concurrent updates. However,
 * retrieving all metric values in one atomic operation is not supported.
 * </p>
 * <p>
 * Note, although this class is marked <strong>abstract</strong> it is not
 * allowed to be subclassed outside the monitoring framework.
 * </p>
 * 
 * @noextend This class is not intended to be subclassed by clients.
 */
public abstract class BaseMetric {

    /**
     * Factory for creating metrics.
     * 
     * @param <T>
     *            the metric type
     * @noextend This interface is not intended to be extended by clients.
     * @noimplement This interface is not intended to be implemented by clients.
     * @since 1.2
     */
    public static interface MetricFactory<T extends BaseMetric> {
        T create(String id);
    }

    /** char set of allowed id chars */
    static final CharSet ALLOWED_ID_CHARS = CharSet
            .getInstance(new String[] { "a-z", "A-Z", "0-9", ".", "-", "_" });

    /** helper constant */
    static final String[] NO_METRICS = new String[0];

    /** common date format */
    static final DateFormat ISO_8601_UTC = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");

    /**
     * Indicates if the specified id is a valid metric id.
     * <p>
     * By definition, all identifiers used within Metrics APIs must not be
     * <code>null</code> or the empty string and may only contain the following
     * printable ASCII characters.
     * <ul>
     * <li>lower- and uppercase letters <code>a..z</code> and <code>A..Z</code></li>
     * <li>numbers <code>0..9</code></li>
     * <li><code>'.'</code></li>
     * <li><code>'-'</code></li>
     * <li><code>'_'</code></li>
     * </ul>
     * </p>
     * <p>
     * This method is used to validate identifiers within the Gyrex API. Clients
     * may call it to verify user entered ids.
     * </p>
     * 
     * @param id
     *            the id
     * @return <code>true</code> if the id is valid, <code>false</code>
     *         otherwise
     */
    public static boolean isValidId(final String id) {
        // not null or blank
        if (StringUtils.isBlank(id))
            return false;

        // scan for invalid chars
        for (final char c : id.toCharArray()) {
            if (!ALLOWED_ID_CHARS.contains(c))
                return false;
        }

        return true;
    }

    /** the id */
    private final String id;

    /** a lock for protecting against concurrent set and reset */
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    /** the last reset timestamp */
    private volatile long statsSince;

    /**
     * Creates a new metric using the specified id.
     * 
     * @param id
     *            the metric id (must be valid according to
     *            {@link #isValidId(String)})
     */
    BaseMetric(final String id) {
        if (!isValidId(id))
            throw new IllegalArgumentException("id is invalid (see BaseMetric#isValidId): " + id);
        this.id = id;

        // note, we do not invoke resetStats here because calling non-private
        // methods during object initialization is problematic at best
        statsSince = System.currentTimeMillis();
    }

    /**
     * Hook for subclasses to overwrite when statistics need to be resetted.
     * <p>
     * At the time this method is invoked, the current thread has acquired the
     * {@link #getWriteLock() write lock} already. Subclasses must
     * <strong>not</strong> modify the write lock.
     * </p>
     * <p>
     * The default implementation does nothing. Subclasses my extend.
     * </p>
     * <p>
     * Note, this method is called by {@link #resetStats()} and should not be
     * invoked directly.
     * </p>
     * 
     * @noreference This method is not intended to be referenced by clients.
     */
    void doResetStats() {
        // empty
    }

    /**
     * Returns a text info of the metric for use by {@link #toString()}.
     * <p>
     * Note, this operation is not atomic.
     * </p>
     * 
     * @return a text info of the metric
     * @noreference This method is not intended to be referenced by clients.
     */
    Object[] dumpMetrics() {
        return NO_METRICS;
    }

    /**
     * Returns a list of attributes contained in the metric.
     * <p>
     * Although public this method must not be called by clients. The framework
     * uses this method to obtain further information about a metric for
     * processing purposes.
     * </p>
     * 
     * @return an unmodifiable collection of metric attributes
     * @noreference This method is not intended to be referenced by clients.
     */
    public final List<MetricAttribute> getAttributes() {
        final List<MetricAttribute> attributes = new ArrayList<MetricAttribute>();
        populateAttributes(attributes);
        return Collections.unmodifiableList(attributes);
    }

    /**
     * Returns a map of attribute values contained in the metric.
     * <p>
     * Although public this method must not be called by clients. The framework
     * uses this method to obtain further information about a metric for
     * processing purposes.
     * </p>
     * 
     * @return an unmodifiable map of metric attribute values
     * @noreference This method is not intended to be referenced by clients.
     */
    public final Map<String, ?> getAttributeValues() {
        final Map<String, Object> attributeValues = new HashMap<String, Object>();
        final Lock lock = getReadLock();
        lock.lock();
        try {
            populateAttributeValues(attributeValues);
        } finally {
            lock.unlock();
        }
        return Collections.unmodifiableMap(attributeValues);
    }

    /**
     * Returns the metric id.
     * <p>
     * The metric id is used to uniquely identify a metric with the system. It's
     * generally a good practise to start the id with a scope identifier (eg., a
     * bundle symbolic name).
     * </p>
     * 
     * @return the metric id
     */
    public final String getId() {
        return id;
    }

    /**
     * Returns a lock which can be used in combination with the
     * {@link #getWriteLock() write lock} to protect against unsafe reads.
     * 
     * @return a lock for write operations
     */
    final Lock getReadLock() {
        return readWriteLock.readLock();
    }

    /**
     * Returns the time since the last {@link #resetStats() statistics reset}.
     * <p>
     * Note the date string is an <a
     * href="http://en.wikipedia.org/wiki/ISO_8601">ISO 8601 UTC</a> string of
     * the form <code>[YYYY][MM][DD]T[hh][mm][ss]Z</code>.
     * </p>
     * 
     * @return the the time since the last statistics reset
     */
    public final String getStatsSince() {
        return ISO_8601_UTC.format(new Date(statsSince));
    }

    /**
     * Returns the time stamp since the last {@link #resetStats() statistics
     * reset}.
     * <p>
     * Note this is the raw time stamp in milliseconds analog to
     * {@link System#currentTimeMillis()}.
     * </p>
     * 
     * @return the the time since the last statistics reset
     */
    protected final long getStatsSinceTS() {
        return statsSince;
    }

    /**
     * Returns a lock which should be used to protect against concurrent set and
     * reset operations.
     * 
     * @return a lock for write operations
     */
    final Lock getWriteLock() {
        return readWriteLock.writeLock();
    }

    /**
     * Populates the specified map with metric attribute information for use by
     * {@link #getAttributes()}.
     * <p>
     * Subclasses should override and add the attributes they defined. They must
     * call <code>super</code> in order to also populate the attributes defined
     * in the super class.
     * </p>
     * <p>
     * Note, this method is called by {@link #getAttributes()} and should not be
     * invoked directly.
     * </p>
     * 
     * @param attributes
     *            the list to populate with the attributes
     * @noreference This method is not intended to be referenced by clients.
     */
    void populateAttributes(final List<MetricAttribute> attributes) {
        attributes.add(new MetricAttribute("statsSince", "the last reset time", String.class));
    }

    /**
     * Populates the specified map with metric attribute values for use by
     * {@link #getAttributeValues()}.
     * <p>
     * Subclasses should override and add values of attributes they defined.
     * They must call <code>super</code> in order to also populate the
     * attributes defined in the super class.
     * </p>
     * <p>
     * At the time this method is invoked, the current thread has acquired the
     * {@link #getReadLock() read lock} already. Subclasses must
     * <strong>not</strong> modify the read lock.
     * </p>
     * <p>
     * Note, this method is called by {@link #getAttributeValues()} and should
     * not be invoked directly.
     * </p>
     * 
     * @param values
     *            the map to populate with the attribute values
     * @noreference This method is not intended to be referenced by clients.
     */
    void populateAttributeValues(final Map<String, Object> values) {
        values.put("statsSince", getStatsSince());
    }

    /**
     * Resets the metric statistics but not the metric values.
     */
    public final void resetStats() {
        final Lock writeLock = getWriteLock();
        writeLock.lock();
        try {
            statsSince = System.currentTimeMillis();
            doResetStats();
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * Returns a string containing a concise, human-readable description of the
     * metric.
     * 
     * @return a string representation of the metric
     */
    @Override
    public String toString() {
        final StringBuilder toString = new StringBuilder();
        toString.append(getClass().getSimpleName()).append('(').append(getId()).append(')');
        toString.append(" {");
        final Object[] metrics = dumpMetrics();
        for (int i = 0; i < metrics.length; i++) {
            if (i > 0) {
                toString.append(',');
            }
            final Object metric = metrics[i];
            if (null != metric) {
                toString.append(metric);
            }
        }
        toString.append('}');
        return toString.toString();
    }

}