package eu.europeana.cloud.service.dps.storm; import com.datastax.driver.core.exceptions.NoHostAvailableException; import com.datastax.driver.core.exceptions.QueryExecutionException; import eu.europeana.cloud.cassandra.CassandraConnectionProvider; import eu.europeana.cloud.cassandra.CassandraConnectionProviderSingleton; import eu.europeana.cloud.common.model.dps.TaskInfo; import eu.europeana.cloud.common.model.dps.TaskState; import eu.europeana.cloud.service.dps.exception.DatabaseConnectionException; import eu.europeana.cloud.service.dps.exception.TaskInfoDoesNotExistException; import eu.europeana.cloud.service.dps.storm.utils.CassandraSubTaskInfoDAO; import eu.europeana.cloud.service.dps.storm.utils.CassandraTaskInfoDAO; import eu.europeana.cloud.service.dps.util.LRUCache; import org.apache.commons.lang3.Validate; 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 java.util.*; /** * This bolt is responsible for store notifications to Cassandra. * * @author Pavel Kefurt <Pavel.Kefurt@gmail.com> */ public class NotificationBolt extends BaseRichBolt { private static final Logger LOGGER = LoggerFactory .getLogger(NotificationBolt.class); protected Map stormConfig; protected TopologyContext topologyContext; protected OutputCollector outputCollector; private final String hosts; private final int port; private final String keyspaceName; private final String userName; private final String password; private static LRUCache<Long, NotificationCache> cache = new LRUCache<Long, NotificationCache>( 100); private String topologyName; private static CassandraConnectionProvider cassandraConnectionProvider; private static CassandraTaskInfoDAO taskInfoDAO; private static CassandraSubTaskInfoDAO subTaskInfoDAO; private static final int PROCESSED_INTERVAL = 100; /** * Constructor of notification bolt. * * @param hosts Cassandra hosts separated by comma (e.g. * localhost,192.168.47.129) * @param port Cassandra port * @param keyspaceName Cassandra keyspace name * @param userName Cassandra username * @param password Cassandra password */ public NotificationBolt(String hosts, int port, String keyspaceName, String userName, String password) { this.hosts = hosts; this.port = port; this.keyspaceName = keyspaceName; this.userName = userName; this.password = password; } @Override public void execute(Tuple tuple) { try { NotificationTuple notificationTuple = NotificationTuple .fromStormTuple(tuple); NotificationCache nCache = cache.get(notificationTuple.getTaskId()); if (nCache == null) { nCache = new NotificationCache(getExpectedSize(notificationTuple.getTaskId())); cache.put(notificationTuple.getTaskId(), nCache); } storeTaskDetails(notificationTuple, nCache); } catch (NoHostAvailableException | QueryExecutionException ex) { LOGGER.error("Cannot store notification to Cassandra because: {}", ex.getMessage()); return; } catch (Exception ex) { LOGGER.error("Problem with store notification because: {}", ex.getMessage()); return; } finally { outputCollector.ack(tuple); } } private void storeTaskDetails(NotificationTuple notificationTuple, NotificationCache nCache) throws TaskInfoDoesNotExistException, DatabaseConnectionException { long taskId = notificationTuple.getTaskId(); switch (notificationTuple.getInformationType()) { case UPDATE_TASK: updateTask(taskId, notificationTuple.getParameters()); break; case END_TASK: endTask(taskId, nCache.getProcessed(), notificationTuple.getParameters()); break; case NOTIFICATION: nCache.inc(); int processesFilesCount = nCache.getProcessed(); storeNotification(processesFilesCount, taskId, notificationTuple.getParameters()); if (nCache.isComplete()) { storeFinishState(taskId, processesFilesCount); } else { if ((processesFilesCount % PROCESSED_INTERVAL) == 0) taskInfoDAO.setUpdateProcessedFiles(taskId, processesFilesCount); } break; } } @Override public void prepare(Map stormConf, TopologyContext tc, OutputCollector oc) { cassandraConnectionProvider = CassandraConnectionProviderSingleton.getCassandraConnectionProvider(hosts, port, keyspaceName, userName, password); taskInfoDAO = CassandraTaskInfoDAO.getInstance(cassandraConnectionProvider); subTaskInfoDAO = CassandraSubTaskInfoDAO.getInstance(cassandraConnectionProvider); topologyName = (String) stormConf.get(Config.TOPOLOGY_NAME); this.stormConfig = stormConf; this.topologyContext = tc; this.outputCollector = oc; } @Override public void declareOutputFields(OutputFieldsDeclarer ofd) { } private void updateTask(long taskId, Map<String, Object> parameters) throws DatabaseConnectionException { Validate.notNull(parameters); String state = String.valueOf(parameters.get(NotificationParameterKeys.TASK_STATE)); String info = String.valueOf(parameters.get(NotificationParameterKeys.INFO)); Date startDate = prepareDate(parameters.get(NotificationParameterKeys.START_TIME)); taskInfoDAO.updateTask(taskId, info, state, startDate); } private void endTask(long taskId, int processeFilesCount, Map<String, Object> parameters) throws DatabaseConnectionException { Validate.notNull(parameters); String state = String.valueOf(parameters.get(NotificationParameterKeys.TASK_STATE)); Date finishDate = prepareDate(parameters.get(NotificationParameterKeys.FINISH_TIME)); String info = String.valueOf(parameters.get(NotificationParameterKeys.INFO)); taskInfoDAO.endTask(taskId, processeFilesCount, info, state, finishDate); } private static Date prepareDate(Object dateObject) { Date date = null; if (dateObject instanceof Date) return (Date) dateObject; return date; } private void storeFinishState(long taskId, int processeFilesCount) throws TaskInfoDoesNotExistException, DatabaseConnectionException { taskInfoDAO.endTask(taskId, processeFilesCount, "Completely processed", String.valueOf(TaskState.PROCESSED), new Date()); } private void storeNotification(int resourceNum, long taskId, Map<String, Object> parameters) throws DatabaseConnectionException { Validate.notNull(parameters); String resource = String.valueOf(parameters.get(NotificationParameterKeys.RESOURCE)); String state = String.valueOf(parameters.get(NotificationParameterKeys.STATE)); String infoText = String.valueOf(parameters.get(NotificationParameterKeys.INFO_TEXT)); String additionalInfo = String.valueOf(parameters.get(NotificationParameterKeys.ADDITIONAL_INFORMATIONS)); String resultResource = String.valueOf(parameters.get(NotificationParameterKeys.RESULT_RESOURCE)); subTaskInfoDAO.insert(resourceNum, taskId, topologyName, resource, state, infoText, additionalInfo, resultResource); } private static class NotificationCache { int totalSize; int processed = 0; NotificationCache(int totalSize) { this.totalSize = totalSize; } public void inc() { processed++; } public Boolean isComplete() { return totalSize != -1 ? processed >= totalSize : false; } public int getProcessed() { return processed; } } public static void clearCache() { cache.clear(); } private int getExpectedSize(long taskId) throws TaskInfoDoesNotExistException { TaskInfo task = taskInfoDAO.searchById(taskId); return task.getContainsElements(); } }