/*******************************************************************************
* Copyright © 2012-2015 eBay Software Foundation
* This program is dual licensed under the MIT and Apache 2.0 licenses.
* Please see LICENSE for more information.
*******************************************************************************/
/**
*
*/
package com.ebay.jetstream.management;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.jmx.export.annotation.ManagedResource;
import com.ebay.jetstream.util.CommonUtils;
/**
*
*
*/
public class Management {
public static class BeanFolder extends ConcurrentHashMap<String, Object> {
private static final long serialVersionUID = 1L;
protected BeanFolder() {
}
}
private static Map<String, Class<? extends ManagedResourceFormatter>> s_resourceFormatters = new HashMap<String, Class<? extends ManagedResourceFormatter>>();
private static List<ManagementListener> s_listeners = new ArrayList<ManagementListener>();
private static final String PATH_SEPARATOR = "/";
private static final String PATH_RELATIVE = "./";
private static BeanFolder s_directory = new BeanFolder();
/**
* Adds a managed bean to the directory. The full path of the bean is taken from the ManagedResource annotation for
* the bean.
*
* @param bean
* the bean to manage.
* @return the path to the bean.
*/
public static String addBean(Object bean) {
return addBean(null, bean);
}
/**
* Adds a managed bean to the directory. Missing path nodes are created as necessary. An IllegalArgumentException is
* thrown if the path is illegal. The path given is combined with any path from a ManagedResource annotation. If the
* annotation has an objectName prefixed with "./", the annotated path is added to the end of the given path, else it
* is added to the beginning of the given path.
*
* @param path
* the path to the managed bean, separated by '/' characters, with the bean name at the path end, or null.
* If null, the path is taken completely from the ManagedResource annotation for the bean.
*
* @param bean
* the bean to manage.
*
* @return the path to the bean.
*
* @throws IllegalArgumentException
* if the path already exists or includes a non-leaf managed bean.
*/
public static String addBean(String path, Object bean) {
String rpath = getResourcePath(path, bean);
String[] pc = rpath.split(PATH_SEPARATOR);
Object object = getPath(pc, 1);
if (!(object instanceof BeanFolder))
throw new IllegalArgumentException("bean already exists at " + rpath);
((BeanFolder) object).put(pc[pc.length - 1], bean);
notifyListeners(bean, rpath, ManagementListener.BeanAddedEvent.class);
return rpath;
}
/**
* Gets a managed bean or bean folder. An exception is thrown if the path is illegal or does not exist.
*
* @param path
* the path to the Bean or BeanFolder.
*
* @return the Bean or BeanFolder described by the given path.
*/
public static Object getBeanOrFolder(String path) {
return CommonUtils.isEmptyTrimmed(path) ? s_directory : getPath(path.split(PATH_SEPARATOR), 0);
}
public static List<ManagementListener> getManagementListeners() {
return Collections.unmodifiableList(s_listeners);
}
/**
* Gets a new instance of a ManagedResourceFormatter for the given format.
*
* @param format
* the string format name.
*
* @return the instance of ManagedResourceFormatter for the given format.
*
* @throws InstantiationException
* @throws IllegalAccessException
*/
public static ManagedResourceFormatter getResourceFormatter(String format) throws InstantiationException,
IllegalAccessException {
Class<? extends ManagedResourceFormatter> clazz = s_resourceFormatters.get(format);
return clazz == null ? null : clazz.newInstance();
}
public static Set<String> getResourceFormatters() {
return s_resourceFormatters.keySet();
}
public static void registerManagementListener(ManagementListener listener) {
s_listeners.add(listener);
}
/**
* Registers a ManagedResourceFormatter to a format name.
*
* @param format
* the string format name to register for.
* @param formatterClass
* the resource formatter class that implements ManagedResourceFormatter. The class must define a default
* (no parameters) constructor.
*/
public static void registerResourceFormatter(String format, Class<? extends ManagedResourceFormatter> formatterClass) {
s_resourceFormatters.put(format, formatterClass);
}
/**
* Removes a bean or tree from the directory.
*
* @param path
* the path to a bean or BeanFolder
* @return true iff anything was removed
*/
public static boolean removeBeanOrFolder(String path) {
return removePath(path.split(PATH_SEPARATOR), s_directory, 0) > 0;
}
/**
* Removes a bean or tree from the directory.
*
* @param path
* the path to a bean or folder, or null. If null, the full path to the bean is calculated from the
* ManagedResource annotation for the bean.
* @param bean
* the bean to remove
* @return true iff anything was removed
*/
public static boolean removeBeanOrFolder(String path, Object bean) {
return removeBeanOrFolder(getResourcePath(path, bean));
}
private static String checkLevel(String pathLevel) {
if (pathLevel == null || pathLevel.length() == 0)
throw new IllegalArgumentException("empty path level found");
return pathLevel;
}
/**
* Gets the path to BeanFolder or managed bean, and optionally adds missing path components to the directory.
*
* @param path
* the path to the managed bean, including the managed bean name.
* @param mode
* N <= 0: no add and limit traversal to Nth from end; N > 0: add to the folder to Nth from end.
* @return the target of the path if found, or the containing folder (mode = 1).
* @throws IllegalArgumentException
* if the path has a managed bean that is not at the path end, or if part of the path is missing (mode ==
* -1).
*/
private static Object getPath(String[] path, int mode) {
BeanFolder folder = s_directory;
Object object = null;
// traverse the path
int istart = 0;
for (int limit = path.length - (mode > 0 ? 0 : mode); istart < limit; istart++) {
object = folder.get(checkLevel(path[istart]));
if (object instanceof BeanFolder)
// this level is a folder, go to next (or end)
folder = (BeanFolder) object;
else if (object == null) {
if (mode <= 0)
// missing a level
throw new IllegalArgumentException("no beans exist at " + path[istart]);
else {
// mode > 0, create missing levels
for (object = folder, limit = path.length - mode; istart < limit; istart++) {
folder = (BeanFolder) object;
folder.put(checkLevel(path[istart]), object = new BeanFolder());
}
break;
}
}
// object not a folder, and not null
else if (istart != path.length - 1)
// found a bean in the middle of the path
throw new IllegalArgumentException("expecting BeanFolder, found managed bean at " + path[istart]);
}
return object;
}
private static String getResourcePath(String path, Object bean) {
String rpath = path;
ManagedResource mr = bean.getClass().getAnnotation(ManagedResource.class);
if (mr != null) {
String xpath = mr.objectName();
if (xpath == null || xpath.length() == 0)
xpath = mr.value();
if (xpath != null && xpath.length() > 0) {
rpath = path == null ? "" : xpath.startsWith(PATH_RELATIVE) ? path + PATH_SEPARATOR : PATH_SEPARATOR + path;
rpath = xpath.startsWith(PATH_RELATIVE) ? rpath + xpath.substring(PATH_RELATIVE.length()) : xpath + rpath;
}
}
return rpath;
}
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value="REC_CATCH_EXCEPTION", justification="Want to suprress exceptions here.")
private static void notifyListeners(Object bean, String path,
Class<? extends ManagementListener.BeanChangedEvent> eventClass) {
if (!s_listeners.isEmpty()) {
ManagementListener.BeanChangedEvent event = null;
try {
event = eventClass.getConstructor(Object.class, String.class).newInstance(bean, path);
for (ManagementListener listener : s_listeners)
try {
listener.beanChanged(event);
}
catch (Exception e) {
}
}
catch (Exception e) {
}
}
}
private static int removePath(String[] path, BeanFolder folder, int index) {
String s = checkLevel(path[index]);
Object object = folder.get(s);
int result = 0;
// recurse to sub-folders
if (object instanceof BeanFolder && index < path.length - 1) {
result = removePath(path, (BeanFolder) object, index + 1);
}
// remove path leaf and folders above that becomes empty due to this
if (object != null && (result == path.length - index && folder.size() == 1 || index == path.length - 1)) {
folder.remove(s);
if (result++ == 0)
notifyListeners(object, s, ManagementListener.BeanRemovedEvent.class);
}
return result;
}
private Management() {
}
}