1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * The contents of this file are subject to the terms of either the Universal Permissive License 7 * v 1.0 as shown at http://oss.oracle.com/licenses/upl 8 * 9 * or the following license: 10 * 11 * Redistribution and use in source and binary forms, with or without modification, are permitted 12 * provided that the following conditions are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions 15 * and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 18 * conditions and the following disclaimer in the documentation and/or other materials provided with 19 * the distribution. 20 * 21 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 22 * endorse or promote products derived from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 26 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 31 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 package org.openjdk.jmc.rjmx.services.internal; 34 35 import java.io.IOException; 36 import java.io.NotSerializableException; 37 import java.io.ObjectInputStream; 38 import java.io.ObjectOutputStream; 39 import java.lang.reflect.Array; 40 import java.util.ArrayList; 41 import java.util.List; 42 import java.util.concurrent.Callable; 43 44 import javax.management.MBeanFeatureInfo; 45 import javax.management.MBeanOperationInfo; 46 import javax.management.MBeanParameterInfo; 47 import javax.management.MBeanServerConnection; 48 import javax.management.ObjectName; 49 50 import org.openjdk.jmc.common.util.TypeHandling; 51 import org.openjdk.jmc.rjmx.services.IOperation; 52 import org.openjdk.jmc.rjmx.services.IOperation.OperationImpact; 53 import org.openjdk.jmc.rjmx.services.IllegalOperandException; 54 import org.openjdk.jmc.rjmx.util.internal.SimpleAttributeInfo; 55 56 public final class MBeanOperationsWrapper extends ArrayList<IOperation> { 57 58 private static final int MAX_DESCRIPTORS = 8; 59 private static final int MAX_LINE_LENGTH = 100; 60 private static final String ELLIPSIS_STRING = "..."; //$NON-NLS-1$ 61 private static final long serialVersionUID = -8499920218074514535L; 62 private static final String IMPACT = ".vmImpact"; //$NON-NLS-1$ 63 private final ObjectName bean; 64 private final transient MBeanServerConnection connection; 65 66 public MBeanOperationsWrapper(ObjectName bean, MBeanOperationInfo[] operations, MBeanServerConnection connection) { 67 super(operations.length); 68 this.connection = connection; 69 this.bean = bean; 70 for (MBeanOperationInfo info : operations) { 71 add(new MBeanOperation(info)); 72 } 73 } 74 75 private static String convertDescription(MBeanFeatureInfo info) { 76 // FIXME: Building tool tips and descriptors should be unified into a toolkit 77 StringBuilder sb = new StringBuilder(); 78 if (info.getDescriptor() != null && info.getDescriptor().getFields() != null) { 79 String[] fields = info.getDescriptor().getFields(); 80 if (fields.length > 0) { 81 // TODO: This is a workaround to get the descriptors to UI-layer. Should be handled using some adaptive mechanism. 82 sb.append(Messages.MBeanOperationsWrapper_DESCRIPTOR).append(":\n "); //$NON-NLS-1$ 83 for (int i = 0; i < Math.min(fields.length, MAX_DESCRIPTORS); i++) { 84 String str = fields[i]; 85 int cur = 0; 86 int newLine = 0; 87 while (cur < str.length() && newLine != -1) { 88 newLine = str.indexOf('\n', cur); 89 if (newLine == -1) { 90 sb.append(shorten(str.substring(cur))).append("\n "); //$NON-NLS-1$ 91 } else { 92 sb.append(shorten(str.substring(cur, newLine))).append("\n "); //$NON-NLS-1$ 93 cur = newLine + 1; 94 } 95 } 96 } 97 } 98 } 99 return shorten(info.getDescription()) + "\n " + sb.toString().trim(); //$NON-NLS-1$ 100 } 101 102 private static String shorten(String s) { 103 if (s == null) { 104 return ""; //$NON-NLS-1$ 105 } else if (s.length() > MAX_LINE_LENGTH) { 106 return (s.subSequence(0, MAX_LINE_LENGTH - ELLIPSIS_STRING.length()) + ELLIPSIS_STRING); 107 } else { 108 return s; 109 } 110 } 111 112 private static List<SimpleAttributeInfo> convertArguments(MBeanOperationInfo info) { 113 MBeanParameterInfo[] sign = info.getSignature(); 114 List<SimpleAttributeInfo> signature = new ArrayList<>(sign.length); 115 for (MBeanParameterInfo paramInfo : sign) { 116 // Use name primarily. Use description as name only if name is null or empty. 117 String name = asNonNullNorEmptyString(paramInfo.getName(), 118 asNonNullNorEmptyString(paramInfo.getDescription(), "")); //$NON-NLS-1$ 119 signature.add(new SimpleAttributeInfo(name, paramInfo.getType(), convertDescription(paramInfo))); 120 } 121 return signature; 122 } 123 124 /** 125 * @return {@code string} if it is not {@code null} and it is not an empty or blank string, 126 * otherwise {@code defaultString} 127 */ 128 private static String asNonNullNorEmptyString(String string, String defaultString) { 129 return (string != null && string.trim().length() > 0) ? string : defaultString; 130 } 131 132 private static OperationImpact convertImpact(MBeanOperationInfo info) { 133 for (String name : info.getDescriptor().getFieldNames()) { 134 if (name.endsWith(IMPACT)) { 135 Object impactField = info.getDescriptor().getFieldValue(name); 136 if (impactField != null) { 137 String impact = impactField.toString(); 138 if (impact.startsWith("Low")) { //$NON-NLS-1$ 139 return OperationImpact.IMPACT_LOW; 140 } 141 if (impact.startsWith("Medium")) { //$NON-NLS-1$ 142 return OperationImpact.IMPACT_MEDIUM; 143 } 144 if (impact.startsWith("High")) { //$NON-NLS-1$ 145 return OperationImpact.IMPACT_HIGH; 146 } 147 } 148 } 149 } 150 return OperationImpact.IMPACT_UNKNOWN; 151 } 152 153 private class MBeanOperation extends AbstractOperation<SimpleAttributeInfo> { 154 155 public MBeanOperation(MBeanOperationInfo info) { 156 super(info.getName(), convertDescription(info), info.getReturnType(), convertArguments(info), 157 convertImpact(info)); 158 } 159 160 @Override 161 public Callable<?> getInvocator(final Object ... argVals) throws IllegalOperandException { 162 List<SimpleAttributeInfo> params = getSignature(); 163 final StringBuilder argString = new StringBuilder("("); //$NON-NLS-1$ 164 if (argVals.length < params.size()) { 165 // Argument list is shorter than the signature 166 throw new IllegalOperandException(params.subList(argVals.length, params.size())); 167 } 168 final String[] sig = new String[params.size()]; 169 for (int i = 0; i < params.size(); i++) { 170 sig[i] = params.get(i).getType(); 171 if (argVals[i] != null) { 172 argString.append(' ').append(describeValue(argVals[i])).append(','); 173 } else if (TypeHandling.isPrimitive(sig[i])) { 174 // Argument value of primitive type is null 175 IllegalOperandException ex = new IllegalOperandException(params.get(i)); 176 while (++i < params.size()) { 177 // Check for other attributes with the same error 178 if (argVals[i] == null && TypeHandling.isPrimitive(params.get(i).getType())) { 179 ex.addInvalidValue(params.get(i)); 180 } 181 } 182 throw ex; 183 } 184 } 185 if (argString.charAt(argString.length() - 1) == ',') { 186 argString.deleteCharAt(argString.length() - 1); 187 } 188 argString.append(" )"); //$NON-NLS-1$ 189 190 return new Callable<Object>() { 191 192 @Override 193 public Object call() throws Exception { 194 return connection.invoke(bean, getName(), argVals, sig); 195 } 196 197 @Override 198 public String toString() { 199 return getName() + argString.toString().replace("\n", "\\n").replace("\r", "\\r"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ 200 } 201 }; 202 } 203 } 204 205 private static String describeValue(Object o) { 206 if (o.getClass().isArray()) { 207 return o.getClass().getComponentType().getName() + "[" + Array.getLength(o) + "]"; //$NON-NLS-1$ //$NON-NLS-2$ 208 } else if (o instanceof String) { 209 return "\"" + o + "\""; //$NON-NLS-1$ //$NON-NLS-2$ 210 } else { 211 return o.toString(); 212 } 213 } 214 215 private void writeObject(ObjectOutputStream out) throws IOException { 216 throw new NotSerializableException("MBeanOperationsWrappers should not be serialized!"); //$NON-NLS-1$ 217 } 218 219 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 220 throw new NotSerializableException("MBeanOperationsWrappers should not be serialized!"); //$NON-NLS-1$ 221 } 222 }