package org.jolokia.handler;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import javax.management.*;
import javax.management.openmbean.OpenMBeanAttributeInfo;
import org.jolokia.converter.Converters;
import org.jolokia.request.JmxWriteRequest;
import org.jolokia.restrictor.Restrictor;
import org.jolokia.util.RequestType;
/*
* 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.
*/
/**
* Handler for dealing with write request.
*
* @author roland
* @since Jun 12, 2009
*/
public class WriteHandler extends JsonRequestHandler<JmxWriteRequest> {
private Converters converters;
/**
* Constructor
*
* @param pRestrictor access restriction to apply
* @param pConverters converters used for serialization
*/
public WriteHandler(Restrictor pRestrictor, Converters pConverters) {
super(pRestrictor);
converters = pConverters;
}
/** {@inheritDoc} */
@Override
public RequestType getType() {
return RequestType.WRITE;
}
/** {@inheritDoc} */
@Override
protected void checkForRestriction(JmxWriteRequest pRequest) {
if (!getRestrictor().isAttributeWriteAllowed(pRequest.getObjectName(),pRequest.getAttributeName())) {
throw new SecurityException("Writing attribute " + pRequest.getAttributeName() +
" forbidden for MBean " + pRequest.getObjectNameAsString());
}
}
/** {@inheritDoc} */
@Override
public Object doHandleRequest(MBeanServerConnection server, JmxWriteRequest request)
throws InstanceNotFoundException, AttributeNotFoundException, ReflectionException, MBeanException, IOException {
try {
return setAttribute(request, server);
} catch (IntrospectionException exp) {
throw new IllegalArgumentException("Cannot get info for MBean " + request.getObjectName() + ": " +exp,exp);
} catch (InvalidAttributeValueException e) {
throw new IllegalArgumentException("Invalid value " + request.getValue() + " for attribute " +
request.getAttributeName() + ", MBean " + request.getObjectNameAsString(),e);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Cannot set value " + request.getValue() + " for attribute " +
request.getAttributeName() + ", MBean " + request.getObjectNameAsString(),e);
} catch (InvocationTargetException e) {
throw new IllegalArgumentException("Cannot set value " + request.getValue() + " for attribute " +
request.getAttributeName() + ", MBean " + request.getObjectNameAsString(),e);
}
}
private Object setAttribute(JmxWriteRequest request, MBeanServerConnection server)
throws MBeanException, AttributeNotFoundException, InstanceNotFoundException,
ReflectionException, IntrospectionException, InvalidAttributeValueException, IllegalAccessException, InvocationTargetException, IOException {
// Old value, will throw an exception if attribute is not known. That's good.
Object oldValue = server.getAttribute(request.getObjectName(), request.getAttributeName());
MBeanInfo mInfo = server.getMBeanInfo(request.getObjectName());
MBeanAttributeInfo aInfo = null;
for (MBeanAttributeInfo i : mInfo.getAttributes()) {
if (i.getName().equals(request.getAttributeName())) {
aInfo = i;
break;
}
}
Object values[];
if (aInfo instanceof OpenMBeanAttributeInfo) {
values = getValues((OpenMBeanAttributeInfo) aInfo, oldValue, request);
} else {
// aInfo is != null otherwise getAttribute() would have already thrown an ArgumentNotFoundException
values = getValues(aInfo.getType(), oldValue, request);
}
Attribute attribute = new Attribute(request.getAttributeName(),values[0]);
server.setAttribute(request.getObjectName(),attribute);
return values[1];
}
/**
* The old value is returned directly, hence we do not want any path conversion
* on this value
*
* @return false;
*/
@Override
public boolean useReturnValueWithPath() {
return false;
}
/**
* Get values for a write request. This method returns an array with two objects.
* If no path is given (<code>pRequest.getExtraArgs() == null</code>), the returned values
* are the new value and the old value. However, if a path is set, the returned new value
* is the outer value (which can be set by an corresponding JMX set operation) where the
* new value is set via the path expression. The old value is the value of the object specified
* by the given path.
*
*
* @param pType type of the outermost object to set as returned by an MBeanInfo structure.
* @param pCurrentValue the object of the outermost object which can be null
* @param pRequest the initial request
* @return object array with two elements, element 0 is the value to set (see above), element 1
* is the old value.
*
* @throws AttributeNotFoundException if no such attribute exists (as specified in the request)
* @throws IllegalAccessException if access to MBean fails
* @throws InvocationTargetException reflection error when setting an object's attribute
*/
private Object[] getValues(String pType, Object pCurrentValue, JmxWriteRequest pRequest)
throws AttributeNotFoundException, IllegalAccessException, InvocationTargetException {
List<String> pathParts = pRequest.getPathParts();
Object newValue = pRequest.getValue();
if (pathParts != null && pathParts.size() > 0) {
if (pCurrentValue == null ) {
throw new IllegalArgumentException(
"Cannot set value with path when parent object is not set");
}
// We set an inner value, hence we have to return provided value itself for resetting
// it later back via JMX
return new Object[] {
pCurrentValue,
converters.getToJsonConverter().setInnerValue(pCurrentValue, newValue, pathParts)
};
} else {
// Return the objectified value
return new Object[] {
converters.getToObjectConverter().prepareValue(pType, newValue),
pCurrentValue
};
}
}
private Object[] getValues(OpenMBeanAttributeInfo pOpenTypeInfo, Object pCurrentValue, JmxWriteRequest pRequest) {
// TODO: What to do when path is not null ? Simplest: Throw exception. Advanced: Extract other values and create
// a new CompositeData with old values and the new value.
// However, since this is probably out of scope, we will simply throw an exception if the path is not empty.
List<String> pathParts = pRequest.getPathParts();
if (pathParts != null && pathParts.size() > 0) {
throw new IllegalArgumentException("Cannot set value for OpenType " + pOpenTypeInfo.getOpenType() + " with inner path " +
pRequest.getPath() + " since OpenTypes are immutable");
}
return new Object[] {
converters.getToOpenTypeConverter().convertToObject(pOpenTypeInfo.getOpenType(), pRequest.getValue()),
pCurrentValue
};
}
}