/** * VMware Continuent Tungsten Replicator * Copyright (C) 2015 VMware, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Initial developer(s): Edward Archibald * Contributor(s): Linas Virbalas */ package com.continuent.tungsten.common.jmx; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.LinkedHashMap; import java.util.Map; import java.util.Vector; import javax.management.MBeanOperationInfo; import com.continuent.tungsten.common.utils.CLLogLevel; import com.continuent.tungsten.common.utils.CLUtils; import com.continuent.tungsten.common.utils.ReflectUtils; /** * @author <a href="mailto:edward.archibald@continuent.com">Edward Archibald</a> * @version 1.0 */ public class DynamicMBeanOperation implements Serializable { /** * */ private static final long serialVersionUID = 1L; private String name = null; private String description = null; private String usage = null; private Vector<String> signature = new Vector<String>(); private Map<String, DynamicMBeanParam> params = new LinkedHashMap<String, DynamicMBeanParam>(); /** * @param method * @param info */ public DynamicMBeanOperation(Method method, MBeanOperationInfo info) { name = method.getName(); description = name; setParamsAndSignature(method, info); setUsage(defaultUsage()); MethodDesc mdesc = (MethodDesc) method.getAnnotation(MethodDesc.class); if (mdesc != null) { if (!mdesc.description().equals("")) description = mdesc.description(); else description = name; if (mdesc.usage() != null || mdesc.usage().length() > 0) { setUsage(mdesc.usage()); } } } /** * @param method * @param info */ private void setParamsAndSignature(Method method, MBeanOperationInfo info) { Class<?>[] paramTypes = method.getParameterTypes(); Annotation[][] paramAnnotations = method.getParameterAnnotations(); for (int i = 0; i < paramTypes.length; i++) { String name = String.format("param%d", i + 1); String description = ""; String displayName = name; Class<?> type = paramTypes[i]; for (Annotation annotation : paramAnnotations[i]) { if (annotation instanceof ParamDesc) { ParamDesc desc = (ParamDesc) annotation; name = desc.name(); if (!desc.description().equals("")) { description = desc.description(); } else { description = name; } if (!desc.displayName().equals("")) { displayName = desc.displayName(); } else { displayName = name; } } } DynamicMBeanParam param = new DynamicMBeanParam(name, i, displayName, description, type); // We put them in the map by display name params.put(displayName, param); signature.add(type.getName()); } } /** * @return the signature of this operation */ public String[] getSignature() { String[] signatureArray = new String[signature.size()]; for (int i = 0; i < signature.size(); i++) signatureArray[i] = signature.get(i); return signatureArray; } /** * This method converts a map of named parameters into a position-ordered * set of parameters. At the same time that it is doing this, it verifies * that all parameters have been supplied, and that the types are correct. * * @param paramMap * @return an array of objects in param order */ public Object[] validateAndGetNamedParams(Map<String, Object> paramMap) throws Exception { if (paramMap == null) { if (params.size() > 0) { throw new Exception( String.format( "No parameters passed to validateAndGetNamedParams but %d were required", params.size())); } } if (params.size() == 0) { return null; } Object[] mbeanParams = new Object[params.size()]; LinkedHashMap<String, DynamicMBeanParam> copyParams = new LinkedHashMap<String, DynamicMBeanParam>( params); for (String paramName : paramMap.keySet()) { Object paramValue = paramMap.get(paramName); DynamicMBeanParam mbeanParam = copyParams.get(paramName); if (mbeanParam == null) { throw new Exception( String.format("No parameter %s found for method %s.", paramName, name)); } // We have a parameter match, now check the datatype // match. if (paramValue != null) { CLUtils.println( String.format( "Checking datatype for param %s: value type=%s, param type=%s", paramName, paramValue.getClass() .getSimpleName(), mbeanParam.getType() .getSimpleName()), CLLogLevel.debug); if (paramValue.getClass() != mbeanParam.getType()) { try { // Determine whether we can do some parsing, so the // later JMX call would not throw an invalid cast // exception. if (mbeanParam.getType().isPrimitive()) { CLUtils.println( String.format( "Testing for castability of param %s value %s to String", paramName, paramValue), CLLogLevel.debug); Class<?> wrapperClass = ReflectUtils .primitiveToWrapper(mbeanParam.getType()); paramValue = wrapperClass.getConstructor( String.class).newInstance(paramValue); } } catch (Exception e) { if (CLUtils.getLogLevel().ordinal() >= CLLogLevel.debug .ordinal()) { CLUtils.println(String .format("EXCEPTION: Validating params for operation %s\n%s", getUsage(), e.toString())); e.printStackTrace(); } // Ignore any exception as this is a usability // increasing step. We pass the argument as is in case // of exception. } } } mbeanParams[mbeanParam.getOrder()] = paramValue; copyParams.remove(paramName); } // Finally, check to make sure all required params were supplied. if (!copyParams.isEmpty()) { throw new Exception( String.format( "Parameters passed in were missing required parameters. Usage: %s\nMissing params:\n%s", defaultUsage(), CLUtils .iterableToCommaSeparatedList(copyParams .keySet()))); } return mbeanParams; } public Object[] validateAndGetPositionalParams(Map<String, Object> paramMap) throws Exception { if (paramMap == null) { if (params.size() > 0) { throw new Exception(String.format( "No parameters passed but %d were required", params.size())); } } if (params.size() == 0) { return null; } Object[] mbeanParams = new Object[paramMap.size()]; // Object[] copyMBeanParams = (Object[]) (params.values().toArray()); int i = 0; boolean inString = false; @SuppressWarnings("unused") String aggregateString = ""; for (Object param : paramMap.values()) { // Determine whether we can do some parsing, so the // later JMX call would not throw an invalid cast // exception. try { // DynamicMBeanParam mbeanParam = (DynamicMBeanParam) // copyMBeanParams[i]; if (param != null) { // Parse null. if (param.getClass() == String.class) { if (!inString) { if (param.toString().compareTo("null") == 0) { param = null; } else if (param.toString().compareTo("true") == 0) { param = true; } else if (param.toString().compareTo("false") == 0) { param = false; } else if (param.toString().startsWith("\"")) { inString = true; aggregateString = param.toString(); } } else { aggregateString += " " + param.toString(); if (param.toString().endsWith("\"")) { inString = false; } } } } } catch (Exception e) { CLUtils.println(e.toString()); } mbeanParams[i++] = param; } return mbeanParams; } /** * {@inheritDoc} * * @see java.lang.Object#toString() */ @Override public String toString() { return getUsage(); } /** * Single line representation of parameters. */ public String formatParams(Map<String, DynamicMBeanParam> params, boolean includeDescription) { return formatParams(params, includeDescription, false, null); } /** * @param multiLine Format parameters to multiple lines? More tidy but * consumes more text space. * @param linePrefix String to use before every line. Used with * multiLine=true. */ private String formatParams(Map<String, DynamicMBeanParam> params, boolean includeDescription, boolean multiLine, String linePrefix) { StringBuilder builder = new StringBuilder(); for (DynamicMBeanParam param : params.values()) { if (builder.length() > 0) { if (multiLine) builder.append("\n"); else builder.append(", "); } if (includeDescription) { if (multiLine) { if (linePrefix != null) builder.append(linePrefix); builder.append(String.format("%-25s : %s", param, param.getDescription())); } else builder.append(String.format("%20s : %s", param, param.getDescription())); } else { builder.append(String.format("%s", param)); } } return builder.toString(); } public String getParamDescription(boolean multiLine, String linePrefix) { return formatParams(params, true, multiLine, linePrefix); } /** * Returns the name value. * * @return Returns the name. */ public String getName() { return name; } /** * Sets the name value. * * @param name The name to set. */ public void setName(String name) { this.name = name; } /** * Returns the description value. * * @return Returns the description. */ public String getDescription() { return description; } /** * Sets the description value. * * @param description The description to set. */ public void setDescription(String description) { this.description = description; } /** * Returns the usage value. * * @return Returns the usage. */ public String getUsage() { return usage; } /** * Sets the usage value. * * @param usage The usage to set. */ public void setUsage(String usage) { this.usage = usage; } /** * Sets the signature value. * * @param signature The signature to set. */ public void setSignature(Vector<String> signature) { this.signature = signature; } public String defaultUsage() { StringBuilder builder = new StringBuilder(); builder.append(String.format("%s", getName())); if (params.size() == 0) { builder.append("()"); return builder.toString(); } builder.append("("); int paramCount = 0; for (DynamicMBeanParam param : params.values()) { builder.append(String.format("%s %s", param.getName(), param .getType().getSimpleName())); if (++paramCount < params.size()) { builder.append(", "); } } builder.append(")"); return builder.toString(); } }