package nl.tno.sensorstorm.impl;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import nl.tno.sensorstorm.api.annotation.MetaParticleHandlerDeclaration;
import nl.tno.sensorstorm.api.annotation.OperationDeclaration;
import nl.tno.sensorstorm.api.particles.DataParticle;
import nl.tno.sensorstorm.api.particles.DataParticleBatch;
import nl.tno.sensorstorm.api.particles.MetaParticle;
import nl.tno.sensorstorm.api.particles.Particle;
import nl.tno.sensorstorm.api.processing.Batcher;
import nl.tno.sensorstorm.api.processing.BatcherException;
import nl.tno.sensorstorm.api.processing.MetaParticleHandler;
import nl.tno.sensorstorm.api.processing.Operation;
import nl.tno.sensorstorm.api.processing.OperationException;
import nl.tno.sensorstorm.api.processing.ParticleBatchOperation;
import nl.tno.sensorstorm.api.processing.SingleParticleOperation;
import nl.tno.storm.configuration.api.ExternalStormConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An OperationManager manages for a specific fieldGrouper the operation
* instance and optional batcher and optional one or more
* {@link MetaParticleHandler}s.
*
*/
public class OperationManager implements Serializable {
private static final long serialVersionUID = 3141072548366321818L;
private static final Logger logger = LoggerFactory
.getLogger(OperationManager.class);
/** The fieldGroupValue this manager works for, can be null. */
private String fieldGrouperValue;
private Operation operation;
private Batcher batcher;
private List<MetaParticleHandler> metaParticleHandlers;
private Class<? extends Operation> operationClass;
private Class<? extends Batcher> batcherClass;
private ExternalStormConfiguration zookeeperStormConfiguration;
@SuppressWarnings("rawtypes")
private Map stormNativeConfig;
/**
* Creates a new OperationManager for a specific fieldGrouper with a batcher
* and operation.
*
* @param fieldGroupValue
* The fieldGroupValue this operationManager works for. An
* operation will be created when a particle which matches the
* fieldGroupValue arrives for the first time. If the
* fieldGroupValue is null, only one instance of the operation
* will be created directly within this constructor.
* @param batcherClass
* The class of the batcher to be used.
* @param batchOperationClass
* The class of the batched operation to be used.
* @param stormNativeConfig
* A reference to the storm config
* @throws IllegalAccessException
* @throws InstantiationException
*/
public OperationManager(String fieldGroupValue,
Class<? extends Batcher> batcherClass,
Class<? extends ParticleBatchOperation> batchOperationClass,
@SuppressWarnings("rawtypes") Map stormNativeConfig,
ExternalStormConfiguration stormConfiguration)
throws InstantiationException, IllegalAccessException {
operationManager(fieldGroupValue, batcherClass, batchOperationClass,
stormNativeConfig, stormConfiguration);
}
/**
* Creates a new OperationManager with a single operation.
*
* @param fieldGroupValue
* The fieldGroupValue this operationManager works for. An
* operation will be created when a particle which matches the
* fieldGroupValue arrives for the first time. If the
* fieldGroupValue is null, only one instance of the operation
* will be created directly within this constructor.
* @param batchOperationClass
* The class of the single operation to be used.
* @param conf
* A reference to the storm config
* @throws IllegalAccessException
* @throws InstantiationException
*/
public OperationManager(String fieldGroupValue,
Class<? extends SingleParticleOperation> operationClass,
@SuppressWarnings("rawtypes") Map stormNativeConfig,
ExternalStormConfiguration stormConfiguration)
throws InstantiationException, IllegalAccessException {
operationManager(fieldGroupValue, null, operationClass,
stormNativeConfig, stormConfiguration);
}
/**
* An internal class to create the operationManager.
*
* @param fieldGroupValue
* @param batcherClass
* @param operationClass
* @param conf
* @throws IllegalAccessException
* @throws InstantiationException
*/
private void operationManager(String fieldGroupValue,
Class<? extends Batcher> batcherClass,
Class<? extends Operation> operationClass,
@SuppressWarnings("rawtypes") Map stormNativeConfig,
ExternalStormConfiguration stormConfiguration)
throws InstantiationException, IllegalAccessException {
fieldGrouperValue = fieldGroupValue;
this.operationClass = operationClass;
this.batcherClass = batcherClass;
this.operationClass = operationClass;
zookeeperStormConfiguration = stormConfiguration;
metaParticleHandlers = new ArrayList<MetaParticleHandler>();
// precreate the operation in order to give it time to initialze before
// all particles will start flowing.
if (fieldGroupValue == null) {
createOperation(null);
}
logger.debug("Operation manager for fieldGroupValue" + fieldGroupValue
+ " created");
}
/**
* Process a DataParticle.
*
* @param dataParticle
* DataParticle to be processed. If the particle == null, null is
* returned
* @return returns a list with zero or more DataParticles, these particles
* should be emitted by the bolt. Or null in case of an error, which
* will be logged
*/
@SuppressWarnings("unchecked")
public List<Particle> processDataParticle(DataParticle dataParticle) {
if (dataParticle == null) {
return null;
}
// make sure this operation manager has an operation and optional
// metaParticleHandlers.
// if the fieldgrouperid == null, the operation is already created in
// the constructor.
if (operation == null) {
// this is the first particle, no operation has been instantiated.
try {
createOperation(dataParticle);
} catch (InstantiationException | IllegalAccessException e) {
logger.error("For fieldGroupValue " + fieldGrouperValue
+ ": can not create the operation ("
+ operationClass.getName() + ") msg=" + e);
return null;
}
}
try {
// is there a batcher?
List<Particle> outputDataParticles = new ArrayList<Particle>();
if (batcher != null) {
// batch dataParticle and give it to the batcherOperation
List<DataParticleBatch> batchedParticles = batcher
.batch(dataParticle);
// are there one or more particle batches to be processed?
if (batchedParticles != null) {
for (DataParticleBatch batchedParticle : batchedParticles) {
List<? extends DataParticle> results = ((ParticleBatchOperation) operation)
.execute(batchedParticle);
if (results != null) {
outputDataParticles.addAll(results);
}
}
}
} else {
// it is an operation without a batcher
List<? extends DataParticle> results = ((SingleParticleOperation) operation)
.execute(dataParticle);
if (results != null) {
outputDataParticles.addAll(results);
}
}
return outputDataParticles;
} catch (BatcherException | OperationException e) {
logger.error(
"Unable to execute operation due to: " + e.getMessage(), e);
return null;
}
}
/**
* Process a MetaParticle.
*
* @param metaParticle
* Particle to be processed. If the particle == null, null is
* returned
* @return returns a list with one MetaParticle (to be sent further up to
* the topology), zero or more DataParticles or null in case of an
* error. These particles should be emitted by the bolt.
*/
public List<Particle> processMetaParticle(MetaParticle metaParticle) {
if (metaParticle != null) {
return handleMetaParticle(metaParticle);
} else {
return null;
}
}
/**
* Create a new operation with Batcher and metaParticleHandlers.
*
* @param firstParticle
* @throws InstantiationException
* @throws IllegalAccessException
*/
private void createOperation(Particle firstParticle)
throws InstantiationException, IllegalAccessException {
long timestamp = -1;
if (firstParticle != null) {
timestamp = firstParticle.getTimestamp();
}
// create batcher if needed
if (batcherClass != null) {
try {
batcher = batcherClass.newInstance();
batcher.init(fieldGrouperValue, timestamp, stormNativeConfig,
zookeeperStormConfiguration);
} catch (BatcherException e) {
throw new InstantiationException(e.getMessage());
}
}
try {
// create new operation and initialize it
operation = operationClass.newInstance();
if (ParticleBatchOperation.class.isInstance(operation)) {
// ParticleBatchOperation
((ParticleBatchOperation) operation).init(fieldGrouperValue,
timestamp, stormNativeConfig,
zookeeperStormConfiguration);
} else if (SingleParticleOperation.class.isInstance(operation)) {
// SingleParticleOperation
operation.init(fieldGrouperValue, timestamp, stormNativeConfig,
zookeeperStormConfiguration);
} else {
// unknown operation type
logger.error("Internal error: OperationClass of unknown type "
+ operationClass.getName() + ", expected: "
+ SingleParticleOperation.class.getName() + " or "
+ ParticleBatchOperation.class.getName());
}
} catch (OperationException oe) {
// stop creating an operation, and inform the caller why
throw new InstantiationException(
"Unable to initialize operation due to: " + oe.getMessage());
}
// create the meta particle handlers related to this operation
createMetaParticleHandlers(operation);
}
/**
* Create all metaHandlers as specified in the annotations.
*
* @param operation
* @throws InstantiationException
* @throws IllegalAccessException
*/
private void createMetaParticleHandlers(Operation operation)
throws InstantiationException, IllegalAccessException {
// walk through all specified metaHandlers in the annotation
OperationDeclaration operationDeclaration = operation.getClass()
.getAnnotation(OperationDeclaration.class);
for (Class<? extends MetaParticleHandler> mph : operationDeclaration
.metaParticleHandlers()) {
MetaParticleHandler newInstance = mph.newInstance();
newInstance.init(operation);
metaParticleHandlers.add(newInstance);
}
}
/**
* pass this metaParticle to all metaHandlers.
*
* @param metaParticle
*/
protected List<Particle> handleMetaParticle(MetaParticle metaParticle) {
List<Particle> result = new ArrayList<Particle>();
for (MetaParticleHandler mph : metaParticleHandlers) {
MetaParticleHandlerDeclaration mphd = mph.getClass().getAnnotation(
MetaParticleHandlerDeclaration.class);
if (metaParticle.getClass().isAssignableFrom(mphd.metaParticle())) {
result.addAll(mph.handleMetaParticle(metaParticle));
}
}
return result;
}
/**
* Given an Operation class, figure out all the types of particles (both
* DataParticles and MetaParticles) produced by this {@link Operation}.
*
* @param operationClass
* Class of the Operation
* @return List of Particle classes
*/
public static List<Class<? extends Particle>> getOutputParticles(
Class<? extends Operation> operationClass) {
List<Class<? extends Particle>> result = new ArrayList<Class<? extends Particle>>();
OperationDeclaration od = operationClass
.getAnnotation(OperationDeclaration.class);
for (Class<? extends DataParticle> cl : od.outputs()) {
result.add(cl);
}
for (Class<? extends MetaParticleHandler> cl : od
.metaParticleHandlers()) {
MetaParticleHandlerDeclaration mphd = cl
.getAnnotation(MetaParticleHandlerDeclaration.class);
result.add(mphd.metaParticle());
}
return result;
}
/**
* Given an Operation class, figure out all the types of DataParticles
* produced by this Operation.
*
* @param operationClass
* Class of the Operation
* @return List of DataParticle classes
*/
public static List<Class<? extends DataParticle>> getOutputDataParticles(
Class<? extends Operation> operationClass) {
List<Class<? extends DataParticle>> result = new ArrayList<Class<? extends DataParticle>>();
OperationDeclaration od = operationClass
.getAnnotation(OperationDeclaration.class);
for (Class<? extends DataParticle> cl : od.outputs()) {
result.add(cl);
}
return result;
}
/**
* Given an Operation class, figure out all the types of MetaParticles
* produced by this Operation.
*
* @param operationClass
* Class of the Operation
* @return List of MetaParticle classes
*/
public static List<Class<? extends MetaParticle>> getOutputMetaParticles(
Class<? extends Operation> operationClass) {
List<Class<? extends MetaParticle>> result = new ArrayList<Class<? extends MetaParticle>>();
OperationDeclaration od = operationClass
.getAnnotation(OperationDeclaration.class);
for (Class<? extends MetaParticleHandler> cl : od
.metaParticleHandlers()) {
MetaParticleHandlerDeclaration mphd = cl
.getAnnotation(MetaParticleHandlerDeclaration.class);
result.add(mphd.metaParticle());
}
return result;
}
}