/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.jmx.model;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DESCRIPTION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.TYPE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.VALUE_TYPE;
import static org.jboss.as.jmx.JmxMessages.MESSAGES;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.management.openmbean.ArrayType;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.openmbean.TabularDataSupport;
import javax.management.openmbean.TabularType;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import org.jboss.dmr.Property;
/**
* Converts between Open MBean types/data and ModelController types/data
*
* @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
*/
public abstract class TypeConverter {
public abstract OpenType<?> getOpenType();
public abstract Object fromModelNode(final ModelNode node);
public abstract ModelNode toModelNode(final Object o);
public abstract Object[] toArray(final List<Object> list);
static OpenType<?> convertToMBeanType(final ModelNode description) {
return getConverter(
description.hasDefined(TYPE) ? description.get(TYPE) : null,
description.hasDefined(VALUE_TYPE) ? description.get(VALUE_TYPE) : null).getOpenType();
}
static ModelNode toModelNode(final ModelNode description, final Object value) {
ModelNode node = new ModelNode();
if (value == null) {
return node;
}
final ModelNode typeNode = description.hasDefined(TYPE) ? description.get(TYPE) : null;
return getConverter(typeNode, description.hasDefined(VALUE_TYPE) ? description.get(VALUE_TYPE) : null).toModelNode(value);
}
static Object fromModelNode(final ModelNode description, final ModelNode value) {
if (value == null || !value.isDefined()) {
return null;
}
final ModelNode typeNode = description.hasDefined(TYPE) ? description.get(TYPE) : null;
final ModelNode valueNode = description.hasDefined(VALUE_TYPE) ? description.get(VALUE_TYPE) : null;
return getConverter(typeNode, valueNode).fromModelNode(value);
}
public static ModelType getType(ModelNode typeNode) {
if (typeNode == null) {
return ModelType.UNDEFINED;
}
try {
return ModelType.valueOf(typeNode.toString());
} catch (RuntimeException e) {
return null;
}
}
public static TypeConverter getConverter(ModelNode typeNode, ModelNode valueTypeNode) {
ModelType modelType = getType(typeNode);
if (modelType == null) {
return new ComplexTypeConverter(typeNode);
}
switch (modelType) {
case BIG_DECIMAL:
return BigDecimalTypeConverter.INSTANCE;
case BIG_INTEGER:
return BigIntegerTypeConverter.INSTANCE;
case BOOLEAN:
return BooleanTypeConverter.INSTANCE;
case BYTES:
return BytesTypeConverter.INSTANCE;
case DOUBLE:
return DoubleTypeConverter.INSTANCE;
case EXPRESSION:
return ExpressionTypeConverter.INSTANCE;
case STRING:
return StringTypeConverter.INSTANCE;
case PROPERTY:
return PropertyTypeConverter.INSTANCE;
case INT:
return IntegerTypeConverter.INSTANCE;
case LONG:
return LongTypeConverter.INSTANCE;
case TYPE:
return ModelTypeTypeConverter.INSTANCE;
case OBJECT:
return new ObjectTypeConverter(valueTypeNode);
case LIST:
return new ListTypeConverter(valueTypeNode);
case UNDEFINED:
return UndefinedTypeConverter.INSTANCE;
default:
throw MESSAGES.unknownType(modelType);
}
}
private static ModelNode nullNodeAsUndefined(ModelNode node) {
if (node == null) {
return new ModelNode();
}
return node;
}
private static class BigDecimalTypeConverter extends TypeConverter {
static final BigDecimalTypeConverter INSTANCE = new BigDecimalTypeConverter();
@Override
public OpenType<?> getOpenType() {
return SimpleType.BIGDECIMAL;
}
@Override
public Object fromModelNode(final ModelNode node) {
if (node == null || !node.isDefined()) {
return null;
}
return node.asBigDecimal();
}
@Override
public ModelNode toModelNode(final Object o) {
if (o == null) {
return new ModelNode();
}
return new ModelNode().set((BigDecimal)o);
}
public Object[] toArray(final List<Object> list) {
return list.toArray(new BigDecimal[list.size()]);
}
}
private static class BigIntegerTypeConverter extends TypeConverter {
static final BigIntegerTypeConverter INSTANCE = new BigIntegerTypeConverter();
@Override
public OpenType<?> getOpenType() {
return SimpleType.BIGINTEGER;
}
@Override
public Object fromModelNode(final ModelNode node) {
if (node == null || !node.isDefined()) {
return null;
}
return node.asBigInteger();
}
@Override
public ModelNode toModelNode(final Object o) {
if (o == null) {
return new ModelNode();
}
return new ModelNode().set((BigInteger)o);
}
public Object[] toArray(final List<Object> list) {
return list.toArray(new BigInteger[list.size()]);
}
}
private static class BooleanTypeConverter extends TypeConverter {
static final BooleanTypeConverter INSTANCE = new BooleanTypeConverter();
@Override
public OpenType<?> getOpenType() {
return SimpleType.BOOLEAN;
}
@Override
public Object fromModelNode(final ModelNode node) {
if (node == null || !node.isDefined()) {
return null;
}
return Boolean.valueOf(node.asBoolean());
}
@Override
public ModelNode toModelNode(final Object o) {
if (o == null) {
return new ModelNode();
}
return new ModelNode().set((Boolean)o);
}
public Object[] toArray(final List<Object> list) {
return list.toArray(new Boolean[list.size()]);
}
}
private static class BytesTypeConverter extends TypeConverter {
static final BytesTypeConverter INSTANCE = new BytesTypeConverter();
static final ArrayType<byte[]> ARRAY_TYPE = ArrayType.getPrimitiveArrayType(byte[].class);
@Override
public OpenType<?> getOpenType() {
return ARRAY_TYPE;
}
@Override
public Object fromModelNode(final ModelNode node) {
if (node == null || !node.isDefined()) {
return null;
}
return node.asBytes();
}
@Override
public ModelNode toModelNode(final Object o) {
if (o == null) {
return new ModelNode();
}
return new ModelNode().set((byte[])o);
}
public Object[] toArray(final List<Object> list) {
return list.toArray(new byte[list.size()][]);
}
}
private static class DoubleTypeConverter extends TypeConverter {
static final DoubleTypeConverter INSTANCE = new DoubleTypeConverter();
@Override
public OpenType<?> getOpenType() {
return SimpleType.DOUBLE;
}
@Override
public Object fromModelNode(final ModelNode node) {
if (node == null || !node.isDefined()) {
return null;
}
return Double.valueOf(node.asDouble());
}
@Override
public ModelNode toModelNode(final Object o) {
if (o == null) {
return new ModelNode();
}
return new ModelNode().set((Double)o);
}
public Object[] toArray(final List<Object> list) {
return list.toArray(new Double[list.size()]);
}
}
private static class IntegerTypeConverter extends TypeConverter {
static final IntegerTypeConverter INSTANCE = new IntegerTypeConverter();
@Override
public OpenType<?> getOpenType() {
return SimpleType.INTEGER;
}
@Override
public Object fromModelNode(final ModelNode node) {
if (node == null || !node.isDefined()) {
return null;
}
return Integer.valueOf(node.asInt());
}
@Override
public ModelNode toModelNode(final Object o) {
if (o == null) {
return new ModelNode();
}
return new ModelNode().set((Integer)o);
}
public Object[] toArray(final List<Object> list) {
return list.toArray(new Integer[list.size()]);
}
}
private static class StringTypeConverter extends TypeConverter {
static final StringTypeConverter INSTANCE = new StringTypeConverter();
@Override
public OpenType<?> getOpenType() {
return SimpleType.STRING;
}
@Override
public Object fromModelNode(final ModelNode node) {
if (node == null || !node.isDefined()) {
return null;
}
return node.asString();
}
@Override
public ModelNode toModelNode(final Object o) {
if (o == null) {
return new ModelNode();
}
return new ModelNode().set((String)o);
}
public Object[] toArray(final List<Object> list) {
return list.toArray(new String[list.size()]);
}
}
private static class UndefinedTypeConverter extends TypeConverter {
static final UndefinedTypeConverter INSTANCE = new UndefinedTypeConverter();
@Override
public OpenType<?> getOpenType() {
return SimpleType.STRING;
}
@Override
public Object fromModelNode(final ModelNode node) {
if (node == null || !node.isDefined()) {
return null;
}
return node.toJSONString(false);
}
@Override
public ModelNode toModelNode(final Object o) {
if (o == null) {
return new ModelNode();
}
return ModelNode.fromJSONString((String)o);
}
public Object[] toArray(final List<Object> list) {
return list.toArray(new String[list.size()]);
}
}
private static class ExpressionTypeConverter extends StringTypeConverter {
static final ExpressionTypeConverter INSTANCE = new ExpressionTypeConverter();
//TODO this is probably fine?
}
private static class PropertyTypeConverter extends StringTypeConverter {
static final ExpressionTypeConverter INSTANCE = new ExpressionTypeConverter();
//TODO Decide how these should look
}
private static class LongTypeConverter extends TypeConverter {
static final LongTypeConverter INSTANCE = new LongTypeConverter();
@Override
public OpenType<?> getOpenType() {
return SimpleType.LONG;
}
@Override
public Object fromModelNode(final ModelNode node) {
if (node == null || !node.isDefined()) {
return null;
}
return Long.valueOf(node.asLong());
}
@Override
public ModelNode toModelNode(final Object o) {
if (o == null) {
return new ModelNode();
}
return new ModelNode().set((Long)o);
}
public Object[] toArray(final List<Object> list) {
return list.toArray(new Long[list.size()]);
}
}
private static class ModelTypeTypeConverter extends TypeConverter {
static final ModelTypeTypeConverter INSTANCE = new ModelTypeTypeConverter();
@Override
public OpenType<?> getOpenType() {
return SimpleType.STRING;
}
@Override
public Object fromModelNode(final ModelNode node) {
if (node == null || !node.isDefined()) {
return null;
}
return String.valueOf(node.asString());
}
@Override
public ModelNode toModelNode(final Object o) {
if (o == null) {
return new ModelNode();
}
return new ModelNode().set(ModelType.valueOf((String)o));
}
public Object[] toArray(final List<Object> list) {
return list.toArray(new String[list.size()]);
}
}
private static class ObjectTypeConverter extends TypeConverter {
final ModelNode valueTypeNode;
final ModelType valueType;
OpenType<?> openType;
ObjectTypeConverter(ModelNode valueTypeNode) {
this.valueTypeNode = nullNodeAsUndefined(valueTypeNode);
ModelType valueType = getType(valueTypeNode);
this.valueType = valueType == ModelType.UNDEFINED ? null : valueType;
}
@Override
public OpenType<?> getOpenType() {
if (openType != null) {
return openType;
}
openType = getConverter(valueTypeNode, null).getOpenType();
if (openType instanceof CompositeType || !valueTypeNode.isDefined()) {
//For complex value types just return the composite type
return openType;
}
try {
CompositeType rowType = new CompositeType(
"entry",
"An entry",
new String[] {"key", "value"},
new String[] {"The key", "The value"},
new OpenType[] {SimpleType.STRING, openType});
openType = new TabularType("A map", "The map is indexed by 'key'", rowType, new String[] {"key"});
return openType;
} catch (OpenDataException e1) {
throw new RuntimeException(e1);
}
}
@Override
public Object fromModelNode(final ModelNode node) {
if (node == null || !node.isDefined()) {
return null;
}
if (valueType != null) {
return fromSimpleModelNode(node);
} else {
TypeConverter converter = getConverter(valueTypeNode, null);
return converter.fromModelNode(node);
}
}
Object fromSimpleModelNode(final ModelNode node) {
final TabularType tabularType = (TabularType)getOpenType();
final TabularDataSupport tabularData = new TabularDataSupport(tabularType);
final Map<String, ModelNode> values = new HashMap<String, ModelNode>();
final List<Property> properties = node.isDefined() ? node.asPropertyList() : null;
if (properties != null) {
for (Property prop : properties) {
values.put(prop.getName(), prop.getValue());
}
}
final TypeConverter converter = TypeConverter.getConverter(valueTypeNode, null);
for (Map.Entry<String, ModelNode> prop : values.entrySet()) {
Map<String, Object> rowData = new HashMap<String, Object>();
rowData.put("key", prop.getKey());
rowData.put("value", converter.fromModelNode(prop.getValue()));
try {
tabularData.put(new CompositeDataSupport(tabularType.getRowType(), rowData));
} catch (OpenDataException e) {
throw new RuntimeException(e);
}
}
return tabularData;
}
@Override
public ModelNode toModelNode(Object o) {
if (o == null) {
return new ModelNode();
}
if (valueType == null) {
//complex
return TypeConverter.getConverter(valueTypeNode, null).toModelNode(o);
} else {
//map
final ModelNode node = new ModelNode();
final TypeConverter converter = TypeConverter.getConverter(valueTypeNode, null);
for (Map.Entry<String, Object> entry : ((Map<String, Object>)o).entrySet()) {
entry = convertTabularTypeEntryToMapEntry(entry);
node.get(entry.getKey()).set(converter.toModelNode(entry.getValue()));
}
return node;
}
}
@Override
public Object[] toArray(List<Object> list) {
if (getOpenType() == SimpleType.STRING) {
return list.toArray(new String[list.size()]);
}
return list.toArray(new Object[list.size()]);
}
//TODO this may go depending on if we want to force tabular types only
private Map.Entry<String, Object> convertTabularTypeEntryToMapEntry(final Map.Entry<?, Object> entry) {
if (entry.getKey() instanceof List) {
//It comes from a TabularType
return new Map.Entry<String, Object>() {
@Override
public String getKey() {
List<?> keyList = (List<?>)entry.getKey();
if (keyList.size() != 1) {
throw MESSAGES.invalidKey(keyList, entry);
}
return (String)keyList.get(0);
}
@Override
public Object getValue() {
return ((CompositeDataSupport)entry.getValue()).get("value");
}
@Override
public Object setValue(Object value) {
throw new UnsupportedOperationException();
}
};
} else {
return new Map.Entry<String, Object>() {
@Override
public String getKey() {
return (String)entry.getKey();
}
@Override
public Object getValue() {
return entry.getValue();
}
@Override
public Object setValue(Object value) {
throw new UnsupportedOperationException();
}
};
}
}
}
private static class ListTypeConverter extends TypeConverter {
final ModelNode valueTypeNode;
ListTypeConverter(ModelNode valueTypeNode) {
this.valueTypeNode = nullNodeAsUndefined(valueTypeNode);
}
@Override
public OpenType<?> getOpenType() {
try {
return ArrayType.getArrayType(getConverter(valueTypeNode, null).getOpenType());
} catch (OpenDataException e) {
throw new RuntimeException(e);
}
}
@Override
public Object fromModelNode(final ModelNode node) {
if (node == null || !node.isDefined()) {
return null;
}
final List<Object> list = new ArrayList<Object>();
final TypeConverter converter = getConverter(valueTypeNode, null);
for (ModelNode element : node.asList()) {
list.add(converter.fromModelNode(element));
}
return converter.toArray(list);
}
@Override
public ModelNode toModelNode(Object o) {
if (o == null) {
return new ModelNode();
}
ModelNode node = new ModelNode();
final TypeConverter converter = getConverter(valueTypeNode, null);
for (Object value : (Object[])o) {
node.add(converter.toModelNode(value));
}
return node;
}
@Override
public Object[] toArray(List<Object> list) {
return null;
}
}
private static class ComplexTypeConverter extends TypeConverter {
final ModelNode typeNode;
ComplexTypeConverter(final ModelNode typeNode) {
this.typeNode = nullNodeAsUndefined(typeNode);
}
@Override
public OpenType<?> getOpenType() {
List<String> itemNames = new ArrayList<String>();
List<String> itemDescriptions = new ArrayList<String>();
List<OpenType<?>> itemTypes = new ArrayList<OpenType<?>>();
//Some of the common operation descriptions use value-types like "The type will be that of the attribute found".
if (!typeNode.isDefined() || typeNode.getType() == ModelType.STRING) {
return SimpleType.STRING;
}
for (String name : typeNode.keys()) {
ModelNode current = typeNode.get(name);
itemNames.add(name);
String description = null;
if (!current.hasDefined(DESCRIPTION)) {
description = "-";
}
else {
description = current.get(DESCRIPTION).asString().trim();
if (description.length() == 0) {
description = "-";
}
}
itemDescriptions.add(getDescription(current));
itemTypes.add(getConverter(current.get(TYPE), current.get(VALUE_TYPE)).getOpenType());
}
try {
return new CompositeType("Complex type", "A complex type", itemNames.toArray(new String[itemNames.size()]), itemDescriptions.toArray(new String[itemDescriptions.size()]), itemTypes.toArray(new OpenType[itemTypes.size()]));
} catch (OpenDataException e) {
throw new RuntimeException(e);
}
}
static String getDescription(ModelNode node) {
if (!node.hasDefined(DESCRIPTION)) {
return "-";
}
String description = node.get(DESCRIPTION).asString();
if (description.trim().length() == 0) {
return "-";
}
return description;
}
@Override
public Object fromModelNode(ModelNode node) {
if (node == null || !node.isDefined()) {
return null;
}
final OpenType<?> openType = getOpenType();
if (openType instanceof CompositeType) {
final CompositeType compositeType = (CompositeType)openType;
//Create a composite
final Map<String, Object> items = new HashMap<String, Object>();
for (String attrName : compositeType.keySet()) {
TypeConverter converter = getConverter(typeNode.get(attrName, TYPE), typeNode.get(attrName, VALUE_TYPE));
items.put(attrName, converter.fromModelNode(node.get(attrName)));
}
try {
return new CompositeDataSupport(compositeType, items);
} catch (OpenDataException e) {
throw new RuntimeException(e);
}
} else {
return node.toJSONString(false);
}
}
@Override
public ModelNode toModelNode(Object o) {
if (o == null) {
return new ModelNode();
}
if (o instanceof CompositeData) {
final ModelNode node = new ModelNode();
final CompositeData composite = (CompositeData)o;
for (String key : composite.getCompositeType().keySet()) {
if (!typeNode.hasDefined(key)){
throw MESSAGES.unknownValue(key);
}
final ModelNode type = typeNode.get(key).get(TYPE);
final ModelNode valueType = typeNode.get(key).get(VALUE_TYPE);
TypeConverter converter = getConverter(type, valueType);
node.get(key).set(converter.toModelNode(composite.get(key)));
}
return node;
} else {
return ModelNode.fromJSONString((String)o);
}
}
@Override
public Object[] toArray(List<Object> list) {
return list.toArray(new CompositeData[list.size()]);
}
}
}