package eu.europeana.cloud.service.dps.storm; import eu.europeana.cloud.common.model.dps.States; import eu.europeana.cloud.common.model.dps.TaskState; import org.apache.storm.Config; import org.apache.storm.task.OutputCollector; import org.apache.storm.task.TopologyContext; import org.apache.storm.topology.OutputFieldsDeclarer; import org.apache.storm.topology.base.BaseRichBolt; import org.apache.storm.tuple.Tuple; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.europeana.cloud.service.dps.TaskExecutionKillService; import eu.europeana.cloud.service.dps.service.zoo.ZookeeperKillService; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Date; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; /** * Abstract class for all Storm bolts used in Europeana Cloud. * * @author Pavel Kefurt <Pavel.Kefurt@gmail.com> */ public abstract class AbstractDpsBolt extends BaseRichBolt { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDpsBolt.class); public static final String NOTIFICATION_STREAM_NAME = "NotificationStream"; protected Tuple inputTuple; protected Map stormConfig; protected TopologyContext topologyContext; protected OutputCollector outputCollector; protected TaskExecutionKillService killService; protected String topologyName; public abstract void execute(StormTaskTuple t); public abstract void prepare(); @Override public void execute(Tuple tuple) { LOGGER.info("Received tuple :" + tuple.toString()); inputTuple = tuple; StormTaskTuple t = null; try { t = StormTaskTuple.fromStormTuple(tuple); if (killService.hasKillFlag(topologyName, t.getTaskId())) { LOGGER.info("Task {} going to be killed.", t.getTaskId()); emitKillNotification(t.getTaskId(), t.getFileUrl(), "", ""); return; } LOGGER.info("Mapped to StormTaskTuple :" + t.toStormTuple().toString()); execute(t); } catch (Exception e) { LOGGER.info("AbstractDpsBolt error: {} \nStackTrace: \n{}", e.getMessage(), e.getStackTrace()); if (t != null) { StringWriter stack = new StringWriter(); e.printStackTrace(new PrintWriter(stack)); emitErrorNotification(t.getTaskId(), t.getFileUrl(), e.getMessage(), stack.toString()); } } finally { outputCollector.ack(tuple); } } @Override public void prepare(Map stormConfig, TopologyContext tc, OutputCollector oc) { this.stormConfig = stormConfig; this.topologyContext = tc; this.outputCollector = oc; List<String> zooServers = (List<String>) stormConfig.get(Config.STORM_ZOOKEEPER_SERVERS); String zooPort = String.valueOf(stormConfig.get(Config.STORM_ZOOKEEPER_PORT)); this.topologyName = (String) stormConfig.get(Config.TOPOLOGY_NAME); //String connectString = String.join(":"+zooPort+",", zooServers); //Java 8 String connectString = StringUtils.join(zooServers, ":" + zooPort + ","); //Java 7 this.killService = new ZookeeperKillService(connectString); prepare(); } @Override public void declareOutputFields(OutputFieldsDeclarer declarer) { //default stream declarer.declare(StormTaskTuple.getFields()); //notifications declarer.declareStream(NOTIFICATION_STREAM_NAME, NotificationTuple.getFields()); } /** * Emit {@link NotificationTuple} with error notification to {@link #NOTIFICATION_STREAM_NAME}. * Only one notification call per resource per task. * * @param taskId task ID * @param resource affected resource (e.g. file URL) * @param message short text * @param additionalInformations the rest of informations (e.g. stack trace) */ protected void emitDropNotification(long taskId, String resource, String message, String additionalInformations) { NotificationTuple nt = NotificationTuple.prepareNotification(taskId, resource, States.DROPPED, message, additionalInformations); outputCollector.emit(NOTIFICATION_STREAM_NAME, nt.toStormTuple()); } /** * Emit {@link NotificationTuple} with error notification to {@link #NOTIFICATION_STREAM_NAME}. * Only one notification call per resource per task. * * @param taskId task ID * @param resource affected resource (e.g. file URL) * @param message short text * @param additionalInformations the rest of informations (e.g. stack trace) */ protected void emitErrorNotification(long taskId, String resource, String message, String additionalInformations) { NotificationTuple nt = NotificationTuple.prepareNotification(taskId, resource, States.ERROR, message, additionalInformations); outputCollector.emit(NOTIFICATION_STREAM_NAME, nt.toStormTuple()); } /** * Emit {@link NotificationTuple} with kill notification to {@link #NOTIFICATION_STREAM_NAME}. * Only one notification call per resource per task. * * @param taskId task ID * @param resource affected resource (e.g. file URL) * @param message short text * @param additionalInformations the rest of informations */ protected void emitKillNotification(long taskId, String resource, String message, String additionalInformations) { NotificationTuple nt = NotificationTuple.prepareNotification(taskId, resource, States.KILLED, message, additionalInformations); outputCollector.emit(NOTIFICATION_STREAM_NAME, nt.toStormTuple()); } protected void endTask(long taskId,String info, TaskState state, Date finishTime) { NotificationTuple nt = NotificationTuple.prepareEndTask(taskId, info, state, finishTime); outputCollector.emit(NOTIFICATION_STREAM_NAME, nt.toStormTuple()); } protected void updateTask(long taskId,String info, TaskState state, Date startTime) { NotificationTuple nt = NotificationTuple.prepareUpdateTask(taskId, info, state, startTime); outputCollector.emit(NOTIFICATION_STREAM_NAME, nt.toStormTuple()); } protected void logAndEmitError(StormTaskTuple t, String message) { LOGGER.error(message); emitErrorNotification(t.getTaskId(), t.getFileUrl(), message, t.getParameters().toString()); } protected void logAndEmitError(StormTaskTuple t, String message, Exception e) { LOGGER.error(message, e); StringWriter stack = new StringWriter(); e.printStackTrace(new PrintWriter(stack)); logAndEmitError(t, message + e.getMessage()); } protected void emitSuccess(StormTaskTuple t) { outputCollector.emit(inputTuple, t.toStormTuple()); } }