/*
* ProductCodes.java
*
* Copyright 2008 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Sun Microsystems nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package example.productcodes;
import java.lang.management.ManagementFactory;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.management.JMX;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeDataView;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.openmbean.TabularData;
import javax.management.openmbean.TabularDataSupport;
import javax.management.openmbean.TabularType;
/**
* The ProductCodes class to illustrate our example.
* Run 'java example.productcodes.ProductCodes', then connect to the
* application using JConsole or
* VisualVM.
*
*
Note: The purpose of the code shown here is to illustrate an
* example discussed on the author's weblog.
* @author Daniel Fuchs
*/
public class ProductCodes {
// helper methods
static Map newHashMap() {
return new HashMap();
}
static Map newHashMap(Map extends K, ? extends V> map) {
return new HashMap(map);
}
static Map newHashMap(int initialCapacity) {
return new HashMap(initialCapacity);
}
/**
* This interface defines a registered product, known by its name and its
* product ID.
*/
public static interface RegisteredProduct {
public String getProductName();
public int getProductId();
}
/**
* The ProductsDBData interface is a common abstraction for our two
* custom mappings discussed in methods 4 and 5 in our ProductsDB MXBean.
*/
public static interface ProductsDBData {
/**
* The time stamp at which the product data was taken.
*/
public Date getTimeStamp();
/**
* A {@code Map}.
*/
public Map toProductsMap();
}
/**
* A first implementation of ProductsDBData, which expects one field per
* product in the composite data. We cannot use directly the ProductsDBData
* interface because we need a static 'from' method.
* The type exported by the MXBean needs therefore to be
* 'ProductsDBRecord' - not 'ProductsDBData'.
*/
public static abstract class ProductsDBRecord implements ProductsDBData {
public abstract Date getTimeStamp();
public abstract Map toProductsMap();
public static ProductsDBRecord from(CompositeData data) {
return ProductsDB.ProductsDBRecordImpl.from(data);
}
}
/**
* A second implementation of ProductsDBData, which uses a custom
* TabularData containing one row per product. We cannot use directly
* the ProductsDBData interface because we need a static 'from' method.
* The type exported by the MXBean needs therefore to be
* 'ProductsDBRecord' - not 'ProductsDBData'.
*/
public static abstract class ProductsDBTable implements ProductsDBData {
public abstract Date getTimeStamp();
public abstract TabularData getProductsTable();
public abstract Map toProductsMap();
public static ProductsDBTable from(CompositeData data) {
return ProductsDB.ProductsDBTableImpl.from(data);
}
}
/**
* The interface ProductsDBMXBean defines five different ways of exporting
* the same information. It demonstrate various custom and default
* mappings to open types.
*/
public static interface ProductsDBMXBean {
/**
* Get product identities - demonstrate regular MXBean mapping
* to array of CompositeData.
* This is usually the easiest way to proceed.
* Notice how this attribute is displayed by VisualVM/JConsole.
*/
public RegisteredProduct[] getRegisteredProducts()
throws InterruptedException;
/**
* Get product map - demonstrate regular MXBean mapping
* to TabularData. An alternative to method 1 above.
* Notice how this attribute is displayed by VisualVM/JConsole.
* Often method 1 above gives better results.
*/
public Map getProductsMap()
throws InterruptedException;
/**
* Get product map - demonstrate regular MXBean mapping
* to TabularData, where the value is a CompositeData and the
* key is duplicated. An alternative to method 1 and 2, usually
* used when the value (here RegisteredProduct) doesn't contain the
* key, and/or has more than one field besides the key.
* A bit redundant in our context where RegisteredProduct has only
* one field besides the key - but shown for the sake of the example.
* Notice how this attribute is displayed by VisualVM/JConsole.
*/
public Map getRegisteredProductsMap()
throws InterruptedException;
/**
* Get a record of all products - a custom mapping - usually discouraged
* unless absolutely necessary.
*/
public ProductsDBRecord getProductsDBRecord()
throws InterruptedException, OpenDataException;
/**
* Get a record of all products - a custom mapping - usually discouraged
* unless absolutely necessary. Notice how the TabularData displayed
* in JConsole/VisualVM has row with meaningful fields names - similar
* to method 1 (RegisteredProduct[]) and better than for method 2
* method 2 (Map)
*/
public ProductsDBTable getProductsDBTable()
throws InterruptedException, OpenDataException;
/**
* register a new product
* @param productName product name
* @return the registered product id, or null.
*/
public int registerProduct(String productName)
throws InterruptedException;
/**
* remove a registered product
* @param productName product name
* @return the removed product id.
* @throws IllegalArgumentException if the product is not registered.
*/
public int removeProduct(String productName)
throws InterruptedException;
}
/**
* This is our actual productsDB MXBean implementation.
* This class demonstrate various possibilities to expose a list of
* registered products - composed of
* {@code String: productName, Integer: productId} pairs,
* in the attributes of an MXBean.
*/
public static class ProductsDB implements ProductsDBMXBean {
private final Map productMap;
private final ReadWriteLock lock;
private int productIdGenerator = 0;
// Constants used to implement our custom MXBeans mappings.
private static final class Fields {
public static final String productName = "productName";
public static final String productId = "productId";
public static final String timeStamp = "timeStamp";
public static final String productTable = "productTable";
}
public ProductsDB() {
productMap = newHashMap();
lock = new ReentrantReadWriteLock();
}
/**
* Regular mapping - Method 1: return an array of interfaces.
* This attribute will be mapped to a CompositeData[] array.
* Each composite data will have two fields - corresponding to the
* two getters defined in the RegisteredProduct interface...
* Use JConsole or VisualVM to see how this attribute is displayed.
*/
public RegisteredProduct[] getRegisteredProducts()
throws InterruptedException {
if (!lock.readLock().tryLock(3, TimeUnit.SECONDS)) {
throw new InterruptedException("Resource locked. Retry later.");
}
try {
final RegisteredProduct[] registrations =
new RegisteredProduct[productMap.size()];
int count = 0;
for (Map.Entry product:productMap.entrySet()) {
final String productName = product.getKey();
final int productId = product.getValue();
final RegisteredProduct
registered = new RegisteredProduct() {
public String getProductName() {return productName;}
public int getProductId() {return productId;}
};
registrations[count++] = registered;
}
return registrations;
} finally {
lock.readLock().unlock();
}
}
/**
* Regular mapping - Method 2: return a {@code Map}
* This attribute will be mapped to a TabularData.
* Each row of the tabular data will have two fieldss, one called 'key'
* whose value will be the product productName, one called 'value'
* whose value will be the product productId.
* Use JConsole or VisualVM to see how this attribute is displayed.
*/
public Map getProductsMap()
throws InterruptedException {
if (!lock.readLock().tryLock(3, TimeUnit.SECONDS)) {
throw new InterruptedException("Resource locked. Retry later.");
}
try {
return Collections.unmodifiableMap(productMap);
} finally {
lock.readLock().unlock();
}
}
/**
* Regular mapping - Method 3: return a
* {@code Map}
* This attribute will be mapped to a TabularData.
* Each row of the tabular data will have two fields, one called 'key'
* whose value will be the product productName, one called 'value'
* whose value will be the a composite data containing two fields,
* one called 'productName' whose value will be the same than the
* value of 'key', one called 'productId' whose value will be the
* product productId.
* This is a bit redundant, and does not offer any advantage compared
* to method 1, so in general method 1 (array of RegisteredProduct)
* will be prefered.
* Use JConsole or VisualVM to see how this attribute is displayed.
*/
public Map getRegisteredProductsMap()
throws InterruptedException {
if (!lock.readLock().tryLock(3, TimeUnit.SECONDS)) {
throw new InterruptedException("Resource locked. Retry later.");
}
try {
final RegisteredProduct[] uids = getRegisteredProducts();
final Map map =
newHashMap(uids.length);
for (RegisteredProduct u : uids) {
map.put(u.getProductName(), u);
}
return map;
} finally {
lock.readLock().unlock();
}
}
/**
* Custom mapping - Method 4:
* Return a CompositeData where each field correspond to a different
* product.
* The productName of the field is the productName of the product.
* This cannot be achieved directly - so we're taking a round-about.
* We cannot directly return CompositeData, because CompositeDatas are
* not recognized by the MXBean framework. The MXBean framework expects
* all attributes of the MXBean to be custom (or simple) types.
* It doesn't expect the MXBean to directly return CompositeData.
* To work around this, we're defining a class - ProductsDBRecord,
* which we will map ourselves to a composite data using the
* CompositeDataView interface. However - ProductsDBRecord needs to
* have at least one mappable field to be accepted by the MXBean
* framework. We therefore define a class which contains a single field,
* "timeStamp". This will be mapped onto a CompositeData that contains
* a single "timeStamp" field. At runtime we're going to extend the
* actual value of the composite data returned in order to add
* additional fields - one per product.
* To do this, we return a subclass of ProductsDBRecord which
* implements CompositeDataView.
* Since we want ProductsDBRecord to be also meaningful for a regular
* client using MXBean proxies, we also need to add some methods that
* such a client might call to retrieve the actual information, that is,
* our {@code Map}. To do that, we define a
* {@code toProductsMap()} method in ProductsDBRecord. We use 'to'
* rather than 'get' because we don't want the returned Map to be
* mapped onto yet another composite data field.
* We also need to define a 'from(CompositeData)' in the
* ProductsDBRecord class, so that an instance of ProductsDBRecord
* can be reconstructed on the client side by the MXBean proxy...
*
* @return A ProductsDBRecord for which we provide our own custom
* CompositeData mapping.
* @throws java.lang.InterruptedException
* @throws javax.management.openmbean.OpenDataException
*/
public ProductsDBRecord getProductsDBRecord()
throws InterruptedException, OpenDataException {
if (!lock.readLock().tryLock(3, TimeUnit.SECONDS)) {
throw new InterruptedException("Resource locked. Retry later.");
}
try {
return new ProductsDBRecordImpl(productMap);
} finally {
lock.readLock().unlock();
}
}
/**
* Custom Mapping - Method 5:
* Return a tabular data where each row is composed of two fields:
* one is the productName, the other is the productId.
* Like for the Method 4 - we cannot do this directly and use the same
* kind of round-about - which is also a bit contrieved.
* Since there is no such thing as a 'TabularDataView' we create a
* custom class - ProductsDBTable, which we will map to a CompositeData
* using CompositeDataView.
* Our CompositeData will contain two fields: one is a timeStamp, the
* other is our custom tabular data.
*
* @return A ProductsDBTable for which we provide our own custom
* CompositeData mapping.
* @throws java.lang.InterruptedException
* @throws javax.management.openmbean.OpenDataException
*/
public ProductsDBTable getProductsDBTable()
throws InterruptedException, OpenDataException {
if (!lock.readLock().tryLock(3, TimeUnit.SECONDS)) {
throw new InterruptedException("Resource locked. Retry later.");
}
try {
return new ProductsDBTableImpl(productMap);
} finally {
lock.readLock().unlock();
}
}
/**
* A class that implements custom CompositeData mapping for
* method 4 above...
*/
public static class ProductsDBRecordImpl extends ProductsDBRecord
implements CompositeDataView {
private final Date timeStamp;
private final Map productMap;
private ProductsDBRecordImpl(Map productMap) {
this(productMap, new Date());
}
private ProductsDBRecordImpl(Map productMap,
Date date) {
this.productMap = newHashMap(productMap);
this.timeStamp = date;
}
public Date getTimeStamp() {
return timeStamp;
}
public Map toProductsMap() {
return productMap;
}
/**
* The toCompositeData() method will extend the 'regular' mapping
* of ProductsDBRecord, adding one field per product to returned
* composite data.
*
* @return An instance of CompositeDataSupport.
* For interoperability with generic clients such as
* JConsole and VisualVM, it is very important to return
* a CompositeDataSupport, and not a subclass.
*/
public CompositeData toCompositeData(CompositeType ct) {
try {
// Creates a map for composite data values.
// It will contain 1 field for each products, plus
final Map values =
newHashMap();
values.putAll(productMap);
values.put(Fields.timeStamp, getTimeStamp());
final String[] productNames =
productMap.keySet().
toArray(new String[productMap.size()]);
final String[] itemNames =
new String[productNames.length + 1];
System.arraycopy(productNames, 0, itemNames, 1,
productNames.length);
itemNames[0] = Fields.timeStamp;
final OpenType[] itemTypes =
new OpenType[productMap.size() + 1];
Arrays.fill(itemTypes, SimpleType.INTEGER);
itemTypes[0] = SimpleType.DATE;
final CompositeType type =
new CompositeType(
ct.getTypeName(),
ct.getDescription(),
itemNames, itemNames, itemTypes);
return new CompositeDataSupport(type, values);
} catch (RuntimeException x) {
throw x;
} catch (Exception x) {
throw new IllegalArgumentException(ct.getTypeName(), x);
}
}
@Override
public String toString() {
return "{" + Fields.timeStamp + "=" +
String.valueOf(timeStamp) +
", " + Fields.productTable + "=" +
String.valueOf(productMap) + "}";
}
/**
* The from() method makes it possible to reconstruct an instance
* of ProductsDBRecordImpl from the received CompositeData on the
* client side. You only need to implement that if you want to be
* able to use MXBeans proxies on the client side.
*/
public static ProductsDBRecordImpl from(CompositeData data) {
final Date date = (Date) data.get(Fields.timeStamp);
Map products = newHashMap();
for (String key : data.getCompositeType().keySet()) {
if (key.equals(Fields.timeStamp)) {
continue;
}
products.put(key, (Integer) data.get(key));
}
return new ProductsDBRecordImpl(products, date);
}
}
/**
* A class that implements custom CompositeData / TabularDat mapping
* for method 5 above...
*/
public static class ProductsDBTableImpl extends ProductsDBTable
implements CompositeDataView {
private final Date timeStamp;
private final ProductsTable productTable;
private ProductsDBTableImpl(Map productMap) {
this(productMap, new Date());
}
private ProductsDBTableImpl(Map productMap,
Date timeStamp) {
this.productTable = new ProductsTable(productMap);
this.timeStamp = timeStamp;
}
@Override
public Date getTimeStamp() {
return timeStamp;
}
@Override
public TabularData getProductsTable() {
return productTable.toTabularData();
}
@Override
public Map toProductsMap() {
return productTable.toProductsMap();
}
@Override
public String toString() {
return "{" + Fields.timeStamp + "=" +
String.valueOf(timeStamp) +
", " + Fields.productTable + "=" +
String.valueOf(productTable) + "}";
}
/**
* The toCompositeData() method will extend the 'regular' mapping
* of ProductsDBTable, providing a meaningful content for the
* TabularData returned by getProductsTable().
*
* @return An instance of CompositeDataSupport.
* For interoperability with generic clients such as
* JConsole and VisualVM, it is very important to return
* a CompositeDataSupport, and not a subclass.
*/
public CompositeData toCompositeData(CompositeType ct) {
try {
final TabularData table = productTable.toTabularData();
final String[] itemNames = {
Fields.timeStamp, Fields.productTable
};
// replace the generated TabularType by our own...
final OpenType[] itemTypes = {SimpleType.DATE,
table.getTabularType()
};
final CompositeType type =
new CompositeType(// this.getClass().getName(),
ct.getTypeName(),
ct.getDescription(),
itemNames, itemNames, itemTypes);
// provide our own values for timeStamp and productTable.
final Object[] itemValues = {timeStamp, table};
return new CompositeDataSupport(type,itemNames,itemValues);
} catch (RuntimeException x) {
throw x;
} catch (Exception x) {
throw new IllegalArgumentException(ct.getTypeName(), x);
}
}
/**
* The from() method makes it possible to reconstruct an instance
* of ProductsDBTableImpl from the received CompositeData on the
* client side. You only need to implement that if you want to be
* able to use MXBeans proxies on the client side.
*/
public static ProductsDBTableImpl from(CompositeData data) {
final Date date = (Date) data.get(Fields.timeStamp);
Map products = newHashMap();
final TabularData productTable =
(TabularData) data.get(Fields.productTable);
for (CompositeData val :
(Collection) productTable.values()) {
products.put((String) val.get(Fields.productName),
(Integer) val.get(Fields.productId));
}
return new ProductsDBTableImpl(products, date);
}
}
/**
* A helper class that perform a custom mapping to and from
* Map from and to a custom TabularData.
* This makes it possible to have a TabularData whose rows have fields
* named 'productName' and 'productId' instead of 'key' and 'values' as
* would have been generated by default (compare with result of
* Method 2 and Method 3 above...)
*/
private static class ProductsTable {
private static volatile CompositeType rowType = null;
private static volatile TabularType tabularType = null;
private final TabularDataSupport table;
public ProductsTable(Map productMap) {
table = new TabularDataSupport(getProductsTableTabularType());
for (Map.Entry e : productMap.entrySet()) {
table.put(newProductRow(e.getKey(), e.getValue()));
}
}
/**
* Returns a TabularData view of our products table.
*
* @return An instance of TabularDataSupport.
* For interoperability with generic clients such as
* JConsole and VisualVM, it is very important to return
* a TabularDataSupport, and not a subclass.
**/
public TabularData toTabularData() {
return table;
}
/**
* Returns a {@code Map} view of our
* products table.
**/
public Map toProductsMap() {
final Map map = newHashMap();
for (Object d : table.values()) {
final CompositeData cd = (CompositeData) d;
map.put((String) cd.get(Fields.productName),
(Integer) cd.get(Fields.productId));
}
return map;
}
/**
* Construct a new row for our TabularData.
* @return An instance of CompositeDataSupport.
* For interoperability with generic clients such as
* JConsole and VisualVM, it is very important to return
* a CompositeDataSupport, and not a subclass.
*/
private CompositeData newProductRow(String productName,
int productId) {
try {
final String[] itemNames = {
Fields.productName, Fields.productId
};
return new CompositeDataSupport(getProductsTableRowType(),
itemNames, new Object[]{productName, productId});
} catch (OpenDataException x) {
throw new IllegalArgumentException(
"Cannot construct ProductsTable", x);
}
}
@Override
public String toString() {
StringBuilder b = new StringBuilder().append("{");
String start = "{";
for (Object d : table.values()) {
b.append(start).
append(Fields.productName).append("=").
append(((CompositeData) d).get(Fields.productName)).
append(", ").
append(Fields.productId).append("=").
append(((CompositeData) d).get(Fields.productId)).
append("}");
start = ", {";
}
return b.append("}").toString();
}
/**
* Creates and return the CompositeType describing the rows of
* our custom TabularData.
*/
private static CompositeType getProductsTableRowType() {
if (rowType != null) {
return rowType;
}
try {
final String[] itemNames = {
Fields.productName, Fields.productId
};
final OpenType[] itemTypes =
{SimpleType.STRING, SimpleType.INTEGER};
rowType = new CompositeType(
RegisteredProduct.class.getName(),
"registered product record", itemNames,
itemNames, itemTypes);
return rowType;
} catch (OpenDataException x) {
// should not happen.
throw new IllegalArgumentException(
"Cannot construct ProductsTable", x);
}
}
/**
* Creates and return the TabularType describing
* our custom TabularData.
*/
private static TabularType getProductsTableTabularType() {
if (tabularType != null) {
return tabularType;
}
try {
tabularType = new TabularType(ProductsTable.class.getName(),
"product table", getProductsTableRowType(),
new String[]{Fields.productName});
return tabularType;
} catch (OpenDataException x) {
// should not happen.
throw new IllegalArgumentException(
"Cannot construct ProductsTable", x);
}
}
}
/**
* This method will add a new product to the list.
* @param productName product productNam
*/
public int registerProduct(String productName)
throws InterruptedException {
if (!lock.writeLock().tryLock(3, TimeUnit.SECONDS)) {
throw new InterruptedException("Resource locked. Retry later.");
}
try {
if (productMap.containsKey(productName)) {
throw new IllegalArgumentException(productName+
" already exists");
}
productMap.put(productName, productIdGenerator++);
return productMap.get(productName);
} finally {
lock.writeLock().unlock();
}
}
/**
* This method will remove an existing product from the list.
* @param productName product productName
*/
public int removeProduct(String productName)
throws InterruptedException {
if (!lock.writeLock().tryLock(3, TimeUnit.SECONDS)) {
throw new InterruptedException("Resource locked. Retry later.");
}
try {
if (!productMap.containsKey(productName)) {
throw new IllegalArgumentException(
"product "+productName+" does not exists");
}
return productMap.remove(productName);
} finally {
lock.writeLock().unlock();
}
}
}
/**
* Our main entry points.
* Creates and registers a ProductsDBMXBean in the platform MBeanServer.
* Adds to products to the DB: "foo" and "bar".
* Then waits, so that we can connect with JConsole or VisualVM and examine
* the differences in the mapping obtained with methods 1, 2, 3, 4, and 5.
*/
public static void main(String[] args) throws Exception {
final MBeanServer platform =
ManagementFactory.getPlatformMBeanServer();
final ObjectName mbeanName = new ObjectName("example:type=ProductsDB");
platform.registerMBean(new ProductsDB(), mbeanName);
ProductsDBMXBean productsDB =
JMX.newMXBeanProxy(platform, mbeanName, ProductsDBMXBean.class);
productsDB.registerProduct("foo");
productsDB.registerProduct("bar");
System.out.println("Got products DB record: "+
productsDB.getProductsDBRecord());
System.out.println("Got products DB table: "+
productsDB.getProductsDBTable());
System.out.println("Strike 'Enter' to exit... ");
System.in.read();
}
}