/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002-2006
* Sleepycat Software. All rights reserved.
*
* $Id: JEMBeanHelper.java,v 1.9 2006/01/03 21:55:47 bostic Exp $
*/
package com.sleepycat.je.jmx;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import com.sleepycat.je.CheckpointConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DatabaseStats;
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.EnvironmentMutableConfig;
import com.sleepycat.je.StatsConfig;
/**
* JEMBeanHelper is a utility class for the MBean implementation which wants to
* add management of a JE environment to its capabilities. MBean
* implementations can contain a JEMBeanHelper instance to get MBean metadata
* for JE and to set attributes, get attributes, and invoke operations.
* <p>
* com.sleepycat.je.jmx.JEMonitor and
* the example program jmx.JEApplicationMBean are two MBean implementations
* which provide support different application use cases. See those classes for
* examples of how to use JEMBeanHelper.
*/
public class JEMBeanHelper {
/*
* A note to JE developers: all available JE attributes and operations are
* described in the following static info arrays. New management
* functionality can be added to the helper by adding to the appropriate
* set of static definitions. For example, if we want to add a new JE
* attribute called "foo", which is available for open environments, we
* need to define a new MBeanAttributeInfo in the OPEN_ATTR array. The
* helper then needs to provide an implementation in set/getAttribute.
*/
/* --------------------- Attributes -------------------------- */
/* Attribute names. */
public static final String ATT_ENV_HOME = "environmentHome";
public static final String ATT_OPEN = "isOpen";
public static final String ATT_IS_READ_ONLY = "isReadOnly";
public static final String ATT_IS_TRANSACTIONAL = "isTransactional";
public static final String ATT_CACHE_SIZE = "cacheSize";
public static final String ATT_CACHE_PERCENT = "cachePercent";
public static final String ATT_LOCK_TIMEOUT = "lockTimeout";
public static final String ATT_IS_SERIALIZABLE = "isSerializableIsolation";
public static final String ATT_TXN_TIMEOUT = "transactionTimeout";
public static final String ATT_SET_READ_ONLY = "openReadOnly";
public static final String ATT_SET_TRANSACTIONAL = "openTransactional";
public static final String ATT_SET_SERIALIZABLE =
"openSerializableIsolation";
/* COMMON_ATTR attributes are available for any environment. */
private static final MBeanAttributeInfo [] COMMON_ATTR = {
new MBeanAttributeInfo(ATT_ENV_HOME,
"java.lang.String",
"Environment home directory.",
true, // readable
false, // writable
false), // isIs
new MBeanAttributeInfo(ATT_OPEN,
"java.lang.Boolean",
"True if this environment is open.",
true, // readable
false, // writable
true) // isIs
};
/* OPEN_ATTR attributes are available for all open environments. */
private static final MBeanAttributeInfo [] OPEN_ATTR = {
new MBeanAttributeInfo(ATT_IS_READ_ONLY,
"java.lang.Boolean",
"True if this environment is read only.",
true, // readable
false, // writable
true), // isIs
new MBeanAttributeInfo(ATT_IS_TRANSACTIONAL,
"java.lang.Boolean",
"True if this environment supports transactions.",
true, // readable
false, // writable
true), // isIs
new MBeanAttributeInfo(ATT_CACHE_SIZE,
"java.lang.Long",
"Cache size, in bytes.",
true, // readable
true, // writable
false), // isIs
new MBeanAttributeInfo(ATT_CACHE_PERCENT,
"java.lang.Integer",
"By default, cache size is (cachePercent * " +
"JVM maximum memory. To change the cache size "+
"using a percentage of the heap size, set " +
"the cache size to 0 and cachePercent to the "+
"desired percentage value.",
true, // readable
true, // writable
false), // isIs
new MBeanAttributeInfo(ATT_LOCK_TIMEOUT,
"java.lang.Long",
"Lock timeout, in microseconds.",
true, // readable
false, // writable
false), // isIs
};
/*
* TRANSACTIONAL_ATTR attributes are available only for open, transactional
* environments.
*/
private static final MBeanAttributeInfo [] TRANSACTIONAL_ATTR = {
new MBeanAttributeInfo(ATT_IS_SERIALIZABLE,
"java.lang.Boolean",
"True if this environment provides " +
"Serializable (degree 3) isolation. The " +
"default is RepeatableRead isolation.",
true, // readable
false, // writable
true), // isIs
new MBeanAttributeInfo(ATT_TXN_TIMEOUT,
"java.lang.Long",
"Transaction timeout, in seconds. A value " +
"of 0 means there is no timeout.",
true, // readable
false, // writable
false) // isIs
};
/*
* CREATE_ATTR attributes are available when the mbean is configured to
* support configuration and opening by the mbean. They express the
* configuration settings.
*/
private static final MBeanAttributeInfo [] CREATE_ATTR = {
new MBeanAttributeInfo(ATT_SET_READ_ONLY,
"java.lang.Boolean",
"True if this environment should be opened " +
"in readonly mode.",
true, // readable
true, // writable
false), // isIs
new MBeanAttributeInfo(ATT_SET_TRANSACTIONAL,
"java.lang.Boolean",
"True if this environment should be opened " +
"in transactional mode.",
true, // readable
true, // writable
false), // isIs
new MBeanAttributeInfo(ATT_SET_SERIALIZABLE,
"java.lang.Boolean",
"True if this environment should be opened " +
"with serializableIsolation. The default is "+
"false.",
true, // readable
true, // writable
false), // isIs
};
/* --------------------- Operations -------------------------- */
/* Operation names */
static final String OP_CLEAN = "cleanLog";
static final String OP_EVICT = "evictMemory";
static final String OP_CHECKPOINT = "checkpoint";
static final String OP_SYNC = "sync";
static final String OP_ENV_STAT = "getEnvironmentStats";
static final String OP_LOCK_STAT = "getLockStats";
static final String OP_TXN_STAT = "getTxnStats";
static final String OP_DB_NAMES = "getDatabaseNames";
static final String OP_DB_STAT = "getDatabaseStats";
private static final MBeanOperationInfo OP_CLEAN_INFO =
new MBeanOperationInfo(OP_CLEAN,
"Remove obsolete environment log files. " +
"Zero or more log files will be cleaned as " +
"necessary to bring the disk space " +
"utilization of the environment above the " +
"configured minimum utilization threshold " +
"as determined by the setting " +
"je.cleaner.minUtilization. Returns the " +
"number of files cleaned, that will be " +
"deleted at the next qualifying checkpoint.",
new MBeanParameterInfo[0], // no params
"java.lang.Integer",
MBeanOperationInfo.UNKNOWN);
private static final MBeanOperationInfo OP_EVICT_INFO =
new MBeanOperationInfo(OP_EVICT,
"Reduce cache usage to the threshold " +
"determined by the setting " +
"je.evictor.useMemoryFloor. ",
new MBeanParameterInfo[0], // no params
"void",
MBeanOperationInfo.UNKNOWN);
/* parameter for checkpoint operation. */
private static final MBeanParameterInfo [] checkpointParams = {
new MBeanParameterInfo ("force", "java.lang.Boolean",
"If true, force a checkpoint even if " +
"there has been no activity since the last " +
"checkpoint. Returns true if a checkpoint " +
"executed.")
};
private static final MBeanOperationInfo OP_CHECKPOINT_INFO =
new MBeanOperationInfo(OP_CHECKPOINT,
"Checkpoint the environment.",
checkpointParams,
"void",
MBeanOperationInfo.UNKNOWN);
private static final MBeanOperationInfo OP_SYNC_INFO =
new MBeanOperationInfo(OP_SYNC,
"Flush the environment to stable storage.",
new MBeanParameterInfo[0], // no params
"void",
MBeanOperationInfo.UNKNOWN);
private static final MBeanParameterInfo [] statParams = {
new MBeanParameterInfo ("clear", "java.lang.Boolean",
"If true, reset statistics after reading."),
new MBeanParameterInfo ("fast", "java.lang.Boolean",
"If true, only return statistics which do " +
"not require expensive computation.")
};
private static final MBeanOperationInfo OP_ENV_STAT_INFO =
new MBeanOperationInfo(OP_ENV_STAT,
"Get environment statistics.",
statParams,
"com.sleepycat.je.EnvironmentStats",
MBeanOperationInfo.INFO);
private static final MBeanOperationInfo OP_LOCK_STAT_INFO =
new MBeanOperationInfo(OP_LOCK_STAT,
"Get locking statistics.",
statParams,
"com.sleepycat.je.LockStats",
MBeanOperationInfo.INFO);
private static final MBeanOperationInfo OP_TXN_STAT_INFO =
new MBeanOperationInfo(OP_TXN_STAT,
"Get transactional statistics.",
statParams,
"com.sleepycat.je.TransactionStats",
MBeanOperationInfo.INFO);
private static final MBeanOperationInfo OP_DB_NAMES_INFO =
new MBeanOperationInfo(OP_DB_NAMES,
"Get the names of databases in the environment.",
new MBeanParameterInfo[0], // no params
"java.util.ArrayList",
MBeanOperationInfo.INFO);
private static final MBeanParameterInfo [] dbStatParams = {
new MBeanParameterInfo ("clear", "java.lang.Boolean",
"If true, reset statistics after reading."),
new MBeanParameterInfo ("fast", "java.lang.Boolean",
"If true, only return statistics which do " +
"not require expensive computation. " +
"Currently all database stats are not fast."),
new MBeanParameterInfo ("databaseName", "java.lang.String",
"database name")
};
private static final MBeanOperationInfo OP_DB_STAT_INFO =
new MBeanOperationInfo(OP_DB_STAT,
"Get database statistics.",
dbStatParams,
"com.sleepycat.je.DatabaseStats",
MBeanOperationInfo.INFO);
/* target JE environment home directory. */
private File environmentHome;
/*
* If canConfigure is true, this helper will make environment configuration
* attributes available in the mbean metadata. Configuration attributes
* will be saved in the openConfig instance.
*/
private boolean canConfigure;
private EnvironmentConfig openConfig;
/* true if the mbean metadata needs to be refreshed. */
private boolean needReset;
/*
* Save whether the environment was open the last time we fetched
* mbean attributes. Use to detect a change in environment status.
*/
private boolean envWasOpen;
/**
* Instantiate a helper, specifying environment home and open capabilities.
*
* @param environmentHome home directory of the target JE environment.
* @param canConfigure If true, the helper will show environment
* configuration attributes.
*/
public JEMBeanHelper(File environmentHome, boolean canConfigure) {
if (environmentHome == null) {
throw new IllegalArgumentException(
"Environment home cannot be null");
}
this.environmentHome = environmentHome;
this.canConfigure = canConfigure;
if (canConfigure) {
openConfig = new EnvironmentConfig();
}
}
/**
* Return the target environment directory.
* @return the environment directory.
*/
public File getEnvironmentHome() {
return environmentHome;
}
/**
* If the helper was instantiated with canConfigure==true, it shows
* environment configuration attributes. Those attributes are returned
* within this EnvironmentConfig object for use in opening environments.
*
* @return EnvironmentConfig object which saves configuration attributes
* recorded through MBean attributes.
*/
public EnvironmentConfig getEnvironmentOpenConfig() {
return openConfig;
}
/**
* Return an Environment only if the environment has already been opened
* in this process. A helper method for MBeans which want to only access
* open environments.
* @return Environment if already open, null if not open.
*/
public Environment getEnvironmentIfOpen() {
if (environmentHome == null) {
return null;
}
return DbInternal.getEnvironmentShell(environmentHome);
}
/**
* Tell the MBean if the available set of functionality has changed.
*
* @return true if the MBean should regenerate its JE metadata.
*/
public synchronized boolean getNeedReset() {
return needReset;
}
/********************************************************************/
/* MBean Attributes */
/********************************************************************/
/**
* Get MBean attribute metadata for this environment.
* @param targetEnv The target JE environment. May be null if the
* environment is not open.
* @return list of MBeanAttributeInfo objects describing the available
* attributes.
*/
public List getAttributeList(Environment targetEnv) {
/* Turn off reset because the mbean metadata is being refreshed. */
setNeedReset(false);
ArrayList attrList = new ArrayList();
/* Add attributes for all JE environments. */
for (int i = 0; i < COMMON_ATTR.length; i++) {
attrList.add(COMMON_ATTR[i]);
}
if (targetEnv == null) {
if (canConfigure) {
/* Add attributes for configuring an environment. */
for (int i = 0; i < CREATE_ATTR.length; i++) {
attrList.add(CREATE_ATTR[i]);
}
}
} else {
/* Add attributes for an open environment. */
for (int i = 0; i < OPEN_ATTR.length; i++) {
attrList.add(OPEN_ATTR[i]);
}
/* Add attributes for an open, transactional environment. */
try {
EnvironmentConfig config = targetEnv.getConfig();
if (config.getTransactional()) {
for (int i = 0; i < TRANSACTIONAL_ATTR.length; i++) {
attrList.add(TRANSACTIONAL_ATTR[i]);
}
}
} catch (DatabaseException ignore) {
/* ignore */
}
}
return attrList;
}
/**
* Get an attribute value for the given environment. Check
* JEMBeanHelper.getNeedReset() after this call because the helper may
* detect that the environment has changed and that the MBean metadata
* should be reset.
*
* @param targetEnv The target JE environment. May be null if the
* environment is not open.
* @param attributeName attribute name.
* @return attribute value.
*/
public Object getAttribute(Environment targetEnv,
String attributeName)
throws AttributeNotFoundException,
MBeanException {
/* Sanity check. */
if (attributeName == null) {
throw new AttributeNotFoundException(
"Attribute name cannot be null");
}
/* These attributes are available regardless of environment state. */
try {
if (attributeName.equals(ATT_ENV_HOME)) {
return environmentHome.getCanonicalPath();
} else if (attributeName.equals(ATT_OPEN)) {
boolean envIsOpen = (targetEnv != null);
resetIfOpenStateChanged(envIsOpen);
return new Boolean(envIsOpen);
} else if (attributeName.equals(ATT_SET_READ_ONLY)) {
return new Boolean(openConfig.getReadOnly());
} else if (attributeName.equals(ATT_SET_TRANSACTIONAL)) {
return new Boolean(openConfig.getTransactional());
} else if (attributeName.equals(ATT_SET_SERIALIZABLE)) {
return new Boolean(openConfig.getTxnSerializableIsolation());
} else {
/* The rest are JE environment attributes. */
if (targetEnv != null) {
EnvironmentConfig config = targetEnv.getConfig();
if (attributeName.equals(ATT_IS_READ_ONLY)) {
return new Boolean(config.getReadOnly());
} else if (attributeName.equals(ATT_IS_TRANSACTIONAL)) {
return new Boolean(config.getTransactional());
} else if (attributeName.equals(ATT_CACHE_SIZE)) {
return new Long(config.getCacheSize());
} else if (attributeName.equals(ATT_CACHE_PERCENT)) {
return new Integer(config.getCachePercent());
} else if (attributeName.equals(ATT_LOCK_TIMEOUT)) {
return new Long(config.getLockTimeout());
} else if (attributeName.equals(ATT_IS_SERIALIZABLE)) {
return new
Boolean(config.getTxnSerializableIsolation());
} else if (attributeName.equals(ATT_TXN_TIMEOUT)) {
return new Long(config.getTxnTimeout());
} else {
throw new AttributeNotFoundException("attribute " +
attributeName +
" is not valid.");
}
}
return null;
}
} catch (Exception e) {
/*
* Add both the message and the exception for easiest deciphering
* of the problem. Sometimes the original exception stacktrace gets
* hidden in server logs.
*/
throw new MBeanException(e, e.getMessage());
}
}
/**
* Set an attribute value for the given environment.
*
* @param targetEnv The target JE environment. May be null if the
* environment is not open.
* @param attribute name/value pair
*/
public void setAttribute(Environment targetEnv,
Attribute attribute)
throws AttributeNotFoundException,
InvalidAttributeValueException {
if (attribute == null) {
throw new AttributeNotFoundException("Attribute cannot be null");
}
/* Sanity check parameters. */
String name = attribute.getName();
Object value = attribute.getValue();
if (name == null) {
throw new AttributeNotFoundException(
"Attribute name cannot be null");
}
if (value == null) {
throw new InvalidAttributeValueException(
"Attribute value for attribute " +
name + " cannot be null");
}
try {
if (name.equals(ATT_SET_READ_ONLY)) {
openConfig.setReadOnly(((Boolean) value).booleanValue());
} else if (name.equals(ATT_SET_TRANSACTIONAL)) {
openConfig.setTransactional(((Boolean) value).booleanValue());
} else if (name.equals(ATT_SET_SERIALIZABLE)) {
openConfig.setTxnSerializableIsolation(
((Boolean) value).booleanValue());
} else {
/* Set the specified attribute if the environment is open. */
if (targetEnv != null) {
EnvironmentMutableConfig config =
targetEnv.getMutableConfig();
if (name.equals(ATT_CACHE_SIZE)) {
config.setCacheSize(((Long) value).longValue());
targetEnv.setMutableConfig(config);
} else if (name.equals(ATT_CACHE_PERCENT)) {
config.setCachePercent(((Integer) value).intValue());
targetEnv.setMutableConfig(config);
} else {
throw new AttributeNotFoundException("attribute " +
name +
" is not valid.");
}
} else {
throw new AttributeNotFoundException("attribute " +
name +
" is not valid.");
}
}
} catch (NumberFormatException e) {
throw new InvalidAttributeValueException("attribute name=" + name);
} catch (DatabaseException e) {
throw new InvalidAttributeValueException("attribute name=" + name +
e.getMessage());
}
}
/********************************************************************/
/* JE Operations */
/********************************************************************/
/**
* Get mbean operation metadata for this environment.
*
* @param targetEnv The target JE environment. May be null if the
* environment is not open.
* @return List of MBeanOperationInfo describing available operations.
*/
public List getOperationList(Environment targetEnv) {
setNeedReset(false);
List operationList = new ArrayList();
if (targetEnv != null) {
/*
* These operations are only available if the environment is
* open.
*/
operationList.add(OP_CLEAN_INFO);
operationList.add(OP_EVICT_INFO);
operationList.add(OP_ENV_STAT_INFO);
operationList.add(OP_LOCK_STAT_INFO);
operationList.add(OP_DB_NAMES_INFO);
operationList.add(OP_DB_STAT_INFO);
/* Add checkpoint only for transactional environments. */
boolean isTransactional = false;
try {
EnvironmentConfig config = targetEnv.getConfig();
isTransactional = config.getTransactional();
} catch (DatabaseException e) {
/* Don't make any operations available. */
return new ArrayList();
}
if (isTransactional) {
operationList.add(OP_CHECKPOINT_INFO);
operationList.add(OP_TXN_STAT_INFO);
} else {
operationList.add(OP_SYNC_INFO);
}
}
return operationList;
}
/**
* Invoke an operation for the given environment.
*
* @param targetEnv The target JE environment. May be null if the
* environment is not open.
* @param actionName operation name.
* @param params operation parameters. May be null.
* @param signature operation signature. May be null.
* @return the operation result
*/
public Object invoke(Environment targetEnv,
String actionName,
Object [] params,
String [] signature)
throws MBeanException {
/* Sanity checking. */
if (actionName == null) {
throw new IllegalArgumentException("actionName cannot be null");
}
try {
if (targetEnv != null) {
if (actionName.equals(OP_CLEAN)) {
int numFiles = targetEnv.cleanLog();
return new Integer(numFiles);
} else if (actionName.equals(OP_EVICT)) {
targetEnv.evictMemory();
return null;
} else if (actionName.equals(OP_CHECKPOINT)) {
CheckpointConfig config = new CheckpointConfig();
if ((params != null) && (params.length > 0)) {
Boolean force = (Boolean) params[0];
config.setForce(force.booleanValue());
}
targetEnv.checkpoint(config);
return null;
} else if (actionName.equals(OP_SYNC)) {
targetEnv.sync();
return null;
} else if (actionName.equals(OP_ENV_STAT)) {
return targetEnv.getStats(getStatsConfig(params));
} else if (actionName.equals(OP_LOCK_STAT)) {
return targetEnv.getLockStats(getStatsConfig(params));
} else if (actionName.equals(OP_TXN_STAT)) {
return targetEnv.getTransactionStats(
getStatsConfig(params));
} else if (actionName.equals(OP_DB_NAMES)) {
return targetEnv.getDatabaseNames();
} else if (actionName.equals(OP_DB_STAT)) {
return getDatabaseStats(targetEnv, params);
}
}
return new IllegalArgumentException("actionName: " +
actionName +
" is not valid");
} catch (DatabaseException e) {
/*
* Add both the message and the exception for easiest
* deciphering of the problem. Sometimes the original exception
* stacktrace gets hidden in server logs.
*/
throw new MBeanException(e, e.getMessage());
}
}
/**
* Helper for creating a StatsConfig object to use as an operation
* parameter.
*/
private StatsConfig getStatsConfig(Object [] params) {
StatsConfig statsConfig = new StatsConfig();
if ((params != null) && (params.length > 0) && (params[0] != null)) {
Boolean clear = (Boolean) params[0];
statsConfig.setClear(clear.booleanValue());
}
if ((params != null) && (params.length > 1) && (params[1] != null)) {
Boolean fast = (Boolean) params[1];
statsConfig.setFast(fast.booleanValue());
}
return statsConfig;
}
/**
* Helper to get statistics for a given database.
* @param params operation parameters
* @return DatabaseStats object
*/
private DatabaseStats getDatabaseStats(Environment targetEnv,
Object [] params)
throws IllegalArgumentException,
DatabaseException {
if ((params == null) || (params.length < 3)) {
return null;
}
String dbName = (String)params[2];
Database db = null;
try {
DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setReadOnly(true);
DbInternal.setUseExistingConfig(dbConfig, true);
db = targetEnv.openDatabase(null, dbName, dbConfig);
return db.getStats(getStatsConfig(params));
} finally {
if (db != null) {
db.close();
}
}
}
/********************************************************************/
/* JE Notifications.
/********************************************************************/
/**
* No notifications are supported.
* @return List of MBeanNotificationInfo for available notifications.
*/
public MBeanNotificationInfo []
getNotificationInfo(Environment targetEnv) {
return null;
}
/********************************************************************/
/* private helpers.
/********************************************************************/
private synchronized void setNeedReset(boolean reset) {
needReset = reset;
}
private synchronized void resetIfOpenStateChanged(boolean isOpen) {
if (isOpen != envWasOpen) {
setNeedReset(true);
envWasOpen = isOpen;
}
}
}