/* * 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 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(); } }