/******************************************************************************* * Copyright (c) 2010 Oracle. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * and the Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php. * You may elect to redistribute this code under either of these licenses. * * Contributors: * Hal Hildebrand - Initial JMX support * Christopher Frost - Refactoring for Spec updates ******************************************************************************/ package org.eclipse.gemini.management.internal; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.Vector; import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.OpenDataException; import javax.management.openmbean.TabularData; import javax.management.openmbean.TabularDataSupport; import org.osgi.framework.ServiceReference; import org.osgi.framework.Version; import org.osgi.jmx.JmxConstants; import org.osgi.jmx.framework.wiring.BundleWiringStateMBean; /** * <p> * This class serves as both the documentation of the type structure and as the * codification of the mechanism to convert to/from the TabularData. * <p> * This class represents the CODEC for property dictionaries. As JMX is a rather * primitive system and is not intended to be a generic RMI type system, the set * of types that can be transfered between the management agent and the managed * OSGi container is limited to simple types, arrays of simple types and vectors * of simple types. This enforcement is strict and no attempt is made to create * a yet another generic serialization mechanism for transferring property * values outside of these types. * <p> * The syntax for the type indicator * * <pre> * type ::= scalar | collection | array * scalar ::= String | Integer | Long | Float | Double | Byte | Short | Character | Boolean | BigDecimal | BigInteger | Version * primitive ::= int | long | float | double | byte | short | char | boolean * array ::= <Array of primitive> | <Array of scalar> * collection ::= Collection of scalar * </pre> * * The values for Arrays and Vectors are separated by ",". * <p> * The structure of the composite data for a row in the table is: * <table border="1"> * <tr> * <td>Key</td> * <td>String</td> * </tr> * <tr> * <td>Value</td> * <td>String</td> * </tr> * <tr> * <td>Type</td> * <td>String</td> * </tr> * </table> * <p> * The */ public final class OSGiProperties { private static final String VERSION = "Version"; /** * The scalar type */ private static final List<String> SCALAR_TYPES = Collections.unmodifiableList(Arrays.asList( JmxConstants.STRING, VERSION, JmxConstants.INTEGER, JmxConstants.LONG, JmxConstants.FLOAT, JmxConstants.DOUBLE, JmxConstants.BYTE, JmxConstants.SHORT, JmxConstants.CHARACTER, JmxConstants.BOOLEAN, JmxConstants.BIGDECIMAL, JmxConstants.BIGINTEGER)); /** * The primitive types */ private static final List<String> PRIMITIVE_TYPES = Collections.unmodifiableList(Arrays.asList( JmxConstants.P_BYTE, JmxConstants.P_CHAR, JmxConstants.P_SHORT, JmxConstants.P_INT, JmxConstants.P_LONG, JmxConstants.P_DOUBLE, JmxConstants.P_FLOAT)); /** * Answer the tabular data representation of the properties dictionary * * @param properties * @return the tabular data representation of the properties */ public static TabularData tableFrom(Dictionary<String, Object> properties) { TabularDataSupport table = new TabularDataSupport(JmxConstants.PROPERTIES_TYPE); if (properties != null) { for (Enumeration<?> keys = properties.keys(); keys.hasMoreElements();) { String key = (String) keys.nextElement(); table.put(encode(key, properties.get(key))); } } return table; } /** * Answer the tabular data representation of the service references * properties * * @param ref * @return the tabular data representing the service reference properties */ public static TabularData tableFrom(ServiceReference<?> ref) { Dictionary<String, Object> props = new Hashtable<String, Object>(); for (String key : ref.getPropertyKeys()) { props.put(key, ref.getProperty(key)); } return tableFrom(props); } /** * Encode the key and value as composite data * * @param key * @param value * @return the encoded composite data of the key and value */ public static CompositeData encode(String key, Object value) { Class<?> clazz = value.getClass(); if (clazz.isArray()) { return encodeArray(key, value, clazz.getComponentType()); } else if(value instanceof Vector){ return encodeVector(key, (Vector<?>) value); } else if (value instanceof Collection) { return encodeCollection(key, (Collection<?>) value); } return propertyData(key, value.toString(), typeOf(clazz)); } /** * Answer the hashtable converted from the supplied tabular data * * @param table * @return the hashtable represented by the tabular data */ @SuppressWarnings("unchecked") public static Dictionary<String, Object> propertiesFrom(TabularData table) { Hashtable<String, Object> props = new Hashtable<String, Object>(); if (table == null) { return props; } for (CompositeData data : (Collection<CompositeData>) table.values()) { props.put((String) data.get(JmxConstants.KEY), parse((String) data.get(JmxConstants.VALUE), (String) data.get(JmxConstants.TYPE))); } return props; } /** * Convert a key-value directive in to the required format for representation over JMX * * @param key * @param value * @return a map of key to the key and value to the value */ public static Map<String, ?> getDirectiveKeyValueItem(String key, Object value){ Map<String, Object> items = new HashMap<String, Object>(); items.put(BundleWiringStateMBean.KEY, key); items.put(BundleWiringStateMBean.VALUE, value); return items; } /** * Encode the array as composite data * * @param key * @param value * @param componentClazz * @return the composite data representation */ private static <T> CompositeData encodeArray(String key, Object value, Class<T> componentClazz) { StringBuilder builder = new StringBuilder(); if (Integer.TYPE.equals(componentClazz)) { int[] array = (int[]) value; for (int i = 0; i < array.length; i++) { builder.append(array[i]); builder.append(','); } } else if (Long.TYPE.equals(componentClazz)) { long[] array = (long[]) value; for (int i = 0; i < array.length; i++) { builder.append(array[i]); builder.append(','); } } else if (Double.TYPE.equals(componentClazz)) { double[] array = (double[]) value; for (int i = 0; i < array.length; i++) { builder.append(array[i]); builder.append(','); } } else if (Float.TYPE.equals(componentClazz)) { float[] array = (float[]) value; for (int i = 0; i < array.length; i++) { builder.append(array[i]); builder.append(','); } } else if (Byte.TYPE.equals(componentClazz)) { byte[] array = (byte[]) value; for (int i = 0; i < array.length; i++) { builder.append(array[i]); builder.append(','); } } else if (Short.TYPE.equals(componentClazz)) { short[] array = (short[]) value; for (int i = 0; i < array.length; i++) { builder.append(array[i]); builder.append(','); } } else if (Character.TYPE.equals(componentClazz)) { char[] array = (char[]) value; for (int i = 0; i < array.length; i++) { builder.append(array[i]); builder.append(','); } } else if (Boolean.TYPE.equals(componentClazz)) { boolean[] array = (boolean[]) value; for (int i = 0; i < array.length; i++) { builder.append(array[i]); builder.append(','); } } else { Object[] array = (Object[]) value; for (int i = 0; i < array.length; i++) { builder.append(array[i]); builder.append(','); } } if(builder.length() > 0){ builder.deleteCharAt(builder.length()-1); } return propertyData(key, builder.toString(), JmxConstants.ARRAY_OF + typeOf(componentClazz)); } /** * Encode the vector as composite data * * @param key * @param value * @return the composite data representation */ private static <T> CompositeData encodeVector(String key, Vector<T> value) { String type = "String"; if (value.size() > 0) { type = typeOf(value.get(0).getClass()); } StringBuilder builder = new StringBuilder(); for (T item: value){ builder.append(item); builder.append(','); } if(builder.length() > 0){ builder.deleteCharAt(builder.length()-1); } return propertyData(key, builder.toString(), JmxConstants.VECTOR_OF + type); } /** * Encode the list as composite data * * @param key * @param value * @return the composite data representation */ private static <T> CompositeData encodeCollection(String key, Collection<T> value) { String type = JmxConstants.STRING; if (value.size() > 0) { type = typeOf(value.iterator().next().getClass()); } StringBuilder builder = new StringBuilder(); for(T item: value){ builder.append(item); builder.append(','); } if(builder.length() > 0){ builder.deleteCharAt(builder.length()-1); } return propertyData(key, builder.toString(), JmxConstants.ARRAY_OF + type); } /** * Answer the string type of the class * * @param clazz * @return the string type of the class */ private static String typeOf(Class<?> clazz) { if (clazz.equals(String.class)) { return JmxConstants.STRING; } if (clazz.equals(Version.class)) { return VERSION; } if (clazz.equals(Integer.class)) { return JmxConstants.INTEGER; } if (clazz.equals(Long.class)) { return JmxConstants.LONG; } if (clazz.equals(Double.class)) { return JmxConstants.DOUBLE; } if (clazz.equals(Double.class)) { return JmxConstants.FLOAT; } if (clazz.equals(Byte.class)) { return JmxConstants.BYTE; } if (clazz.equals(Short.class)) { return JmxConstants.SHORT; } if (clazz.equals(Character.class)) { return JmxConstants.CHARACTER; } if (clazz.equals(Boolean.class)) { return JmxConstants.BOOLEAN; } if (clazz.equals(BigDecimal.class)) { return JmxConstants.BIGDECIMAL; } if (clazz.equals(BigInteger.class)) { return JmxConstants.BIGINTEGER; } if (clazz.equals(Integer.TYPE)) { return JmxConstants.P_INT; } if (clazz.equals(Long.TYPE)) { return JmxConstants.P_LONG; } if (clazz.equals(Double.TYPE)) { return JmxConstants.P_DOUBLE; } if (clazz.equals(Double.TYPE)) { return JmxConstants.P_FLOAT; } if (clazz.equals(Byte.TYPE)) { return JmxConstants.P_BYTE; } if (clazz.equals(Short.TYPE)) { return JmxConstants.P_SHORT; } if (clazz.equals(Character.TYPE)) { return JmxConstants.P_CHAR; } if (clazz.equals(Boolean.TYPE)) { return JmxConstants.P_BOOLEAN; } throw new IllegalArgumentException("Illegal type: " + clazz); } /** * Answer the composite data representation of the key/value pair * * @param key * @param value * @param type * @return the composite data representation of the key/value pair */ private static CompositeData propertyData(String key, String value, String type) { Map<String, Object> items = new HashMap<String, Object>(); items.put(JmxConstants.KEY, key); items.put(JmxConstants.VALUE, value); items.put(JmxConstants.TYPE, type); try { return new CompositeDataSupport(JmxConstants.PROPERTY_TYPE, items); } catch (OpenDataException e) { throw new IllegalStateException("Cannot form property open data", e); } } /** * Parse the string value into an Object * * @param value * @param type * @return the object represented by the String */ private static Object parse(String value, String type) { StringTokenizer tokens = new StringTokenizer(type); if (!tokens.hasMoreElements()) { throw new IllegalArgumentException("Type is empty"); } String token = tokens.nextToken(); if ("Array".equals(token)) { return parseArray(value, tokens); } if ("Vector".equals(token)) { return parseCollection(value, tokens); } if (SCALAR_TYPES.contains(token) || PRIMITIVE_TYPES.contains(token)) { return parseValue(value, token); } throw new IllegalArgumentException("Unknown type: " + type); } /** * Parse the array represented by the string value * * @param value * @param tokens * @return the array represented by the string value */ private static Object parseArray(String value, StringTokenizer tokens) { if (!tokens.hasMoreTokens()) { throw new IllegalArgumentException("Expecting <of> token in Array type"); } if (!"of".equals(tokens.nextToken())) { throw new IllegalArgumentException("Expecting <of> token in Array type"); } if (!tokens.hasMoreTokens()) { throw new IllegalArgumentException("Expecting <primitive>|<scalar> token in Array type"); } String type = tokens.nextToken(); if (SCALAR_TYPES.contains(type)) { return parseScalarArray(value, type); } else if (PRIMITIVE_TYPES.contains(type)) { return parsePrimitiveArray(value, type); } else { throw new IllegalArgumentException("Expecting <scalar>|<primitive> type token in Array type: " + type); } } /** * Parse the vector represented by the supplied string value * * @param value * @param tokens * @return the vector represented by the supplied string value */ private static Object parseCollection(String value, StringTokenizer tokens) { if (!tokens.hasMoreTokens()) { throw new IllegalArgumentException("Expecting <of> token in Collection type"); } if (!tokens.nextElement().equals("of")) { throw new IllegalArgumentException("Expecting <of> token in Collection type"); } if (!tokens.hasMoreTokens()) { throw new IllegalArgumentException("Expecting <scalar> token in Collection type"); } String type = tokens.nextToken(); StringTokenizer values = new StringTokenizer(value, ","); Collection<Object> collection = new Vector<Object>(); if (!SCALAR_TYPES.contains(type)) { throw new IllegalArgumentException("Expecting <scalar> type token in Collection type: " + type); } while (values.hasMoreTokens()) { collection.add(parseScalar(values.nextToken().trim(), type)); } return collection; } /** * Parse the array represented by the string value * * @param value * @param type * @return the array represented by the string value */ private static Object[] parseScalarArray(String value, String type) { ArrayList<Object> array = new ArrayList<Object>(); StringTokenizer values = new StringTokenizer(value, ","); while (values.hasMoreTokens()) { array.add(parseScalar(values.nextToken().trim(), type)); } return array.toArray(createScalarArray(type, array.size())); } /** * Parse the array from the supplied values * * @param value * @param type * @return the array from the supplied values */ private static Object parsePrimitiveArray(String value, String type) { StringTokenizer values = new StringTokenizer(value, ","); if (JmxConstants.P_INT.equals(type)) { int[] array = new int[values.countTokens()]; int i = 0; while (values.hasMoreTokens()) { array[i++] = Integer.parseInt(values.nextToken().trim()); } return array; } if (JmxConstants.P_LONG.equals(type)) { long[] array = new long[values.countTokens()]; int i = 0; while (values.hasMoreTokens()) { array[i++] = Long.parseLong(values.nextToken().trim()); } return array; } if (JmxConstants.P_DOUBLE.equals(type)) { double[] array = new double[values.countTokens()]; int i = 0; while (values.hasMoreTokens()) { array[i++] = Double.parseDouble(values.nextToken().trim()); } return array; } if (JmxConstants.P_FLOAT.equals(type)) { float[] array = new float[values.countTokens()]; int i = 0; while (values.hasMoreTokens()) { array[i++] = Float.parseFloat(values.nextToken().trim()); } return array; } if (JmxConstants.P_BYTE.equals(type)) { byte[] array = new byte[values.countTokens()]; int i = 0; while (values.hasMoreTokens()) { array[i++] = Byte.parseByte(values.nextToken().trim()); } return array; } if (JmxConstants.P_SHORT.equals(type)) { short[] array = new short[values.countTokens()]; int i = 0; while (values.hasMoreTokens()) { array[i++] = Short.parseShort(values.nextToken().trim()); } return array; } if (JmxConstants.P_CHAR.equals(type)) { char[] array = new char[values.countTokens()]; int i = 0; while (values.hasMoreTokens()) { array[i++] = values.nextToken().trim().charAt(0); } return array; } if (JmxConstants.P_BOOLEAN.equals(type)) { boolean[] array = new boolean[values.countTokens()]; int i = 0; while (values.hasMoreTokens()) { array[i++] = Boolean.parseBoolean(values.nextToken().trim()); } return array; } throw new IllegalArgumentException("Unknown primitive type: " + type); } /** * Create the scalar array from the supplied type * * @param type * @param size * @return the scalar array from the supplied type */ private static Object[] createScalarArray(String type, int size) { if (JmxConstants.STRING.equals(type)) { return new String[size]; } if (VERSION.equals(type)) { return new Version[size]; } if (JmxConstants.INTEGER.equals(type)) { return new Integer[size]; } if (JmxConstants.LONG.equals(type)) { return new Long[size]; } if (JmxConstants.DOUBLE.equals(type)) { return new Double[size]; } if (JmxConstants.FLOAT.equals(type)) { return new Float[size]; } if (JmxConstants.BYTE.equals(type)) { return new Byte[size]; } if (JmxConstants.SHORT.equals(type)) { return new Short[size]; } if (JmxConstants.CHARACTER.equals(type)) { return new Character[size]; } if (JmxConstants.BOOLEAN.equals(type)) { return new Boolean[size]; } if (JmxConstants.BIGDECIMAL.equals(type)) { return new BigDecimal[size]; } if (JmxConstants.BIGINTEGER.equals(type)) { return new BigInteger[size]; } throw new IllegalArgumentException("Unknown scalar type: " + type); } /** * Construct the scalar value represented by the string * * @param value * @param type * @return the scalar value represented by the string */ private static Object parseScalar(String value, String type) { if (JmxConstants.STRING.equals(type)) { return value; } if (VERSION.equals(type)) { return Version.parseVersion(value); } if (JmxConstants.INTEGER.equals(type)) { return Integer.parseInt(value); } if (JmxConstants.LONG.equals(type)) { return Long.parseLong(value); } if (JmxConstants.DOUBLE.equals(type)) { return Double.parseDouble(value); } if (JmxConstants.FLOAT.equals(type)) { return Float.parseFloat(value); } if (JmxConstants.BYTE.equals(type)) { return Byte.parseByte(value); } if (JmxConstants.SHORT.equals(type)) { return Short.parseShort(value); } if (JmxConstants.CHARACTER.equals(type)) { return value.charAt(0); } if (JmxConstants.BOOLEAN.equals(type)) { return Boolean.parseBoolean(value); } if (JmxConstants.BIGDECIMAL.equals(type)) { return new BigDecimal(value); } if (JmxConstants.BIGINTEGER.equals(type)) { return new BigInteger(value); } throw new IllegalArgumentException("Unknown scalar type: " + type); } /** * Construct the scalar value represented by the string * * @param value * @param type * @return the scalar value represented by the string */ private static Object parseValue(String value, String type) { try{ return parseScalar(value, type); }catch (IllegalArgumentException e) { if (JmxConstants.P_INT.equals(type)) { return Integer.parseInt(value); } if (JmxConstants.P_LONG.equals(type)) { return Long.parseLong(value); } if (JmxConstants.P_DOUBLE.equals(type)) { return Double.parseDouble(value); } if (JmxConstants.P_FLOAT.equals(type)) { return Float.parseFloat(value); } if (JmxConstants.P_BYTE.equals(type)) { return Byte.parseByte(value); } if (JmxConstants.P_SHORT.equals(type)) { return Short.parseShort(value); } if (JmxConstants.P_CHAR.equals(type)) { return value.charAt(0); } if (JmxConstants.P_BOOLEAN.equals(type)) { return Boolean.parseBoolean(value); } throw new IllegalArgumentException("Unknown scalar type: " + type); } } }