blob: c7dfcb033c8d42eef2924bc777556c5e987f089e [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*
*/
package org.apache.qpid.management.domain.model;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.RuntimeOperationsException;
import org.apache.qpid.management.Messages;
import org.apache.qpid.management.domain.handler.impl.IMethodInvocationListener;
import org.apache.qpid.management.domain.handler.impl.InvocationResult;
import org.apache.qpid.management.domain.handler.impl.MethodOrEventDataTransferObject;
import org.apache.qpid.management.domain.model.type.Binary;
import org.apache.qpid.management.domain.services.SequenceNumberGenerator;
import org.apache.qpid.transport.codec.BBDecoder;
import org.apache.qpid.transport.util.Logger;
/**
* Qpid Class definition.
* A type definition for a manageable object.
* This class is also responsible to manage incoming obejct instance data (configuration & instrumentation).
* How can we handle data before schema is injected into this class? simply we must retain that data in raw format.
* This class has 2 states :
* 1) first state is when schema is not yet injected. In this case the incoming object data is retained as is (in raw format);
* 2) second state is when schema is injected. In this case each injection of data will result in an update / create / delete of
* the corresponding object instance. In addition, the first time the state change, the old retained raw data is cnverted in
* object instance(s).
*
* @author Andrea Gazzarini
*/
class QpidClass extends QpidEntity
{
/**
* State interface for this class definition.
* Each state is responsible to handle the injection of the data and / or schema.
*
* @author Andrea Gazzarini
*/
interface State
{
/**
* Adds configuration data for the object instance associated to the given object identifier.
*
* @param objectId the object identifier.
* @param rawData the raw configuration data.
*/
void addInstrumentationData (Binary objectId, byte[] rawData);
/**
* Adds instrumentation data for the object instance associated to the given object identifier.
*
* @param objectId the object identifier.
* @param rawData the raw instrumentation data.
*/
void addConfigurationData (Binary objectId, byte[] rawData);
/**
* Inject the schema into this class definition.
*
* @param propertyDefinitions
* @param statisticDefinitions
* @param methodDefinitions
* @throws UnableToBuildFeatureException when it's not possibile to parse schema and build the class definition.
*/
public void setSchema (
List<Map<String, Object>> propertyDefinitions,
List<Map<String, Object>> statisticDefinitions,
List<MethodOrEventDataTransferObject> methodDefinitions) throws UnableToBuildFeatureException;
};
/**
* This is the initial state of every qpid class.
* The class definition instance is created but its schema has not been injected.
* Incoming configuration & instrumentation data will be stored in raw format because we don't know how to
* parse it until the schema arrives.
* In addition, this state is responsible (when data arrives) to request its schema.
*/
final State _schemaNotRequested = new State() {
/**
* Stores the incoming data in raw format and request the schema for this class.
* After that a transition to the next state is made.
*
* @param objectId the object instance identifier.
* @param rawData incoming configuration data.
*/
public synchronized void addConfigurationData (Binary objectId, byte[] rawData)
{
try
{
requestSchema();
_state = _schemaRequestedButNotYetInjected;
} catch (Exception e)
{
_logger.error(
Messages.QMAN_100015_UNABLE_TO_SEND_SCHEMA_REQUEST,
_parent.getName(),
_name);
} finally {
QpidManagedObject instance = getObjectInstance(objectId,false);
instance._rawConfigurationData.add(rawData);
}
}
/**
* Stores the incoming data in raw format and request the schema for this class.
* After that a transition to the next state is made.
*
* @param objectId the object instance identifier.
* @param rawData incoming instrumentation data.
*/
public synchronized void addInstrumentationData (Binary objectId, byte[] rawData)
{
try
{
requestSchema();
_state = _schemaRequestedButNotYetInjected;
} catch (Exception e)
{
_logger.error(
Messages.QMAN_100015_UNABLE_TO_SEND_SCHEMA_REQUEST,
_parent.getName(),
_name);
} finally {
QpidManagedObject instance = getObjectInstance(objectId,false);
instance._rawConfigurationData.add(rawData);
}
}
/**
* This method only throws an illegal state exception because when a schema arrives
* this state is no longer valid.
*/
public void setSchema (
List<Map<String, Object>> propertyDefinitions,
List<Map<String, Object>> statisticDefinitions,
List<MethodOrEventDataTransferObject> methodDefinitions) throws UnableToBuildFeatureException
{
throw new IllegalStateException("When a schema arrives it's not possible for this class to be in this state.");
}
};
/**
* This is the first state of this class definition : the schema is not yet injected so each injection of object data will be
* retained in raw format.
*/
final State _schemaRequestedButNotYetInjected = new State()
{
/**
* Stores the incoming data in raw format.
*
* @param objectId the object instance identifier.
* @param rawData incoming configuration data.
*/
public void addConfigurationData (Binary objectId, byte[] rawData)
{
QpidManagedObject instance = getObjectInstance(objectId,false);
instance._rawConfigurationData.add(rawData);
}
/**
* Stores the incoming data in raw format.
*
* @param objectId the object instance identifier.
* @param rawData incoming instrumentation data.
*/
public void addInstrumentationData (Binary objectId, byte[] rawData)
{
QpidManagedObject instance = getObjectInstance(objectId,false);
instance._rawInstrumentationData.add(rawData);
}
/**
* When a schema is injected into this defintiion the following should happen :
* 1) the incoming schema is parsed and the class definition is built;
* 2) the retained raw data is converted into object instance(s)
* 3) the internal state of this class changes;
*
* If someting is wrong during that process the schema is not built and the state don't change.
*/
public synchronized void setSchema (
List<Map<String, Object>> propertyDefinitions,
List<Map<String, Object>> statisticDefinitions,
List<MethodOrEventDataTransferObject> methodDefinitions) throws UnableToBuildFeatureException
{
MBeanAttributeInfo [] attributesMetadata = new MBeanAttributeInfo[propertyDefinitions.size()+statisticDefinitions.size()];
MBeanOperationInfo [] operationsMetadata = new MBeanOperationInfo[methodDefinitions.size()];
buildAttributes(propertyDefinitions,statisticDefinitions,attributesMetadata);
buildMethods(methodDefinitions,operationsMetadata);
_metadata = new MBeanInfo(_name,_name,attributesMetadata,null,operationsMetadata,null);
// Converting stored object instances into JMX MBean and removing raw instance data.
for (Entry<Binary, QpidManagedObject> instanceEntry : _objectInstances.entrySet())
{
Binary objectId = instanceEntry.getKey();
QpidManagedObject instance = instanceEntry.getValue();
for (Iterator<byte[]> iterator = instance._rawInstrumentationData.iterator(); iterator.hasNext();)
{
updateInstanceWithInstrumentationData(instance,iterator.next());
iterator.remove();
}
for (Iterator<byte[]> iterator = instance._rawConfigurationData.iterator(); iterator.hasNext();)
{
updateInstanceWithConfigurationData(instance, iterator.next());
iterator.remove();
}
JMX_SERVICE.registerObjectInstance(instance,_parent.getOwnerId(),_parent.getName(),_name,objectId);
}
_state = _schemaInjected;
}
};
/**
* After a schema is built into this definition this is the current state of the class.
*/
final State _schemaInjected = new State()
{
/**
* Updates the configuration state of the object instance associates with the given object identifier.
*
* @param objectId the object identifier.
* @param rawData the configuration data (raw format).
*/
public void addConfigurationData (Binary objectId, byte[] rawData)
{
QpidManagedObject instance = getObjectInstance(objectId,true);
updateInstanceWithConfigurationData(instance, rawData);
}
/**
* Updates the instrumentation state of the object instance associates with the given object identifier.
*
* @param objectId the object identifier.
* @param rawData the instrumentation data (raw format).
*/
public void addInstrumentationData (Binary objectId, byte[] rawData)
{
QpidManagedObject instance = getObjectInstance(objectId,true);
updateInstanceWithInstrumentationData(instance, rawData);
}
/**
* Never called when the class definition has this state.
*/
public void setSchema (
List<Map<String, Object>> propertyDefinitions,
List<Map<String, Object>> statisticDefinitions,
List<MethodOrEventDataTransferObject> methodDefinitions) throws UnableToBuildFeatureException
{
throw new IllegalStateException("When a schema arrives it's not possible for this class to be in this state.");
}
};
/**
* MBean used for representing remote broker object instances.
* This is the core component of the QMan domain model
*
* @author Andrea Gazzarini
*/
class QpidManagedObject extends QpidManagedEntity implements MBeanRegistration
{
private Binary _objectId;
// Arrays used for storing raw data before this mbean is registered to mbean server.
List<byte[]> _rawInstrumentationData = new ArrayList<byte[]>();
List<byte[]> _rawConfigurationData = new ArrayList<byte[]>();
/**
* Builds a new managed object with the given object identifier.
*
* @param objectId the object identifier.
*/
QpidManagedObject(Binary objectId)
{
this._objectId = objectId;
}
/**
* Returns the value of the given attribute.s
*
* @throws AttributeNotFoundException when no attribute is found with the given name.
*/
public Object getAttribute (String attributeName) throws AttributeNotFoundException, MBeanException, ReflectionException
{
if (attributeName == null)
{
throw new RuntimeOperationsException(new IllegalArgumentException("attribute name must not be null"));
}
if (_properties.containsKey(attributeName) || _statistics.containsKey(attributeName))
{
return _attributes.get(attributeName);
} else
{
throw new AttributeNotFoundException(attributeName);
}
}
/**
* Executes an operation on this object instance.
*
* @param actionName the name of the method.
* @param params the method parameters
* @param signature the method signature.
*/
public Object invoke (String actionName, Object[] params, String[] signature) throws MBeanException,ReflectionException
{
// TODO : Overloaded methods
QpidMethod method = _methods.get(actionName);
if (method != null)
{
try
{
method.validate(params);
return invokeMethod(_objectId, method, params);
} catch (Exception exception)
{
throw new MBeanException(exception);
}
} else {
throw new ReflectionException(new NoSuchMethodException(actionName));
}
}
/**
* Sets the value of the given attribute on this object instance.
*
* @param attribute contains the new value of the attribute.
* @throws AttributeNotFoundException when the given attribute is not found on this object instance.
* @throws InvalidAttributeValueException when the given value is violating one attribute invariant.
*/
public void setAttribute (Attribute attribute) throws AttributeNotFoundException,
InvalidAttributeValueException, MBeanException, ReflectionException
{
QpidProperty property = _properties.get(attribute.getName());
try
{
property.validate(attribute.getValue());
} catch(ValidationException exception)
{
throw new InvalidAttributeValueException(exception.getMessage());
}
throw new RuntimeException("Not yet implemented.");
}
/**
* Sets the values of several attributes of this MBean.
*
* @param attributes a list of attributes: The identification of the attributes to be set and the values they are to be set to.
* @return The list of attributes that were set, with their new values.
*/
public AttributeList setAttributes (AttributeList attributes)
{
throw new RuntimeException("Not yet implemented.");
}
/**
* MBean server callback after deregistration.
*/
public void postDeregister ()
{
}
/**
* After the object is registered the raw data is set to null.
* This is done because we no longer need this data : it has already been
* injected into this object instance.
*
* @param registrationDone a flag indicating if the instance has been registered to mbean server.
*/
public void postRegister (Boolean registrationDone)
{
if (registrationDone)
{
_rawConfigurationData = null;
_rawInstrumentationData = null;
}
}
/**
* MBean server callback before deregistration.
*/
public void preDeregister () throws Exception
{
}
/**
* MBean server callback before registration.
*/
public ObjectName preRegister (MBeanServer server, ObjectName name) throws Exception
{
return name;
}
}
Map<String, QpidProperty> _properties = new HashMap<String, QpidProperty>();
Map<String, QpidStatistic> _statistics = new HashMap<String, QpidStatistic>();
private Map<String, QpidMethod> _methods = new HashMap<String, QpidMethod>();
private List<QpidProperty> _schemaOrderedProperties = new ArrayList<QpidProperty>();
private List<QpidStatistic> _schemaOrderedStatistics= new ArrayList<QpidStatistic>();
private int _howManyPresenceBitMasks;
private BlockingQueue<InvocationResult> _exchangeChannelForMethodInvocations;
private final IMethodInvocationListener _methodInvocationListener;
Map<Binary, QpidManagedObject> _objectInstances = new HashMap<Binary, QpidManagedObject>();
State _state = _schemaNotRequested;;
private final static class Log
{
private final static Logger LOGGER = Logger.get(QpidClass.class);
final static void logMethodInvocationResult(InvocationResult result)
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug(String.valueOf(result));
}
}
}
/**
* Builds a new class with the given name and package as parent.
*
* @param className the name of the class.
* @param hash the class schema hash.
* @param parentPackage the parent of this class.
*/
QpidClass(String className, Binary hash, QpidPackage parentPackage)
{
super(className,hash, parentPackage);
this._methodInvocationListener = _parent.getMethodInvocationListener();
this._exchangeChannelForMethodInvocations = new SynchronousQueue<InvocationResult>();
}
/**
* Adds the configuration data for the object instance associated to the given object identifier.
*
* @param objectId the object identifier.
* @param rawData the raw configuration data.
*/
void addInstrumentationData (Binary objectId, byte[] rawData)
{
_logger.debug(
Messages.QMAN_200014_INCOMING_INSTRUMENTATION_DATA,
_parent.getOwnerId(),
_parent.getName(),
_name,
objectId);
_state.addInstrumentationData(objectId, rawData);
}
/**
* Adds the instrumentation data for the object instance associated to the given object identifier.
*
* @param objectId the object identifier.
* @param rawData the raw instrumentation data.
*/
void addConfigurationData (Binary objectId, byte[] rawData)
{
_logger.debug(
Messages.QMAN_200015_INCOMING_CONFIGURATION_DATA,
_parent.getOwnerId(),
_parent.getName(),
_name,
objectId);
_state.addConfigurationData(objectId, rawData);
}
/**
* Sets the schema for this class definition.
* A schema is basically a metadata description of all properties, statistics, methods and events of this class.
*
* @param propertyDefinitions properties metadata.
* @param statisticDefinitions statistics metadata.
* @param methodDefinitions methods metadata.
* @throws UnableToBuildFeatureException when some error occurs while parsing the incoming schema.
*/
void setSchema (
List<Map<String, Object>> propertyDefinitions,
List<Map<String, Object>> statisticDefinitions,
List<MethodOrEventDataTransferObject> methodDefinitions) throws UnableToBuildFeatureException
{
_logger.info(Messages.QMAN_000010_INCOMING_SCHEMA,_parent.getOwnerId(),_parent.getName(),_name);
_state.setSchema(propertyDefinitions, statisticDefinitions, methodDefinitions);
}
/**
* Internal method used for building attributes definitions.
*
* @param props the map contained in the properties schema.
* @param stats the map contained in the statistics schema.
* @param attributes the management metadata for attributes.
* @throws UnableToBuildFeatureException when it's not possibile to build one attribute definition.
*/
void buildAttributes (
List<Map<String, Object>> props,
List<Map<String, Object>> stats,
MBeanAttributeInfo[] attributes) throws UnableToBuildFeatureException
{
int index = 0;
int howManyOptionalProperties = 0;
for (Map<String, Object> propertyDefinition : props)
{
QpidFeatureBuilder builder = QpidFeatureBuilder.createPropertyBuilder(propertyDefinition);
builder.build();
QpidProperty property = (QpidProperty) builder.getQpidFeature();
howManyOptionalProperties += (property.isOptional()) ? 1 : 0;
_properties.put(property.getName(),property);
_schemaOrderedProperties.add(property);
attributes[index++]=(MBeanAttributeInfo) builder.getManagementFeature();
_logger.debug(
Messages.QMAN_200016_PROPERTY_DEFINITION_HAS_BEEN_BUILT,
_parent.getName(),
_name,
property);
}
_howManyPresenceBitMasks = (int)Math.ceil((double)howManyOptionalProperties / 8);
_logger.debug(
Messages.QMAN_200018_OPTIONAL_PROPERTIES_INFO,
_parent.getOwnerId(),
_parent.getName(),
_name,
_howManyPresenceBitMasks);
for (Map<String, Object> statisticDefinition : stats)
{
QpidFeatureBuilder builder = QpidFeatureBuilder.createStatisticBuilder(statisticDefinition);
builder.build();
QpidStatistic statistic = (QpidStatistic) builder.getQpidFeature();
_statistics.put(statistic.getName(),statistic);
_schemaOrderedStatistics.add(statistic);
attributes[index++]=(MBeanAttributeInfo) builder.getManagementFeature();
_logger.debug(
Messages.QMAN_200017_STATISTIC_DEFINITION_HAS_BEEN_BUILT,
_parent.getName(),
_name,
statistic);
}
}
/**
* Returns the object instance associated to the given identifier.
* Note that if the identifier is not associated to any obejct instance, a new one will be created.
*
* @param objectId the object identifier.
* @param registration a flag indicating whenever the (new ) instance must be registered with MBean server.
* @return the object instance associated to the given identifier.
*/
QpidManagedObject getObjectInstance(Binary objectId, boolean registration)
{
QpidManagedObject objectInstance = _objectInstances.get(objectId);
if (objectInstance == null)
{
objectInstance = new QpidManagedObject(objectId);
_objectInstances.put(objectId, objectInstance);
if (registration)
{
JMX_SERVICE.registerObjectInstance(objectInstance,_parent.getOwnerId(),_parent.getName(),_name,objectId);
}
}
return objectInstance;
}
/**
* Internal method used for building method defintiions.
*
* @param definitions the properties map contained in the incoming schema.
* @param operationsMetadata
* @throws UnableToBuildFeatureException when it's not possibile to build one or more definitions.
*/
void buildMethods (List<MethodOrEventDataTransferObject> definitions, MBeanOperationInfo[] operationsMetadata) throws UnableToBuildFeatureException
{
int index = 0;
for (MethodOrEventDataTransferObject definition: definitions)
{
QpidFeatureBuilder builder = QpidFeatureBuilder.createMethodBuilder(definition);
builder.build();
operationsMetadata [index++]= (MBeanOperationInfo) builder.getManagementFeature();
QpidMethod method = (QpidMethod) builder.getQpidFeature();
_methods.put(method.getName(),method);
}
}
/**
* Header (opcode='M')
* ObjectId of target object (128 bits)
* Package name (str8)
* Class name (str8)
* Class hash (bin128)
* Method name (str8) [as defined in the schema]
* Now encode all input ("I") and i/o (IO) arguments in the order in which they are defined in the schema.
* (i.e. make one pass over the argument list and encode arguments that are either input or inptu/output).
* @param objectId
* @param method
* @param parameters
* @throws Exception
*/
private InvocationResult invokeMethod(Binary objectId,QpidMethod method,Object [] parameters) throws Exception
{
try
{
_service.connect();
int sequenceNumber = SequenceNumberGenerator.getNextSequenceNumber();
_methodInvocationListener.operationIsGoingToBeInvoked(new InvocationEvent(this,sequenceNumber,_exchangeChannelForMethodInvocations));
_service.invoke(_parent.getName(), _name, _hash,objectId,parameters, method,sequenceNumber,objectId.getBankId(),objectId.getBrokerId());
InvocationResult result = _exchangeChannelForMethodInvocations.poll(5000,TimeUnit.MILLISECONDS);
if (result == null)
{
throw new TimeoutException();
}
Map<String, Object> output = method.decodeParameters(result.getOutputAndBidirectionalArgumentValues());
result.setOutputSection(output);
Log.logMethodInvocationResult(result);
if (result.isException())
{
result.createAndThrowException();
}
return result;
} finally
{
_service.close();
}
}
/**
* Updates the given obejct instance with the given incoming configuration data.
*
* @param instance the managed object instance.
* @param rawData the incoming configuration data which contains new values for instance properties.
*/
void updateInstanceWithConfigurationData(QpidManagedObject instance,byte [] rawData)
{
BBDecoder decoder = new BBDecoder();
decoder.init(ByteBuffer.wrap(rawData));
byte [] presenceBitMasks = decoder.readBytes(_howManyPresenceBitMasks);
for (QpidProperty property : _schemaOrderedProperties)
{
try {
Object value = property.decodeValue(decoder,presenceBitMasks);
instance.createOrReplaceAttributeValue(property.getName(),value);
} catch(Exception ignore) {
_logger.error(Messages.QMAN_100016_UNABLE_TO_DECODE_FEATURE_VALUE, _parent.getName(),_name,property.getName());
}
}
}
/**
* Updates the given object instance with the given incoming instrumentation data.
*
* @param instance the managed object instance.
* @param rawData the incoming instrumentation data which contains new values for instance properties.
*/
void updateInstanceWithInstrumentationData(QpidManagedObject instance,byte [] rawData)
{
BBDecoder decoder = new BBDecoder();
decoder.init(ByteBuffer.wrap(rawData));
for (QpidStatistic statistic : _schemaOrderedStatistics)
{
try {
Object value = statistic.decodeValue(decoder);
instance.createOrReplaceAttributeValue(statistic.getName(),value);
} catch(Exception ignore) {
_logger.error(Messages.QMAN_100016_UNABLE_TO_DECODE_FEATURE_VALUE, _parent.getName(),_name,statistic.getName());
}
}
}
@Override
public String toString ()
{
return new StringBuilder()
.append(_parent.getOwnerId())
.append("::")
.append(_parent.getName())
.append('.')
.append(_name)
.toString();
}
/**
* Removes the object instance associated to the given identifier.
*
* @param objectId the object identifier.
*/
void removeObjectInstance (Binary objectId)
{
QpidManagedObject toBeRemoved = _objectInstances.remove(objectId);
if (toBeRemoved != null)
{
JMX_SERVICE.unregisterObjectInstance(_parent.getOwnerId(),_parent.getName(),_name,toBeRemoved._objectId);
}
}
/**
* Deregisters all the object instances and release all previously acquired resources.
*/
void releaseResources ()
{
_objectInstances.clear();
JMX_SERVICE.unregisterObjectInstances();
_service.close();
}
}