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.internal;
  34 
  35 import java.lang.reflect.Array;
  36 import java.util.ArrayList;
  37 import java.util.Collection;
  38 import java.util.List;
  39 import java.util.concurrent.Callable;
  40 
  41 import javax.management.MBeanFeatureInfo;
  42 import javax.management.MBeanOperationInfo;
  43 import javax.management.MBeanParameterInfo;
  44 import javax.management.MBeanServerConnection;
  45 import javax.management.ObjectName;
  46 
  47 import org.openjdk.jmc.common.util.TypeHandling;
  48 import org.openjdk.jmc.rjmx.services.IOperation;
  49 import org.openjdk.jmc.rjmx.services.IllegalOperandException;
  50 import org.openjdk.jmc.rjmx.services.internal.AbstractOperation;
  51 import org.openjdk.jmc.rjmx.services.internal.Messages;
  52 import org.openjdk.jmc.rjmx.util.internal.SimpleAttributeInfo;
  53 
  54 final class MBeanOperationWrapper extends AbstractOperation<SimpleAttributeInfo> {
  55         private static final String IMPACT = ".vmImpact"; //$NON-NLS-1$
  56         private static final int MAX_DESCRIPTORS = 8;
  57         private static final int MAX_LINE_LENGTH = 100;
  58         private static final String ELLIPSIS_STRING = "..."; //$NON-NLS-1$
  59 
  60         private final MBeanServerConnection connection;
  61         private final ObjectName objectName;
  62 
  63         private MBeanOperationWrapper(MBeanServerConnection connection, ObjectName objectName, MBeanOperationInfo info) {
  64                 super(info.getName(), convertDescription(info), info.getReturnType(), convertArguments(info),
  65                                 convertImpact(info));
  66                 this.connection = connection;
  67                 this.objectName = objectName;
  68         }
  69 
  70         @Override
  71         public Callable<?> getInvocator(final Object ... argVals) throws IllegalOperandException {
  72                 List<SimpleAttributeInfo> params = getSignature();
  73                 final StringBuilder argString = new StringBuilder("("); //$NON-NLS-1$
  74                 if (argVals.length < params.size()) {
  75                         // Argument list is shorter than the signature
  76                         throw new IllegalOperandException(params.subList(argVals.length, params.size()));
  77                 }
  78                 final String[] sig = new String[params.size()];
  79                 for (int i = 0; i < params.size(); i++) {
  80                         sig[i] = params.get(i).getType();
  81                         if (argVals[i] != null) {
  82                                 argString.append(' ').append(describeValue(argVals[i])).append(',');
  83                         } else if (TypeHandling.isPrimitive(sig[i])) {
  84                                 // Argument value of primitive type is null
  85                                 IllegalOperandException ex = new IllegalOperandException(params.get(i));
  86                                 while (++i < params.size()) {
  87                                         // Check for other attributes with the same error
  88                                         if (argVals[i] == null && TypeHandling.isPrimitive(params.get(i).getType())) {
  89                                                 ex.addInvalidValue(params.get(i));
  90                                         }
  91                                 }
  92                                 throw ex;
  93                         }
  94                 }
  95                 if (argString.charAt(argString.length() - 1) == ',') {
  96                         argString.deleteCharAt(argString.length() - 1);
  97                 }
  98                 argString.append(" )"); //$NON-NLS-1$
  99 
 100                 return new Callable<Object>() {
 101 
 102                         @Override
 103                         public Object call() throws Exception {
 104                                 return connection.invoke(objectName, getName(), argVals, sig);
 105                         }
 106 
 107                         @Override
 108                         public String toString() {
 109                                 return getName() + argString.toString().replace("\n", "\\n").replace("\r", "\\r"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
 110                         }
 111                 };
 112         }
 113 
 114         static Collection<IOperation> createOperations(
 115                 ObjectName objectName, MBeanOperationInfo[] operations, MBeanServerConnection connection) {
 116                 List<IOperation> wrappedOperations = new ArrayList<>();
 117                 for (MBeanOperationInfo info : operations) {
 118                         wrappedOperations.add(new MBeanOperationWrapper(connection, objectName, info));
 119                 }
 120                 return wrappedOperations;
 121         }
 122 
 123         /**
 124          * @return {@code string} if it is not {@code null} and it is not an empty or blank string,
 125          *         otherwise {@code defaultString}
 126          */
 127         private static String asNonNullNorEmptyString(String string, String defaultString) {
 128                 return (string != null && string.trim().length() > 0) ? string : defaultString;
 129         }
 130 
 131         private static String describeValue(Object o) {
 132                 if (o.getClass().isArray()) {
 133                         return o.getClass().getComponentType().getName() + "[" + Array.getLength(o) + "]"; //$NON-NLS-1$ //$NON-NLS-2$
 134                 } else if (o instanceof String) {
 135                         return "\"" + o + "\""; //$NON-NLS-1$ //$NON-NLS-2$
 136                 } else {
 137                         return o.toString();
 138                 }
 139         }
 140 
 141         private static OperationImpact convertImpact(MBeanOperationInfo info) {
 142                 for (String name : info.getDescriptor().getFieldNames()) {
 143                         if (name.endsWith(IMPACT)) {
 144                                 Object impactField = info.getDescriptor().getFieldValue(name);
 145                                 if (impactField != null) {
 146                                         String impact = impactField.toString();
 147                                         if (impact.startsWith("Low")) { //$NON-NLS-1$
 148                                                 return OperationImpact.IMPACT_LOW;
 149                                         }
 150                                         if (impact.startsWith("Medium")) { //$NON-NLS-1$
 151                                                 return OperationImpact.IMPACT_MEDIUM;
 152                                         }
 153                                         if (impact.startsWith("High")) { //$NON-NLS-1$
 154                                                 return OperationImpact.IMPACT_HIGH;
 155                                         }
 156                                 }
 157                         }
 158                 }
 159                 return OperationImpact.IMPACT_UNKNOWN;
 160         }
 161 
 162         private static List<SimpleAttributeInfo> convertArguments(MBeanOperationInfo info) {
 163                 MBeanParameterInfo[] sign = info.getSignature();
 164                 List<SimpleAttributeInfo> signature = new ArrayList<>(sign.length);
 165                 for (MBeanParameterInfo paramInfo : sign) {
 166                         // Use name primarily. Use description as name only if name is null or empty.
 167                         String name = asNonNullNorEmptyString(paramInfo.getName(),
 168                                         asNonNullNorEmptyString(paramInfo.getDescription(), "")); //$NON-NLS-1$
 169                         signature.add(new SimpleAttributeInfo(name, paramInfo.getType(), convertDescription(paramInfo)));
 170                 }
 171                 return signature;
 172         }
 173 
 174         private static String convertDescription(MBeanFeatureInfo info) {
 175                 // FIXME: Building tool tips and descriptors should be unified into a toolkit
 176                 StringBuilder sb = new StringBuilder();
 177                 if (info.getDescriptor() != null && info.getDescriptor().getFields() != null) {
 178                         String[] fields = info.getDescriptor().getFields();
 179                         if (fields.length > 0) {
 180                                 // TODO: This is a workaround to get the descriptors to UI-layer. Should be handled using some adaptive mechanism.
 181                                 sb.append(Messages.MBeanOperationsWrapper_DESCRIPTOR).append(":\n "); //$NON-NLS-1$
 182                                 for (int i = 0; i < Math.min(fields.length, MAX_DESCRIPTORS); i++) {
 183                                         String str = fields[i];
 184                                         int cur = 0;
 185                                         int newLine = 0;
 186                                         while (cur < str.length() && newLine != -1) {
 187                                                 newLine = str.indexOf('\n', cur);
 188                                                 if (newLine == -1) {
 189                                                         sb.append(shorten(str.substring(cur))).append("\n "); //$NON-NLS-1$
 190                                                 } else {
 191                                                         sb.append(shorten(str.substring(cur, newLine))).append("\n "); //$NON-NLS-1$
 192                                                         cur = newLine + 1;
 193                                                 }
 194                                         }
 195                                 }
 196                         }
 197                 }
 198                 return shorten(info.getDescription()) + "\n " + sb.toString().trim(); //$NON-NLS-1$
 199         }
 200 
 201         private static String shorten(String s) {
 202                 if (s == null) {
 203                         return ""; //$NON-NLS-1$
 204                 } else if (s.length() > MAX_LINE_LENGTH) {
 205                         return (s.subSequence(0, MAX_LINE_LENGTH - ELLIPSIS_STRING.length()) + ELLIPSIS_STRING);
 206                 } else {
 207                         return s;
 208                 }
 209         }
 210 }