org.glassfish.admin.amx.impl.config.ConfigBeanJMXSupport Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of payara-micro Show documentation
Show all versions of payara-micro Show documentation
Micro Distribution of the Payara Project for IBM JDK
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2009-2012 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.glassfish.admin.amx.impl.config;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.management.Descriptor;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.AttributeChangeNotification;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.ObjectName;
import javax.management.modelmbean.DescriptorSupport;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenType;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import com.sun.enterprise.config.serverbeans.Domain;
import org.glassfish.external.arc.Stability;
import org.glassfish.external.arc.Taxonomy;
import static org.glassfish.external.amx.AMX.*;
import org.glassfish.admin.amx.core.Util;
import static org.glassfish.admin.amx.config.AMXConfigConstants.*;
import org.glassfish.admin.amx.config.AMXConfigProxy;
import org.glassfish.admin.amx.impl.util.ImplUtil;
import org.glassfish.admin.amx.util.ClassUtil;
import org.glassfish.admin.amx.util.CollectionUtil;
import org.glassfish.admin.amx.util.MapUtil;
import org.glassfish.admin.amx.util.SetUtil;
import org.glassfish.admin.amx.util.ListUtil;
import org.glassfish.admin.amx.util.StringUtil;
import org.glassfish.admin.amx.util.jmx.JMXUtil;
import org.glassfish.admin.amx.util.stringifier.SmartStringifier;
import org.glassfish.api.admin.config.PropertiesDesc;
import org.glassfish.api.admin.config.PropertyDesc;
import org.glassfish.quality.ToDo;
import org.jvnet.hk2.config.Attribute;
import org.jvnet.hk2.config.Units;
import org.jvnet.hk2.config.ConfigBean;
import org.jvnet.hk2.config.ConfigBeanProxy;
import org.jvnet.hk2.config.Dom;
import org.jvnet.hk2.config.DuckTyped;
import org.jvnet.hk2.config.Element;
import org.jvnet.hk2.config.Configured;
import org.jvnet.hk2.config.ConfigModel;
import org.jvnet.hk2.config.DomDocument;
import org.glassfish.admin.amx.impl.util.InjectedValues;
import java.util.logging.Level;
import org.glassfish.admin.amx.util.AMXLoggerInfo;
/**
* Helps generate required JMX artifacts (MBeanInfo, etc) from a ConfigBean interface, as well
* storing author useful information about each @Configured interface.
* @author llc
*/
@Taxonomy(stability = Stability.NOT_AN_INTERFACE)
class ConfigBeanJMXSupport
{
private final Class extends ConfigBeanProxy> mIntf;
private final List mAttrInfos = new ArrayList();
private final List mElementInfos = new ArrayList();
private final List mDuckTypedInfos = new ArrayList();
private final NameHint mNameHint;
private final MBeanInfo mMBeanInfo;
private final String mKey; // xml name
private static String nameFromKey(final String key)
{
if (key == null)
{
return null;
}
if (key.startsWith("@"))
{
return key.substring(1);
}
if (key.startsWith("<"))
{
return key.substring(1, key.length() - 1);
}
throw new IllegalArgumentException(key);
}
ConfigBeanJMXSupport(final ConfigBean configBean)
{
this(configBean.getProxyType(), nameFromKey(configBean.model.key));
//debug( "ConfigBeanJMXSupport: " + configBean.getProxyType().getName() + ": key=" + configBean.model.key + ", keyedAs=" + configBean.model.keyedAs);
//debug( toString() );
}
/**
The 'key' should not be necessary as the annotations should supply that information.
But some are defective, without setting key=true.
*/
ConfigBeanJMXSupport(
final Class extends ConfigBeanProxy> intf,
final String key)
{
mIntf = intf;
mKey = key;
findStuff(intf, mAttrInfos, mElementInfos, mDuckTypedInfos);
sanityCheckConfigured();
mMBeanInfo = _getMBeanInfo();
sanityCheckMBeanInfo();
mNameHint = findNameHint();
/**
if (hasConfiguredBug() && key == null) {
ImplUtil.getLogger().warning("ConfigBeanJMXSupport (AMX): working around @Configured bug for " + mIntf.getName() +
", using \"" + configuredBugKey() + "\" as the key attribute");
}
*/
}
public Class extends ConfigBeanProxy> getIntf() { return mIntf; }
public String toString()
{
final StringBuilder buf = new StringBuilder();
final String DELIM = ", ";
final String NL = StringUtil.NEWLINE();
buf.append(mIntf.getName() + " = ");
buf.append(NL + "Attributes: {" + NL);
for (final AttributeMethodInfo info : mAttrInfos)
{
buf.append(info.attrName() + "/" + info.xmlName() + DELIM);
}
buf.append(NL + "}" + NL + "Elements: {" + NL);
for (final ElementMethodInfo info : mElementInfos)
{
buf.append(info.attrName() + "/" + info.xmlName() + DELIM);
}
final Set childTypes = childTypes().keySet();
buf.append(NL + "}" + NL + "Child types: {" + NL);
for (final String type : childTypes)
{
buf.append(type + DELIM);
}
buf.append(NL + "}" + NL + "DuckTyped: {" + NL);
for (final DuckTypedInfo info : mDuckTypedInfos)
{
buf.append(info + NL);
}
buf.append(NL + "}" + NL);
return buf.toString();
}
/**
Is the signature a perfect match?
In a modular system, we can't load classes from classnames to compare, so we
can't do isAssignableFrom(). So look for a perfect match as the priority.
*/
private boolean isPerfectMatch(final String[] types, final Class>[] sig)
{
if (types == null && (sig == null || sig.length == 0))
{
return true;
}
boolean mismatch = false;
for (int i = 0; i < sig.length; ++i)
{
if (sig[i].getName().equals(types[i]))
{
mismatch = true;
break;
}
}
return !mismatch;
}
public DuckTypedInfo findDuckTyped(final String name, final String[] types)
{
DuckTypedInfo info = null;
final int numTypes = types == null ? 0 : types.length;
for (final DuckTypedInfo candidate : mDuckTypedInfos)
{
// debug( "Match " + name + "=" + numTypes + " against " + candidate.name() + "=" + candidate.signature().length );
final Class>[] sig = candidate.signature();
if (candidate.name().equals(name) && numTypes == sig.length)
{
//debug( "Matched DuckTyped method: " + name );
if (isPerfectMatch(types, sig))
{
info = candidate;
break;
}
else if (info == null)
{
// first one takes priority
info = candidate;
}
}
}
return info;
}
public String getTypeString()
{
return getTypeString(mIntf);
}
public String getTypeString(final Class extends ConfigBeanProxy> intf)
{
String type = null;
final Configured configuredAnnotation = intf.getAnnotation(Configured.class);
if (configuredAnnotation != null && configuredAnnotation.name().length() != 0)
{
type = configuredAnnotation.name();
if ( type == null || type.length() == 0 )
{
throw new IllegalArgumentException("ConfigBeanJMXSupport.getTypeString(): Malformed @Configured annotation on " + intf.getName() );
}
}
else
{
final Package pkg = intf.getPackage();
String simple = intf.getName().substring(pkg.getName().length() + 1, intf.getName().length());
type = Util.typeFromName(simple);
if ( type == null || type.length() == 0 )
{
throw new IllegalArgumentException("ConfigBeanJMXSupport.getTypeString(): Malformed type generated from " + intf.getName() );
}
}
return type;
}
public MBeanInfo getMBeanInfo()
{
return mMBeanInfo;
}
// create only one of these, it's always the same
private static final MBeanNotificationInfo ATTRIBUTE_CHANGE_NOTIF_INFO =
new MBeanNotificationInfo(
new String[] { AttributeChangeNotification.ATTRIBUTE_CHANGE},
AttributeChangeNotification.class.getName(),
"attribute change");
private MBeanInfo _getMBeanInfo()
{
final List attrsList = new ArrayList();
for (final AttributeMethodInfo info : mAttrInfos)
{
attrsList.add(attributeToMBeanAttributeInfo(info));
}
for (final ElementMethodInfo e : mElementInfos)
{
final MBeanAttributeInfo attrInfo = elementToMBeanAttributeInfo(e.method());
if (attrInfo != null)
{
attrsList.add(attrInfo);
}
}
final MBeanAttributeInfo[] attrs = new MBeanAttributeInfo[attrsList.size()];
attrsList.toArray(attrs);
final String classname = mIntf.getName();
final String description = "ConfigBean " + mIntf.getName();
final MBeanOperationInfo[] operations = toMBeanOperationInfos();
final Descriptor descriptor = descriptor();
final MBeanNotificationInfo[] notifications = new MBeanNotificationInfo[] {ATTRIBUTE_CHANGE_NOTIF_INFO};
final MBeanInfo info = new MBeanInfo(
classname,
description,
attrs,
null,
operations,
notifications,
descriptor);
return info;
}
private boolean hasNameAttribute()
{
for (final MBeanAttributeInfo attrInfo : getMBeanInfo().getAttributes())
{
if (ATTR_NAME.equals(attrInfo.getName()))
{
return true;
}
}
return false;
}
private void sanityCheckMBeanInfo()
{
// verify that we don't have an item with getName() that's marked as a singleton
// another ID could be used too (eg 'thread-pool-id'), no way to tell.
if (isSingleton())
{
if (hasNameAttribute())
{
AMXLoggerInfo.getLogger().log(Level.FINE,
"ConfigBeanJMXSupport (AMX): @Configured interface {0} has getName() which is not a key value. Remove getName() or use @Attribute(key=true)",
mIntf.getName());
}
}
}
// if no key value can be found, consider it a singleton
public boolean isSingleton()
{
if (mKey != null)
{
return false;
}
/*
if (hasConfiguredBug()) {
return false;
}
*/
for (final AttributeMethodInfo info : mAttrInfos)
{
if (info.key())
{
return false;
}
}
for (final ElementMethodInfo info : mElementInfos)
{
if (info.key())
{
return false;
}
}
return true;
}
// if no elements, then it's a leaf
// Tricky case FIXME: what if there are List elements.
boolean isLeaf()
{
return mElementInfos.size() == 0;
}
/** partial list (quick check) of legal remoteable types */
private static final Set> REMOTABLE = SetUtil.newSet(new Class>[]
{
Void.class,
Boolean.class,
Character.class,
String.class,
Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, BigDecimal.class, BigInteger.class,
Date.class,
ObjectName.class,
CompositeType.class,
CompositeDataSupport.class
});
/**
Be very conservative at first.
Some @Configured types can be converted to ObjectName.
*/
private static boolean isRemoteableType(final Class> clazz)
{
// quick check for the 99% case
if (clazz.isPrimitive() ||
REMOTABLE.contains(clazz) ||
CompositeData.class.isAssignableFrom(clazz) ||
OpenType.class.isAssignableFrom(clazz))
{
return true;
}
if (clazz.isArray())
{
return isRemoteableType(clazz.getComponentType());
}
if (Collection.class.isAssignableFrom(clazz))
{
// no way to tell, allow it
return true;
}
if (Map.class.isAssignableFrom(clazz))
{
// no way to tell, allow it
return true;
}
if (ConfigBeanProxy.class.isAssignableFrom(clazz))
{
// represented as an ObjectName
return true;
}
return false;
}
private static boolean isRemoteableDuckTyped(final Method m, final DuckTyped duckTyped)
{
boolean isRemotable = true;
final Class> returnType = m.getReturnType();
if (!isRemoteableType(returnType))
{
return false;
}
final Class>[] sig = m.getParameterTypes();
for (final Class> c : sig)
{
if (!isRemoteableType(c))
{
return false;
}
// in an OSGI world, passing an argument of type Class is highly dubious
if (c == Class.class)
{
return false;
}
}
return true;
}
private static Class> remoteType(final Class> clazz)
{
if (ConfigBeanProxy.class.isAssignableFrom(clazz))
{
return ObjectName.class;
}
return clazz;
}
private void findStuff(
final Class extends ConfigBeanProxy> intf,
final List attrs,
final List elements,
final List duckTyped)
{
for (final Method m : intf.getMethods())
{
AttributeMethodInfo a;
//debug( "Method: " + m.getName() + " on " + m.getDeclaringClass() );
if ((a = AttributeMethodInfo.get(m)) != null)
{
attrs.add(a);
if ( a.returnType() != String.class )
{
AMXLoggerInfo.getLogger().log(Level.INFO, AMXLoggerInfo.illegalNonstring,
new Object[]{intf.getName(), m.getName(), a.returnType().getName()});
}
continue;
}
ElementMethodInfo e;
if ((e = ElementMethodInfo.get(m)) != null)
{
elements.add(e);
continue;
}
final DuckTyped dt = m.getAnnotation(DuckTyped.class);
if (dt != null && isRemoteableDuckTyped(m, dt))
{
duckTyped.add(new DuckTypedInfo(m, dt));
}
}
}
/** check for Bad Stuff in Configured interface. */
public List sanityCheckConfigured()
{
final List problems = new ArrayList();
for( final AttributeMethodInfo info : mAttrInfos )
{
final Class> dataType = info.inferDataType();
if ( (dataType == Boolean.class || dataType == boolean.class) && info.notNull() && ! info.hasDefaultValue() )
{
problems.add( "Missing defaultValue for Boolean @Configured " + mIntf.getName() + ".get" + info.attrName() + "()" );
}
}
if ( problems.size() != 0 )
{
System.out.println( CollectionUtil.toString( problems, "\n" ) );
}
return problems;
}
public static String xmlName(final MBeanAttributeInfo info, final String defaultValue)
{
final String value = (String) info.getDescriptor().getFieldValue(DESC_XML_NAME);
return value == null ? defaultValue : value;
}
public static boolean isKey(final MBeanAttributeInfo info)
{
return (Boolean) info.getDescriptor().getFieldValue(DESC_KEY);
}
public static String defaultValue(final MBeanAttributeInfo info)
{
return (String) info.getDescriptor().getFieldValue(DESC_DEFAULT_VALUE);
}
/** Return a Map from the Attribute name to the xml name. */
public Map getToXMLNameMapping()
{
final Map m = new HashMap();
final MBeanInfo info = getMBeanInfo();
for (final MBeanAttributeInfo attrInfo : info.getAttributes())
{
m.put(attrInfo.getName(), xmlName(attrInfo, attrInfo.getName()));
}
return m;
}
/** Return a Map from the xml name to the Attribute name. */
public Map getFromXMLNameMapping()
{
final Map m = new HashMap();
final MBeanInfo info = getMBeanInfo();
for (final MBeanAttributeInfo attrInfo : info.getAttributes())
{
m.put(xmlName(attrInfo, attrInfo.getName()), attrInfo.getName());
}
return m;
}
public static boolean isAttribute(final MBeanAttributeInfo info)
{
final String value = (String) info.getDescriptor().getFieldValue(DESC_KIND);
return value == null || Attribute.class.getName().equals(value);
}
public static boolean isElement(final MBeanAttributeInfo info)
{
final String value = (String) info.getDescriptor().getFieldValue(DESC_KIND);
return Element.class.getName().equals(value);
}
public static DescriptorSupport descriptor(final AttributeMethodInfo info)
{
final DescriptorSupport d = new DescriptorSupport();
final Attribute a = info.attribute();
d.setField(DESC_KIND, Attribute.class.getName());
if (!a.defaultValue().equals("\u0000"))
{
d.setField(DESC_DEFAULT_VALUE, a.defaultValue());
}
d.setField(DESC_KEY, a.key());
d.setField(DESC_REQUIRED, a.required());
d.setField(DESC_REFERENCE, a.reference());
d.setField(DESC_VARIABLE_EXPANSION, a.variableExpansion());
d.setField(DESC_DATA_TYPE, info.inferDataType().getName());
return d;
}
public static DescriptorSupport descriptor(final Element e)
{
final DescriptorSupport d = new DescriptorSupport();
d.setField(DESC_KIND, Element.class.getName());
d.setField(DESC_KEY, e.key());
d.setField(DESC_REQUIRED, e.required());
d.setField(DESC_REFERENCE, e.reference());
d.setField(DESC_VARIABLE_EXPANSION, e.variableExpansion());
return d;
}
public static DescriptorSupport descriptor(final DuckTyped dt)
{
final DescriptorSupport d = new DescriptorSupport();
d.setField(DESC_KIND, DuckTyped.class.getName());
return d;
}
private DescriptorSupport descriptor()
{
final DescriptorSupport d = new DescriptorSupport();
String amxInterfaceName = AMXConfigProxy.class.getName(); // generic default
final String intfPackage = mIntf.getPackage().getName();
if (Domain.class.getPackage().getName().equals(intfPackage))
{
amxInterfaceName = mIntf.getName();
}
d.setField(DESC_STD_INTERFACE_NAME, amxInterfaceName);
d.setField(DESC_GENERIC_INTERFACE_NAME, AMXConfigProxy.class.getName());
d.setField(DESC_STD_IMMUTABLE_INFO, true);
d.setField(DESC_GROUP, "config");
// Adoption is not supported, only other config elements
d.setField(DESC_SUPPORTS_ADOPTION, false);
d.setField(DESC_IS_SINGLETON, isSingleton());
final String[] subTypes = CollectionUtil.toArray(childTypes().keySet(), String.class);
d.setField(DESC_SUB_TYPES, subTypes);
return d;
}
public final Set requiredAttributeNames()
{
final Set s = new HashSet();
for (final AttributeMethodInfo info : mAttrInfos)
{
if (info.required())
{
s.add(info.attrName());
}
}
for (final ElementMethodInfo info : mElementInfos)
{
if (info.required())
{
s.add(info.attrName());
}
}
return s;
}
/**
DuckTyped methods are always exposed as operations, never as Attributes.
*/
public MBeanOperationInfo duckTypedToMBeanOperationInfo(final DuckTypedInfo info)
{
final Descriptor descriptor = descriptor(info.duckTyped());
final String name = info.name();
final Class> type = remoteType(info.returnType());
final String description = "@DuckTyped " + name + " of " + mIntf.getName();
final int impact = MBeanOperationInfo.UNKNOWN; // how to tell?
final List paramInfos = new ArrayList();
int i = 0;
for (final Class> paramClass : info.signature())
{
final String paramName = "p" + i;
final String paramType = remoteType(paramClass).getName();
final String paramDescription = "parameter " + i;
final MBeanParameterInfo paramInfo = new MBeanParameterInfo(paramName, paramType, paramDescription, null);
paramInfos.add(paramInfo);
++i;
}
final MBeanParameterInfo[] paramInfosArray = CollectionUtil.toArray(paramInfos, MBeanParameterInfo.class);
final MBeanOperationInfo opInfo = new MBeanOperationInfo(name, description,
paramInfosArray, type.getName(), impact, descriptor);
return opInfo;
}
public MBeanOperationInfo[] toMBeanOperationInfos()
{
final List opInfos = new ArrayList();
for (final DuckTypedInfo info : mDuckTypedInfos)
{
final MBeanOperationInfo opInfo = duckTypedToMBeanOperationInfo(info);
if (opInfo != null)
{
opInfos.add(opInfo);
}
}
return CollectionUtil.toArray(opInfos, MBeanOperationInfo.class);
}
private static final Set IGNORE_ANNOTATION_METHODS = SetUtil.newUnmodifiableStringSet("toString", "hashCode", "annotationType");
private void addAnnotationsToDescriptor(final Descriptor d, final AttributeMethodInfo info)
{
final Annotation[] annotations = info.annotations();
for (final Annotation a : annotations)
{
final String prefix = DESC_ANNOTATION_PREFIX + "@" + a.annotationType().getName() + ":";
final Method[] values = a.getClass().getDeclaredMethods();
for (final Method m : values)
{
final String fieldName = m.getName();
if (IGNORE_ANNOTATION_METHODS.contains(fieldName))
{
continue;
}
//debug( "INVOKING: " + fieldName + ", returnType = " + m.getReturnType() );
if (m.getParameterTypes().length == 0)
{
try
{
final Object fieldValue = m.invoke(a);
// make sure all metadata is safe across the wire: convert to String
Object actualValue = fieldValue;
if (actualValue != null /* && ! actualValue.getClass().getName().startsWith("java.lang") */)
{
actualValue = SmartStringifier.toString(actualValue);
}
d.setField(prefix + fieldName, actualValue);
}
catch (final Exception e)
{
AMXLoggerInfo.getLogger().log( Level.INFO, AMXLoggerInfo.cantGetField,
new Object[] {a, e.getLocalizedMessage()} );
}
}
}
}
}
public MBeanAttributeInfo attributeToMBeanAttributeInfo(final AttributeMethodInfo info)
{
final Descriptor descriptor = descriptor(info);
final String name = info.attrName();
final String xmlName = info.xmlName();
descriptor.setField(DESC_XML_NAME, xmlName);
//debug( m.getName() + " => " + name + " => " + xmlName );
if (info.pattern() != null)
{
descriptor.setField(DESC_PATTERN_REGEX, info.pattern());
}
if (info.units() != null)
{
descriptor.setField(DESC_UNITS, info.units());
}
if (info.min() != null)
{
descriptor.setField(DESC_MIN, info.min());
}
if (info.max() != null)
{
descriptor.setField(DESC_MAX, info.max());
}
addAnnotationsToDescriptor(descriptor, info);
descriptor.setField(DESC_NOT_NULL, "" + info.notNull());
Class type = info.returnType();
String description = "@Attribute " + name;
final boolean isReadable = true;
// we assume that all getters are writeable for now
final boolean isWriteable = true;
final boolean isIs = false;
final MBeanAttributeInfo attrInfo =
new MBeanAttributeInfo(name, type.getName(), description, isReadable, isWriteable, isIs, descriptor);
return attrInfo;
}
/** An @Element("*") is anonymous, no specified type, could be anything */
public static final String ANONYMOUS_SUB_ELEMENT = "*";
private static abstract class MethodInfo
{
protected final Method mMethod;
protected final String mAttrName;
protected final String mXMLName;
MethodInfo(final Method m, final String xmlName)
{
mMethod = m;
mAttrName = JMXUtil.getAttributeName(m);
mXMLName = xmlName;
}
public Method method()
{
return mMethod;
}
public String attrName()
{
return mAttrName;
}
public String xmlName()
{
return mXMLName;
}
public Class> returnType()
{
return mMethod.getReturnType();
}
public abstract boolean required();
public abstract boolean key();
/** return ConfigBeanProxy interface, or null if not a ConfigBeanProxy */
public Class extends ConfigBeanProxy> intf()
{
final Class returnType = returnType();
if (ConfigBeanProxy.class.isAssignableFrom(returnType))
{
return returnType.asSubclass(ConfigBeanProxy.class);
}
return null;
}
}
public static final class ElementMethodInfo extends MethodInfo
{
private final Element mElement;
private ElementMethodInfo(final Method m, final Element e)
{
super(m, e.value().length() == 0 ? Util.typeFromName(JMXUtil.getAttributeName(m)) : e.value());
mElement = e;
}
public static ElementMethodInfo get(final Method m)
{
final Element e = m.getAnnotation(Element.class);
return e == null ? null : new ElementMethodInfo(m, e);
}
public Element element()
{
return mElement;
}
public boolean anonymous()
{
return ANONYMOUS_SUB_ELEMENT.equals(xmlName());
}
public List> anonymousTypes()
{
if (!anonymous())
{
return null;
}
final Class> anon = internalReturnType(method());
if (!ConfigBeanProxy.class.isAssignableFrom(anon))
{
return null;
}
//System.out.println( "ANONYMOUS ELEMENT LIST: " + anon );
final Class[] interfaces = getTypesImplementing(anon);
final List> types = ListUtil.newList();
for (final Class clazz : interfaces)
{
types.add(clazz.asSubclass(ConfigBeanProxy.class));
}
return types;
}
public boolean required()
{
return mElement.required();
}
public boolean key()
{
return mElement.key();
}
}
/** works only for @Configured types */
public static Class[] getTypesImplementing(final Class> clazz)
{
final DomDocument domDoc = new DomDocument(InjectedValues.getInstance().getHabitat());
try
{
final List models = domDoc.getAllModelsImplementing(clazz);
final Class[] interfaces = new Class[models == null ? 0 : models.size()];
if (models != null)
{
int i = 0;
for (final ConfigModel model : models)
{
final String classname = model.targetTypeName;
final Class> intf = model.classLoaderHolder.loadClass(classname);
interfaces[i] = intf;
//System.out.println( "Loaded: " + intf + " with tagName of " + model.getTagName() );
++i;
}
}
return interfaces;
}
catch (final Exception e)
{
AMXLoggerInfo.getLogger().log( Level.INFO, AMXLoggerInfo.cantGetTypesImplementing,
new Object[] {clazz, e.getLocalizedMessage()} );
throw new RuntimeException(e);
}
}
private static boolean isIntegral(final String s)
{
if (s.equals("0") || s.equals("1"))
{
return true;
}
try
{
Long.parseLong(s);
return true;
}
catch (Exception e)
{
}
return false;
}
private static final Map UNITS_SUFFIXES = MapUtil.newMap(
"Millis", Units.MILLISECONDS,
"Milliseconds", Units.MILLISECONDS,
"Seconds", Units.SECONDS,
"Hours", Units.HOURS,
"Days", Units.DAYS,
"Bytes", Units.BYTES,
"Kilobytes", Units.KILOBYTES,
"Megabytes", Units.MEGABYTES);
/** Create the default min/max values for primitive types */
private static Map, long[]> makeMIN_MAX()
{
final Map, long[]> m = new HashMap, long[]>();
long[] mm = new long[]
{
Byte.MIN_VALUE, Byte.MAX_VALUE
};
m.put(byte.class, mm);
m.put(Byte.class, mm);
mm = new long[]
{
Short.MIN_VALUE, Short.MAX_VALUE
};
m.put(short.class, mm);
m.put(Short.class, mm);
mm = new long[]
{
Integer.MIN_VALUE, Integer.MAX_VALUE
};
m.put(int.class, mm);
m.put(Integer.class, mm);
mm = new long[]
{
Long.MIN_VALUE, Long.MAX_VALUE
};
m.put(long.class, mm);
m.put(Long.class, mm);
/*
m.put(PositiveInteger.class, new long[] { 1, Integer.MAX_VALUE } );
m.put(NonNegativeInteger.class, new long[] { 0, Integer.MAX_VALUE } );
m.put(Port.class, new long[] { 0, 65535 } );
*/
return m;
}
private static final Map, long[]> MIN_MAX = makeMIN_MAX();
private static long[] minMaxFromDataType(final Class> dataType)
{
return MIN_MAX.get(dataType);
}
public static final class AttributeMethodInfo extends MethodInfo
{
private final Attribute mAttribute;
private AttributeMethodInfo(final Method m, final Attribute a)
{
super(m, a.value().length() == 0 ? Util.typeFromName(JMXUtil.getAttributeName(m)) : a.value());
mAttribute = a;
}
public static AttributeMethodInfo get(final Method m)
{
final Attribute a = m.getAnnotation(Attribute.class);
return a == null ? null : new AttributeMethodInfo(m, a);
}
public Attribute attribute()
{
return mAttribute;
}
public boolean required()
{
return mAttribute.required();
}
public boolean key()
{
return mAttribute.key();
}
public String pattern()
{
final javax.validation.constraints.Pattern pat = mMethod.getAnnotation(javax.validation.constraints.Pattern.class);
return pat == null ? null : pat.regexp();
}
public String units()
{
final Units units = mMethod.getAnnotation(Units.class);
return units == null ? inferUnits() : units.units();
}
public Long min()
{
final Min min = mMethod.getAnnotation(Min.class);
if (min != null)
{
return min.value();
}
final long[] minMax = minMaxFromDataType(attribute().dataType());
return minMax == null ? null : minMax[0];
}
public Long max()
{
final Max max = mMethod.getAnnotation(Max.class);
if (max != null)
{
return max.value();
}
final long[] minMax = minMaxFromDataType(attribute().dataType());
return minMax == null ? null : minMax[1];
}
/** infer the data type, using specified value if present */
public String inferUnits()
{
if (Number.class.isAssignableFrom(inferDataType()))
{
final String attrName = attrName();
for (final String key : UNITS_SUFFIXES.keySet())
{
if (attrName.endsWith(key))
{
return UNITS_SUFFIXES.get(key);
}
}
return Units.COUNT;
}
return null;
}
public boolean hasDefaultValue()
{
final Object defaultValue = attribute().defaultValue();
return defaultValue != null && ! defaultValue.equals( "\u0000" );
}
/** infer the data type, using specified value if present */
public Class> inferDataType()
{
Class> dataType = attribute().dataType();
if (dataType != String.class)
{
// explicitly specified as non-String, use it
return dataType;
}
// infer a Boolean, strictly "true" or "false"
final Object defaultValue = attribute().defaultValue();
if (defaultValue.equals("true") || defaultValue.equals("false"))
{
return Boolean.class;
}
// infer a number
if (max() != null)
{
if (max().equals(Long.MAX_VALUE))
{
return Long.class;
}
else
{
return Integer.class;
}
}
else if (min() != null)
{
return min().equals(Long.MIN_VALUE) ? Long.class : Integer.class;
}
else if (isIntegral("" + defaultValue))
{
return Long.class;
}
return dataType;
}
public boolean notNull()
{
final NotNull n = mMethod.getAnnotation(NotNull.class);
return n != null;
}
public Annotation[] annotations()
{
return method().getAnnotations();
}
}
public static final class DuckTypedInfo
{
private final DuckTyped mDuckTyped;
private final Method mMethod;
DuckTypedInfo(final Method m, final DuckTyped duckTyped)
{
mMethod = m;
mDuckTyped = duckTyped;
}
public DuckTyped duckTyped()
{
return mDuckTyped;
}
public String name()
{
return mMethod.getName();
}
public Class> duck()
{
return mMethod.getDeclaringClass();
}
public Method method()
{
return mMethod;
}
public Class> returnType()
{
return method().getReturnType();
}
public boolean isPseudoAttribute()
{
return name().startsWith("get") || name().startsWith("is") && signature().length == 0;
}
public Class>[] signature()
{
return method().getParameterTypes();
}
public String toString()
{
String paramsString = "";
final Class>[] paramTypes = signature();
if (paramTypes.length != 0)
{
final StringBuilder builder = new StringBuilder();
final String delim = ", ";
for (final Class> paramClass : method().getParameterTypes())
{
builder.append(ClassUtil.stripPackageName(paramClass.getName()) + delim);
}
builder.setLength(builder.length() - delim.length());
paramsString = builder.toString();
}
return ClassUtil.stripPackageName(mMethod.getReturnType().getName()) + " " +
duck().getName() + "." + mMethod.getName() + "(" + paramsString + ")";
}
}
/**
Get the child types, excluding String[] and anonymous.
*/
public Set> childInterfaces()
{
final Set> intfs = childInterfaces(mElementInfos);
return intfs;
}
private static Class> internalReturnType(final Method method)
{
Class returnType = method.getReturnType();
try
{
if (Collection.class.isAssignableFrom(returnType))
{
final Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType)
{
final ParameterizedType pt = (ParameterizedType) genericReturnType;
final Type[] argTypes = pt.getActualTypeArguments();
if (argTypes.length == 1)
{
final Type argType = argTypes[0];
if (argType instanceof Class)
{
returnType = (Class) argType;
}
else
{
throw new IllegalArgumentException();
}
}
}
}
}
catch (final Exception e)
{
System.out.println("AMX ConfigBeanAMXSupport: can't get generic return type for method " +
method.getDeclaringClass().getName() + "." + method.getName() + "(): " + e.getClass().getName() + " = " + e.getMessage());
}
return returnType;
}
/**
Find a matching ElementMethodInfo by class.
*/
public ElementMethodInfo getElementMethodInfo(final Class extends ConfigBeanProxy> intf)
{
ElementMethodInfo match = null;
for (final ElementMethodInfo info : mElementInfos)
{
if (internalReturnType(info.method()) == intf)
{
match = info;
break;
}
}
if ( match == null )
{
// could be somethign generic, list List
for (final ElementMethodInfo info : mElementInfos)
{
if ( internalReturnType(info.method()).isAssignableFrom(intf) )
{
match = info;
break;
}
}
}
return match;
}
public Map> childTypes()
{
final Map> types = new HashMap>();
for (final Class extends ConfigBeanProxy> intf : childInterfaces())
{
types.put(getTypeString(intf), intf);
}
return types;
}
public Class extends ConfigBeanProxy> getConfigBeanProxyClassFor(final String type, final boolean recursive)
{
return childTypes().get(type);
}
public Set> childInterfaces(final List infos)
{
final Set> classes = new HashSet>();
for (final ElementMethodInfo info : infos)
{
if (info.anonymous())
{
final List> types = info.anonymousTypes();
if (types != null)
{
classes.addAll(types);
}
}
else
{
final Class methodReturnType = info.returnType();
Class extends ConfigBeanProxy> intf = null;
if (info.intf() != null)
{
intf = info.intf();
}
else if (Collection.class.isAssignableFrom(methodReturnType))
{
final Type genericReturnType = info.method().getGenericReturnType();
if (genericReturnType instanceof ParameterizedType)
{
final ParameterizedType pt = (ParameterizedType) genericReturnType;
final Type[] argTypes = pt.getActualTypeArguments();
if (argTypes.length == 1)
{
final Type argType = argTypes[0];
if ((argType instanceof Class) && (Class) argType == String.class)
{
// ignore for our purposes here
}
else
{
intf = ((Class) argType).asSubclass(ConfigBeanProxy.class);
}
}
}
}
if (intf != null)
{
classes.add(intf);
}
}
}
return classes;
}
/**
@Elements are represented as Attributes: getters for sub-elements are presented
as ObjectName, Collection presented as String[], Collection extends ConfigBeanProxy>
represented as ObjectName[].
*/
public MBeanAttributeInfo elementToMBeanAttributeInfo(final Method m)
{
// we assume that all getters are writeable for now, not true for sub-elements (ObjectName)
boolean isWriteable = true;
final ElementMethodInfo info = ElementMethodInfo.get(m);
if (info == null || info.anonymous())
{
return null;
}
final String name = info.attrName(); // eg strip the "get"
final String xmlName = info.xmlName();
//debug( m.getName() + " => " + name + " => " + xmlName );
final Class methodReturnType = info.returnType();
Class> returnType = null;
if (info.intf() != null)
{
// some sub-type, which we must represent as an ObjectName
returnType = ObjectName.class;
isWriteable = false;
}
else if (Collection.class.isAssignableFrom(methodReturnType))
{
final Type genericReturnType = m.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType)
{
final ParameterizedType pt = (ParameterizedType) genericReturnType;
final Type[] argTypes = pt.getActualTypeArguments();
if (argTypes.length == 1)
{
final Type argType = argTypes[0];
if ((argType instanceof Class) && (Class) argType == String.class)
{
returnType = String[].class;
}
else
{
returnType = ObjectName[].class;
isWriteable = false;
}
}
}
}
else
{
// some unknown type we cannot handle
}
MBeanAttributeInfo attrInfo = null;
if (returnType != null)
{
final DescriptorSupport descriptor = descriptor(info.element());
descriptor.setField(DESC_ELEMENT_CLASS, returnType.getName());
descriptor.setField(DESC_XML_NAME, xmlName);
final ToDo toDo = info.method().getAnnotation(ToDo.class);
if (toDo != null)
{
descriptor.setField(DESC_CONFIG_PREFIX + "toDo", toDo.priority() + ", " + toDo.details());
}
final PropertiesDesc props = info.method().getAnnotation(PropertiesDesc.class);
if (props != null)
{
final String propType = props.systemProperties() ? "system-property" : "property";
for (final PropertyDesc p : props.props())
{
final String value = p.defaultValue() + " | " + p.dataType().getName() + " | " + p.description();
descriptor.setField(DESC_CONFIG_PREFIX + propType + "." + p.name(), value);
}
}
String description = "@Element " + name + " of interface " + mIntf.getName();
final boolean isReadable = true;
final boolean isIs = false;
attrInfo = new MBeanAttributeInfo(name, returnType.getName(), description, isReadable, isWriteable, isIs, descriptor);
}
return attrInfo;
}
public String getNameHint()
{
return mNameHint.mHint;
}
public boolean nameHintIsElement()
{
return mNameHint.mIsElement;
}
public static String toXMLName(final String name)
{
return name == null ? name : Dom.convertName(name);
}
private final static String DEFAULT_NAME_HINT = "name";
private static final class NameHint
{
public static final NameHint NAME = new NameHint(DEFAULT_NAME_HINT);
public static final NameHint NONE = new NameHint(null);
private final String mHint;
private final boolean mIsElement;
public NameHint(final String hint, final boolean isElement)
{
mHint = toXMLName(hint);
mIsElement = isElement;
}
public NameHint(final String hint)
{
this(hint, false);
}
}
/**
Return the name of the XML attribute which contains the value to be used as its name.
First element is the name hint, 2nd indicates its type
*/
private NameHint findNameHint()
{
if (isSingleton())
{
return NameHint.NONE;
}
if (mKey != null)
{
return new NameHint(mKey);
}
// final String configuredBugKey = configuredBugKey();
for (final AttributeMethodInfo info : mAttrInfos)
{
if (info.key())
{
//debug( "findNameHint: mKey = " + mKey + ", info says " + info.xmlName() );
return new NameHint(info.xmlName());
}
/*
else if (configuredBugKey != null && info.attrName().equalsIgnoreCase(configuredBugKey)) {
//debug( "findNameHint: mKey = " + mKey + ", workaround says " + configuredBugKey );
return new NameHint(configuredBugKey);
}
*/
}
/**
Is this possible?
for (final ElementMethodInfo info : mElements.values()) {
if (info.getElement().key()) {
return new NameHint(info.getName(), true);
}
}
*/
return NameHint.NAME;
}
public Map getDefaultValues(final boolean useAttributeNames)
{
final Map m = new HashMap();
final MBeanInfo info = getMBeanInfo();
for (final MBeanAttributeInfo attrInfo : info.getAttributes())
{
final String defaultValue = defaultValue(attrInfo);
// emit values that exist (only); null is of no use.
if (defaultValue != null)
{
final String attrName = attrInfo.getName();
final String name = useAttributeNames ? attrName : xmlName(attrInfo, attrName);
m.put(name, defaultValue);
}
}
return m;
}
private static void debug(final String s)
{
System.out.println("### " + s);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy