package org.jolokia.handler;
/*
* Copyright 2009-2013 Roland Huss
*
* 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.
*/
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.management.*;
import javax.management.openmbean.OpenMBeanParameterInfo;
import javax.management.openmbean.OpenType;
import org.jolokia.converter.*;
import org.jolokia.request.*;
import org.jolokia.restrictor.Restrictor;
import org.jolokia.util.RequestType;
/**
* Handler for dealing with execute requests.
*
* @author roland
* @since Jun 12, 2009
*/
public class ExecHandler extends JsonRequestHandler<JmxExecRequest> {
private Converters converters;
/**
* Constructor
* @param pRestrictor restrictor for checking access restrictions
* @param pConverters converters for serialization
*/
public ExecHandler(Restrictor pRestrictor, Converters pConverters) {
super(pRestrictor);
converters = pConverters;
}
/** {@inheritDoc} */
@Override
public RequestType getType() {
return RequestType.EXEC;
}
/** {@inheritDoc} */
@Override
protected void checkForRestriction(JmxExecRequest pRequest) {
if (!getRestrictor().isOperationAllowed(pRequest.getObjectName(),pRequest.getOperation())) {
throw new SecurityException("Operation " + pRequest.getOperation() +
" forbidden for MBean " + pRequest.getObjectNameAsString());
}
}
/**
* Execute an JMX operation. The operation name is taken from the request, as well as the
* arguments to use. If the operation is given in the format "op(type1,type2,...)"
* (e.g "getText(java.lang.String,int)" then the argument types are taken into account
* as well. This way, overloaded JMX operation can be used. If an overloaded JMX operation
* is called without specifying the argument types, then an exception is raised.
*
*
* @param server server to try
* @param request request to process from where the operation and its arguments are extracted.
* @return the return value of the operation call
*/
@Override
public Object doHandleRequest(MBeanServerConnection server, JmxExecRequest request)
throws InstanceNotFoundException, AttributeNotFoundException, ReflectionException, MBeanException, IOException {
OperationAndParamType types = extractOperationTypes(server,request);
int nrParams = types.paramClasses.length;
Object[] params = new Object[nrParams];
List<Object> args = request.getArguments();
verifyArguments(request, types, nrParams, args);
for (int i = 0;i < nrParams; i++) {
if (types.paramOpenTypes != null && types.paramOpenTypes[i] != null) {
params[i] = converters.getToOpenTypeConverter().convertToObject(types.paramOpenTypes[i], args.get(i));
} else {
params[i] = converters.getToObjectConverter().prepareValue(types.paramClasses[i], args.get(i));
}
}
// TODO: Maybe allow for a path as well which could be applied on the return value ...
return server.invoke(request.getObjectName(),types.operationName,params,types.paramClasses);
}
// check whether the given arguments are compatible with the signature and if not so, raise an excepton
private void verifyArguments(JmxExecRequest request, OperationAndParamType pTypes, int pNrParams, List<Object> pArgs) {
if ( (pNrParams > 0 && pArgs == null) || (pArgs != null && pArgs.size() != pNrParams)) {
throw new IllegalArgumentException("Invalid number of operation arguments. Operation " +
request.getOperation() + " on " + request.getObjectName() + " requires " + pTypes.paramClasses.length +
" parameters, not " + (pArgs == null ? 0 : pArgs.size()) + " as given");
}
}
/**
* Extract the operation and type list from a given request
*
* @param pServer server from which obtain the MBean type info
* @param pRequest the exec request
* @return combined object containing the operation name and parameter classes
*/
private OperationAndParamType extractOperationTypes(MBeanServerConnection pServer, JmxExecRequest pRequest)
throws ReflectionException, InstanceNotFoundException, IOException {
if (pRequest.getOperation() == null) {
throw new IllegalArgumentException("No operation given for exec Request on MBean " + pRequest.getObjectName());
}
List<String> opArgs = splitOperation(pRequest.getOperation());
String operation = opArgs.get(0);
List<String> types;
if (opArgs.size() > 1) {
if (opArgs.size() == 2 && opArgs.get(1) == null) {
// Empty signature requested
types = Collections.emptyList();
} else {
types = opArgs.subList(1,opArgs.size());
}
} else {
List<MBeanParameterInfo[]> paramInfos = extractMBeanParameterInfos(pServer, pRequest, operation);
if (paramInfos.size() == 1) {
return new OperationAndParamType(operation,paramInfos.get(0));
} else {
// type requested from the operation
throw new IllegalArgumentException(
getErrorMessageForMissingSignature(pRequest, operation, paramInfos));
}
}
List<MBeanParameterInfo[]> paramInfos = extractMBeanParameterInfos(pServer, pRequest, operation);
MBeanParameterInfo[] matchingSignature = getMatchingSignature(types, paramInfos);
if (matchingSignature == null) {
throw new IllegalArgumentException(
"No operation " + pRequest.getOperation() + " on MBean " + pRequest.getObjectNameAsString() + " exists. " +
"Known signatures: " + signatureToString(paramInfos));
}
return new OperationAndParamType(operation, matchingSignature);
}
/**
* Extract a list of operation signatures which match a certain operation name. The returned list
* can contain multiple signature in case of overloaded JMX operations.
*
* @param pServer server from where to fetch the MBean info for a given request's object name
* @param pRequest the JMX request
* @param pOperation the operation whose signature should be extracted
* @return a list of signature. If the operation is overloaded, this contains mutliple entries,
* otherwise only a single entry is contained
*/
private List<MBeanParameterInfo[]> extractMBeanParameterInfos(MBeanServerConnection pServer, JmxExecRequest pRequest,
String pOperation)
throws InstanceNotFoundException, ReflectionException, IOException {
try {
MBeanInfo mBeanInfo = pServer.getMBeanInfo(pRequest.getObjectName());
List<MBeanParameterInfo[]> paramInfos = new ArrayList<MBeanParameterInfo[]>();
for (MBeanOperationInfo opInfo : mBeanInfo.getOperations()) {
if (opInfo.getName().equals(pOperation)) {
paramInfos.add(opInfo.getSignature());
}
}
if (paramInfos.size() == 0) {
throw new IllegalArgumentException("No operation " + pOperation +
" found on MBean " + pRequest.getObjectNameAsString());
}
return paramInfos;
} catch (IntrospectionException e) {
throw new IllegalStateException("Cannot extract MBeanInfo for " + pRequest.getObjectNameAsString(),e);
}
}
/**
* Check whether a matching signature exists from a list of MBean parameter infos. The match is done against a list of types
* (in string form) which was extracted from the request
*
* @param pTypes types to match agains. These are full qualified class names in string representation
* @param pParamInfos list of parameter infos
* @return the matched signature MBeanParamaterInfo[]
*/
private MBeanParameterInfo[] getMatchingSignature(List<String> pTypes, List<MBeanParameterInfo[]> pParamInfos) {
OUTER:
for (MBeanParameterInfo[] infos : pParamInfos) {
if (infos.length == 0 && pTypes.size() == 0) {
// No-arg argument
return infos;
}
if (pTypes.size() != infos.length) {
// Number of arguments dont match
continue OUTER;
}
for (int i=0;i<infos.length;i++) {
String type = infos[i].getType();
if (!type.equals(pTypes.get(i))) {
// Non-matching signature
continue OUTER;
}
}
// If we did it until here, we are finished.
return infos;
}
return null;
}
// Extract operation and optional type parameters
private List<String> splitOperation(String pOperation) {
List<String> ret = new ArrayList<String>();
Pattern p = Pattern.compile("^(.*)\\((.*)\\)$");
Matcher m = p.matcher(pOperation);
if (m.matches()) {
ret.add(m.group(1));
if (m.group(2).length() > 0) {
// No escaping required since the parts a Java types which does not
// allow for commas
String[] args = m.group(2).split("\\s*,\\s*");
ret.addAll(Arrays.asList(args));
} else {
// It's "()" which means a no-arg method
ret.add(null);
}
} else {
ret.add(pOperation);
}
return ret;
}
private String getErrorMessageForMissingSignature(JmxExecRequest pRequest, String pOperation, List<MBeanParameterInfo[]> pParamInfos) {
StringBuffer msg = new StringBuffer("Operation ");
msg.append(pOperation).
append(" on MBean ").
append(pRequest.getObjectNameAsString()).
append(" is overloaded. Signatures found: ");
msg.append(signatureToString(pParamInfos));
msg.append(". Use a signature when specifying the operation.");
return msg.toString();
}
private String signatureToString(List<MBeanParameterInfo[]> pParamInfos) {
StringBuffer ret = new StringBuffer();
for (MBeanParameterInfo[] ii : pParamInfos) {
ret.append("(");
for (MBeanParameterInfo i : ii) {
ret.append(i.getType()).append(",");
}
ret.setLength(ret.length()-1);
ret.append("),");
}
ret.setLength(ret.length()-1);
return ret.toString();
}
// ==================================================================================
// Used for parsing
private static final class OperationAndParamType {
private OperationAndParamType(String pOperationName, MBeanParameterInfo[] pParameterInfos) {
operationName = pOperationName;
paramClasses = new String[pParameterInfos.length];
paramOpenTypes = new OpenType<?>[pParameterInfos.length];
int i=0;
for (MBeanParameterInfo info : pParameterInfos) {
if (info instanceof OpenMBeanParameterInfo) {
OpenMBeanParameterInfo openTypeInfo = (OpenMBeanParameterInfo) info;
paramOpenTypes[i] = openTypeInfo.getOpenType();
}
paramClasses[i++] = info.getType();
}
}
private String operationName;
private String paramClasses[];
private OpenType<?> paramOpenTypes[];
}
}