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 }