package nl.tno.sensorstorm.storm; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import nl.tno.sensorstorm.api.particles.MetaParticle; import nl.tno.sensorstorm.api.particles.Particle; import nl.tno.sensorstorm.particlemapper.ParticleMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import backtype.storm.generated.GlobalStreamId; import backtype.storm.grouping.CustomStreamGrouping; import backtype.storm.task.WorkerTopologyContext; public abstract class SensorStormGrouping implements CustomStreamGrouping, Serializable { private static final long serialVersionUID = 9192870640924179453L; protected Logger logger = LoggerFactory.getLogger(this.getClass()); private List<Integer> targetBoltIds; @Override public void prepare(WorkerTopologyContext context, GlobalStreamId stream, List<Integer> targetBoltIds) { this.targetBoltIds = targetBoltIds; } @Override public List<Integer> chooseTasks(int customGrouperTaskId, List<Object> values) { List<Integer> routeToBoltIds; // Is this tuple a Particle? Class<Particle> particleClass = valuesToParticleClass(values); if (particleClass != null) { // is this tuple a metaParticle? if (MetaParticle.class.isAssignableFrom(particleClass)) { routeToBoltIds = getBoltIdsForMetaParticle(targetBoltIds, particleClass, values); } else { // not a metaParticle particle routeToBoltIds = getBoltIdsForNonMetaParticle(targetBoltIds, particleClass, values); } } else { // else values can not be converted into a particle -> perform // default grouping routeToBoltIds = getBoltIdsForDefaultTuple(values); } return routeToBoltIds; } @SuppressWarnings("unchecked") private Class<Particle> valuesToParticleClass(List<Object> values) { // values should at least contain 2 items, the timestamp and the // className if (values.size() >= ParticleMapper.PARTICLE_MINIMAL_FIELDS) { Object timestampObject = values.get(ParticleMapper.TIMESTAMP_IDX); Object particleClassObject = values .get(ParticleMapper.PARTICLE_CLASS_IDX); if ((timestampObject == null) || (!(timestampObject instanceof Long))) { return null; } if ((particleClassObject == null) || (!(particleClassObject instanceof String))) { return null; } try { String particleClassName = (String) particleClassObject; Class<?> particleClass = Class.forName(particleClassName); if (Particle.class.isAssignableFrom(particleClass)) { return (Class<Particle>) particleClass; } else { // Values particleClassName is not a particle return null; } } catch (ClassNotFoundException e) { // Values do not convert into a particle return null; } } else { return null; } } /** * Return the boltIds to which this metaParticle must be routed. * * @param targetBoltIdList * @param particleClass * @param values * @return */ protected List<Integer> getBoltIdsForMetaParticle( List<Integer> targetBoltIdList, Class<? extends Particle> particleClass, List<Object> values) { List<Integer> boltIds = new ArrayList<Integer>(); // broadcast the MetaParticle tuple boltIds.addAll(targetBoltIdList); return boltIds; } /** * Get the boltId to which this nonMetaParticle must be routed. Default * behaviour is to route on the first value == the timestamp van the * particle, if available. Otherwise an empty list is returned. * * @param targetBoltIdList * @param particleClass * @param values * @return */ protected List<Integer> getBoltIdsForNonMetaParticle( List<Integer> targetBoltIdList, Class<? extends Particle> particleClass, List<Object> values) { // Is the timestamp field available to base routing on? if (ParticleMapper.TIMESTAMP_IDX < values.size()) { // route on the first value return getBoltIdsForValue(values.get(ParticleMapper.TIMESTAMP_IDX)); } else { return new ArrayList<Integer>(); } } /** * Get the boltId to which this non particle tuple must be routed to. The * routing will be based on the first value, if available. Otherwise an * empty list is returned. * * @param values * @return */ protected List<Integer> getBoltIdsForDefaultTuple(List<Object> values) { // Is there at least one field available to base routing on? if (values.size() > 0) { // route on the first value return getBoltIdsForValue(values.get(0)); } else { return new ArrayList<Integer>(); } } /** * Helper class to determine the boltId beloning to the value, useing the * selectTargetBoltId method. * * @param value * @return */ protected List<Integer> getBoltIdsForValue(Object value) { List<Integer> boltIds = new ArrayList<Integer>(); int targetBoltId = selectTargetBoltId(targetBoltIds, value); boltIds.add(targetBoltId); return boltIds; } /** * Select the boltId from the targetBoltIdList, based on the value. Default * behaviour is the hashcode of the value modulo the size of the * targetBoltIdList * * @param targetBoltIdList * @param value * @return */ protected int selectTargetBoltId(List<Integer> targetBoltIdList, Object value) { // object.hashCode can be negative! first abs before %. return targetBoltIdList.get(Math.abs(value.hashCode()) % targetBoltIdList.size()); } }