/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2017 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://oss.oracle.com/licenses/CDDL+GPL-1.1
* or 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 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.
*/
package org.glassfish.admin.amx.core;
import java.util.regex.Pattern;
import org.glassfish.admin.amx.util.jmx.JMXUtil;
import org.glassfish.admin.amx.util.SetUtil;
import org.glassfish.admin.amx.util.ClassUtil;
import org.glassfish.admin.amx.core.AMXMBeanMetadata;
import static org.glassfish.external.amx.AMX.*;
import javax.management.Notification;
import javax.management.ObjectName;
import java.io.Serializable;
import java.util.*;
import javax.management.MBeanServer;
import org.glassfish.external.arc.Stability;
import org.glassfish.external.arc.Taxonomy;
/**
Utility routines pertinent to the MBean API.
*/
@Taxonomy(stability = Stability.UNCOMMITTED)
public final class Util {
private static final String QUOTE_CHAR = "\"";
private static void debug(final String s) {
System.out.println(s);
}
public static String quoteIfNeeded(String name) {
if(name.indexOf(":") > 1) {
return ObjectName.quote(name);
} else {
return name;
}
}
public static String unquoteIfNeeded(String name) {
if(name != null && (name.startsWith(QUOTE_CHAR) && name.endsWith(QUOTE_CHAR))) {
return ObjectName.unquote(name);
} else {
return name;
}
}
private Util() {
}
/**
Create a new ObjectName, caller is guaranteeing that the name is
well-formed (a RuntimeException will be thrown if not). This avoids
having to catch all sorts of JMX exceptions.<br>
<b>NOTE:</b> Do not call this method if there is not certainty of a well-formed name.
@param name
*/
public static ObjectName newObjectName(String name) {
return (JMXUtil.newObjectName(name));
}
/**
Build an ObjectName. Calls newObjectName( domain + ":" + props )
@param domain the JMX domain
@param props properties of the ObjectName
*/
public static ObjectName newObjectName(String domain, String props) {
return (newObjectName(domain + ":" + props));
}
/**
Build an ObjectName pattern.
@param domain the JMX domain
@param props properties of the ObjectName
*/
public static ObjectName newObjectNamePattern(
final String domain,
final String props) {
return (JMXUtil.newObjectNamePattern(domain, props));
}
/**
Build an ObjectName pattern.
@param objectName
*/
public static ObjectName newObjectNamePattern(ObjectName objectName) {
final String props = objectName.getKeyPropertyListString();
return (newObjectNamePattern(objectName.getDomain(), props));
}
/**
Make an ObjectName property of the form <i>name</i>=<i>value</i>.
@param name
@param value
*/
public static String makeProp(
final String name,
final String value) {
return (JMXUtil.makeProp(name, value));
}
/**
Make an ObjectName property of the form type=<i>value</i>.
@param value
*/
public static String makeTypeProp(final String value) {
return (makeProp(TYPE_KEY, value));
}
/**
Make an ObjectName property of the form name=<i>value</i>.
*/
public static String makeNameProp(final String name) {
return (makeProp(NAME_KEY, "" + quoteIfNeeded(name)));
}
/**
@param type
@param name
*/
public static String makeRequiredProps(
final String type,
final String name) {
String props = Util.makeTypeProp(type);
if (!(name == null || name.length() == 0 || name.equals(NO_NAME))) {
final String nameProp = Util.makeNameProp(name);
props = Util.concatenateProps(props, nameProp);
}
return (props);
}
/**
Extract the type and name properties and return it as a single property
<i>type</i>=<i>name</i>
@param objectName
*/
public static String getSelfProp(final ObjectName objectName) {
final String type = objectName.getKeyProperty(TYPE_KEY);
final String name = objectName.getKeyProperty(NAME_KEY);
return (Util.makeProp(type, name));
}
/**
Extract all properties other than type=<type>,name=<name>.
@param objectName
*/
public static String getAdditionalProps(final ObjectName objectName) {
final java.util.Hashtable<String,String> allProps = objectName.getKeyPropertyList();
allProps.remove(TYPE_KEY);
allProps.remove(NAME_KEY);
String props = "";
for (final Map.Entry<String,String> e : allProps.entrySet()) {
final String prop = makeProp(e.getKey(), e.getValue());
props = concatenateProps(props, prop);
}
return props;
}
public static String concatenateProps(
final String props1,
final String props2) {
return (JMXUtil.concatenateProps(props1, props2));
}
public static String concatenateProps(
final String props1,
final String props2,
final String props3) {
return (concatenateProps(concatenateProps(props1, props2), props3));
}
/**
@return a List of ObjectNames from a Set of AMX.
*/
public static List<ObjectName> toObjectNameList(final Collection<? extends AMXProxy> amxs) {
final List<ObjectName> objectNames = new ArrayList<ObjectName>();
for (final AMXProxy next : amxs) {
objectNames.add(next.objectName());
}
return (Collections.checkedList(objectNames, ObjectName.class));
}
/**
@return a Map of ObjectNames from a Map whose values are AMX.
*/
public static Map<String, ObjectName> toObjectNameMap(final Map<String, ? extends AMXProxy> amxMap) {
final Map<String, ObjectName> m = new HashMap<String, ObjectName>();
for (final Map.Entry<String,? extends AMXProxy> e : amxMap.entrySet()) {
final AMXProxy value = e.getValue();
m.put(e.getKey(), value.objectName());
}
return (Collections.checkedMap(m, String.class, ObjectName.class));
}
/**
@return an ObjectName[] from an AMX[]
*/
public static ObjectName[] toObjectNamesArray(final AMXProxy[] amx) {
final ObjectName[] objectNames = new ObjectName[amx.length];
for (int i = 0; i < objectNames.length; ++i) {
objectNames[i] = amx[i] == null ? null : amx[i].objectName();
}
return (objectNames);
}
public static ObjectName[] toObjectNamesArray(final Collection<? extends AMXProxy> amxs) {
final ObjectName[] objectNames = new ObjectName[amxs.size()];
int i = 0;
for (final AMXProxy amx : amxs) {
objectNames[i] = amx.objectName();
++i;
}
return (objectNames);
}
/**
Create a Map keyed by the value of the NAME_KEY with
value the AMX item.
@param amxs Set of AMX
*/
public static <T extends AMXProxy> Map<String, T> createNameMap(final Set<T> amxs) {
final Map<String, T> m = new HashMap<String, T>();
for (final T amx : amxs) {
final String name = amx.getName();
m.put(name, amx);
}
return (m);
}
/**
Create a Map keyed by the value of the NAME_KEY with
value the ObjectName. Note that if two or more ObjectNames
share the same name, the resulting Map will contain only
one of the original ObjectNames.
@param objectNames Set of ObjectName
*/
public static final Map<String, ObjectName> createObjectNameMap(final Set<ObjectName> objectNames) {
final Map<String, ObjectName> m = new HashMap<String, ObjectName>();
for (final ObjectName objectName : objectNames) {
final String name = getNameProp(objectName);
assert (!m.containsKey(name)) :
"createObjectNameMap: key already present: " + name + " in " + objectName;
m.put(name, objectName);
}
assert (m.keySet().size() == objectNames.size());
return (Collections.checkedMap(m, String.class, ObjectName.class));
}
public static <T extends AMXProxy> List<T> asProxyList(final Collection<? extends AMXProxy> c, final Class<T> intf) {
final List<T> list = new ArrayList<T>();
for (final AMXProxy amx : c) {
list.add(amx.as(intf));
}
return list;
}
/**
All Notifications emitted by AMX MBeans which are not
standard types defined by JMX place a Map
into the userData field of the Notification. This call
retrieves that Map, which may be null if no additional
data is included.
*/
public static Map<String, Serializable> getAMXNotificationData(final Notification notif) {
return Collections.unmodifiableMap(
JMXUtil.getUserDataMapString_Serializable(notif));
}
/**
Use of generic type form taking Class<T> is preferred.
*/
public static Serializable getAMXNotificationValue(final Notification notif, final String key) {
final Map<String, Serializable> data =
getAMXNotificationData(notif);
if (data == null) {
throw new IllegalArgumentException(notif.toString());
}
if (!data.containsKey(key)) {
throw new IllegalArgumentException("Value not found for " + key
+ " in " + notif);
}
return data.get(key);
}
/**
Retrieve a particular value associated with the specified
key from an AMX Notification.
@see #getAMXNotificationData
*/
public static <T extends Serializable> T getAMXNotificationValue(
final Notification notif,
final String key,
final Class<T> theClass) {
final Serializable value = getAMXNotificationValue(notif, key);
return theClass.cast(value);
}
public static void sleep(final long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
}
}
/**
A safe way to cast to AMX.
*/
public static AMXProxy asAMX(final Object o) {
return AMXProxy.class.cast(o);
}
/**
Filter the AMX dynamic proxies to those that implement the specified interface,
and return a new Set with the matching items. The 'desired' interface can be
any AMX-defined class, including the mixin ones.
@param candidates the Set to be filtered
@param desired the interface to filter by
*/
public static <T extends AMXProxy> Set<T> filterAMX(final Set<T> candidates, final Class<?> desired) {
final Set<T> result = new HashSet<T>();
for (final T amx : candidates) {
if (desired.isAssignableFrom(amx.getClass())) {
result.add(amx);
}
}
return result;
}
public static Map<String, ObjectName> filterByType(final ObjectName[] objectNames, final String type) {
Map<String, ObjectName> m = null;
if (objectNames != null) {
m = new HashMap<String, ObjectName>();
for (final ObjectName o : objectNames) {
if (type.equals(o.getKeyProperty(TYPE_KEY))) {
// use the name property, not the name from getNameProp()
m.put(o.getKeyProperty(NAME_KEY), o);
}
}
}
return m;
}
/**
Filter the AMX dynamic proxies to those that implement the specified interface,
and return a new Map with the matching items. The 'desired' interface can be
any AMX-defined class, including the mixin ones.
@param candidates the Map to be filtered
@param desired the interface to filter by
*/
public static <T extends AMXProxy> Map<String, T> filterAMX(final Map<String, T> candidates, final Class<?> desired) {
final Map<String, T> result = new HashMap<String, T>();
for (final Map.Entry<String,T> e : candidates.entrySet()) {
final T amx = e.getValue();
if (desired.isAssignableFrom(amx.getClass())) {
result.put(e.getKey(), amx);
}
}
return result;
}
public static String getTypeProp(final ObjectName objectName) {
return objectName.getKeyProperty(TYPE_KEY);
}
/**
Get the value of the NAME_KEY property within the ObjectName, or null if
not present.
@return the name
*/
public static String getNameProp(final ObjectName objectName) {
return objectName.getKeyProperty(NAME_KEY);
}
public static String getParentPathProp(final ObjectName objectName) {
return objectName.getKeyProperty(PARENT_PATH_KEY);
}
public static String getParentPathProp(final AMXProxy amx) {
return getParentPathProp(amx.extra().objectName());
}
public static ObjectName getParent(final MBeanServer server, final ObjectName objectName) {
return (ObjectName) JMXUtil.getAttribute(server, objectName, ATTR_PARENT);
}
/**
Generate the default MBean type from a String, eg from a classname.
*/
public static String typeFromName(final String s) {
if (s.indexOf("-") >= 0) {
return s; // if it already has dashes, leave unchanged
}
String simpleName = s;
final int idx = s.lastIndexOf(".");
if (idx >= 0) {
simpleName = s.substring(idx + 1);
}
return domConvertName(simpleName);
}
/** Proxy interfaces may contain a type field denoting the type to be used in the ObjectName;
* this is an alternative to an annotation that may be desirable to avoid
* a dependency on the amx-core module. Some proxy interfaces also represent
* MBeans whose type and other metadata is derived not from the proxy interface,
* but from another authoritative source; this allows an explicit
* linkage that allows the AMXProxyHandler to deduce the correct type, given
* the interface (and avoids any further complexity, the KISS principle).
* eg public static final String AMX_TYPE = "MyType";
* <p>
* A good example of this is the config MBeans which use lower case types with dashes. Other
* types may use classnames, or other variants; the proxy code can't assume any particular
* mapping from a proxy interface to the actual MBean type.
*/
public static final String TYPE_FIELD = "AMX_TYPE";
/**
Deduce the type to be used in the path. Presence of a TYPE_FIELD field always
take precedence, then the AMXMBeanMetadata.
*/
public static String deduceType(final Class<?> intf) {
if (intf == null) {
throw new IllegalArgumentException("null interface");
}
String type = null;
AMXMBeanMetadata meta = null;
final Object typeField = ClassUtil.getFieldValue(intf, TYPE_FIELD);
if (typeField instanceof String) {
type = (String) typeField;
} else if ((meta = intf.getAnnotation(AMXMBeanMetadata.class)) != null) {
final String typeValue = meta.type();
if (typeValue.equals(AMXMBeanMetadata.NULL) || typeValue.length() == 0) {
type = Util.typeFromName(intf.getName());
} else {
type = typeValue;
}
} else {
// no annotation, use our default conversion
type = Util.typeFromName(intf.getName());
}
return type;
}
public static ObjectName getAncestorByType(final MBeanServer mbeanServer, final ObjectName child, final String type) {
ObjectName cur = child;
while ((cur = (ObjectName) JMXUtil.getAttribute(mbeanServer, cur, ATTR_PARENT)) != null) {
if (getTypeProp(cur).equals(type)) {
break;
}
}
return cur;
}
// BEGIN domConvertName
/* Code below is taken from Dom.java to avoid a dependency */
static final String[] PROPERTY_PREFIX = new String[]{"get", "set", "is", "has"};
private static String domConvertName(final String nameIn) {
String name = nameIn;
for (final String p : PROPERTY_PREFIX) {
if (name.startsWith(p)) {
name = name.substring(p.length());
break;
}
}
// tokenize by finding 'x|X' and 'X|Xx' then insert '-'.
final StringBuilder buf = new StringBuilder(name.length() + 5);
for (final String t : TOKENIZER.split(name)) {
if (buf.length() > 0) {
buf.append('-');
}
buf.append(t.toLowerCase(Locale.ENGLISH));
}
return buf.toString();
}
/**
* Used to tokenize the property name into XML name.
*/
static final Pattern TOKENIZER;
private static String split(String lookback, String lookahead) {
return "((?<=" + lookback + ")(?=" + lookahead + "))";
}
private static String or(String... tokens) {
final StringBuilder buf = new StringBuilder();
for (String t : tokens) {
if (buf.length() > 0) {
buf.append('|');
}
buf.append(t);
}
return buf.toString();
}
static {
String pattern = or(
split("x", "X"), // AbcDef -> Abc|Def
split("X", "Xx"), // USArmy -> US|Army
//split("\\D","\\d"), // SSL2 -> SSL|2
split("\\d", "\\D") // SSL2Connector -> SSL|2|Connector
);
pattern = pattern.replace("x", "\\p{Lower}").replace("X", "\\p{Upper}");
TOKENIZER = Pattern.compile(pattern);
}
// END domConvertName
}