com.adaptris.core.interceptor.SlowMessageNotification.java Source code

Java tutorial

Introduction

Here is the source code for com.adaptris.core.interceptor.SlowMessageNotification.java

Source

/*
 * Copyright 2015 Adaptris Ltd.
 * 
 * Licensed 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 com.adaptris.core.interceptor;

import static org.apache.commons.lang.StringUtils.isEmpty;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import javax.management.MalformedObjectNameException;
import javax.validation.Valid;

import com.adaptris.annotation.ComponentProfile;
import com.adaptris.core.AdaptrisComponent;
import com.adaptris.core.AdaptrisMessage;
import com.adaptris.core.CoreException;
import com.adaptris.core.runtime.ParentRuntimeInfoComponent;
import com.adaptris.core.runtime.RuntimeInfoComponent;
import com.adaptris.core.runtime.RuntimeInfoComponentFactory;
import com.adaptris.core.runtime.WorkflowManager;
import com.adaptris.core.util.ManagedThreadFactory;
import com.adaptris.util.TimeInterval;
import com.thoughtworks.xstream.annotations.XStreamAlias;

/**
 * Interceptor that emits a {@link javax.management.Notification} if a message has exceeded the specified threshold
 * for processing within a workflow.
 * 
 * <p>
 * The {@link javax.management.Notification#setUserData(Object)} part of the notification is a {@link java.util.Properties}
 * object containing information about the message that exceeded the interceptors threshold. Note
 * that notifications are emitted whenever a message is deemed to have exceeded the threshold; you
 * might get multiple notifications if a message exceeds the threshold, fails and is automatically
 * retried (which again exceeds the threshold).
 * </p>
 * <p>
 * A cleanup thread is also started as part of this interceptor which purges internal data on a
 * periodic basis; this period is calculated based on the notification threshold (+1 minute) and any
 * messages that are outstanding will have a notification emitted for it, and removed. Because of
 * this, it will be possible to be notified before the message formally exits the workflow, but the
 * notification will still be after the threshold has been exceeded. If this occurs, then the
 * endTime and timeTaken markers are set to {@code -1}
 * </p>
 * 
 * @author lchan
 * 
 * @since 3.0.4
 */
@XStreamAlias("slow-message-notification")
@ComponentProfile(summary = "Interceptor that issues a JMX notification if a message took too long", tag = "interceptor,jmx")
public class SlowMessageNotification extends NotifyingInterceptor {

    private static final String MESSAGE_PREFIX = "Threshold exceeded for ";

    /**
     * Key within the properties containing the messageID
     */
    public static final String KEY_MESSAGE_ID = "message.id";
    /**
     * Key within the properties containing the start time in milliseconds
     * 
     */
    public static final String KEY_MESSAGE_START = "message.startTime";
    /**
     * Key within the properties containing the end time in milliseconds
     * 
     */
    public static final String KEY_MESSAGE_END = "message.endTime";
    /**
     * Key within the properties containing the time taken in milliseconds
     * 
     */
    public static final String KEY_MESSAGE_DURATION = "message.timeTaken";
    /**
     * Key within the properties containing whether the message was successful or not
     * 
     */
    public static final String KEY_MESSAGE_SUCCESS = "message.wasSuccessful";

    private static final TimeInterval DEFAULT_THRESHOLD = new TimeInterval(1L, TimeUnit.MINUTES);

    @Valid
    private TimeInterval notifyThreshold;
    private transient Map<String, MessageThroughputStat> currentMessages;

    private transient ScheduledExecutorService executor;
    private transient ScheduledFuture cleanupTask;
    private transient TimeInterval cleanupInterval;

    static {
        RuntimeInfoComponentFactory.registerComponentFactory(new JmxFactory());
    }

    public SlowMessageNotification() {
        currentMessages = new ConcurrentHashMap<>();
    }

    public SlowMessageNotification(String uid, TimeInterval threshold) {
        this();
        setUniqueId(uid);
        setNotifyThreshold(threshold);
    }

    SlowMessageNotification(String uid, TimeInterval threshold, TimeInterval cleanup) {
        this(uid, threshold);
        cleanupInterval = cleanup;
    }

    @Override
    public void workflowStart(AdaptrisMessage inputMsg) {
        currentMessages.put(inputMsg.getUniqueId(), new MessageThroughputStat(inputMsg.getUniqueId()));
    }

    @Override
    public void workflowEnd(AdaptrisMessage inputMsg, AdaptrisMessage outputMsg) {
        String msgId = inputMsg.getUniqueId();
        MessageThroughputStat c = currentMessages.get(msgId);
        if (c != null) {
            currentMessages.remove(msgId);
            c.updateStats(inputMsg, outputMsg);
            if (c.timeTaken > notifyThreshold()) {
                sendNotification(c);
            }
        }
    }

    @Override
    public void init() throws CoreException {
    }

    @Override
    public void start() throws CoreException {
        executor = Executors.newSingleThreadScheduledExecutor(new ManagedThreadFactory(getClass().getSimpleName()));
        scheduleTask();
    }

    @Override
    public void stop() {
        cancelTask();
        shutdownExecutor();
    }

    @Override
    public void close() {
        currentMessages.clear();
    }

    public TimeInterval getNotifyThreshold() {
        return notifyThreshold;
    }

    /**
     * Specify the duration which if exceeded a {@link javax.management.Notification} will be sent.
     * 
     * @param t the duration, if not specified then 1 minute.
     */
    public void setNotifyThreshold(TimeInterval t) {
        this.notifyThreshold = t;
    }

    long notifyThreshold() {
        return TimeInterval.toMillisecondsDefaultIfNull(getNotifyThreshold(), DEFAULT_THRESHOLD);
    }

    long cleanupInterval() {
        return TimeInterval.toMillisecondsDefaultIfNull(cleanupInterval,
                notifyThreshold() + DEFAULT_THRESHOLD.toMilliseconds());
    }

    private void scheduleTask() {
        cleanupTask = executor.scheduleWithFixedDelay(new CleanupTask(), 100L, cleanupInterval(),
                TimeUnit.MILLISECONDS);
        log.trace("Scheduled {}", cleanupTask);
    }

    private void cancelTask() {
        if (cleanupTask != null) {
            cleanupTask.cancel(true);
            log.trace("Poller {} cancelled", cleanupTask);
            cleanupTask = null;
        }
    }

    private void shutdownExecutor() {
        ManagedThreadFactory.shutdownQuietly(executor, DEFAULT_THRESHOLD);
        executor = null;
    }

    private void sendNotification(MessageThroughputStat d) {
        sendNotification(MESSAGE_PREFIX + d.messageId, d.asProperties());
    }

    private class MessageThroughputStat {

        private long startTime;
        private long timeTaken = -1;
        private long endTime = -1;
        private String messageId;
        private boolean wasSuccessful;

        MessageThroughputStat(String msgId) {
            startTime = System.currentTimeMillis();
            messageId = msgId;
        }

        void updateStats(AdaptrisMessage... msgs) {
            endTime = System.currentTimeMillis();
            timeTaken = Math.abs(endTime - startTime);
            wasSuccessful = wasSuccessful(msgs);
        }

        Properties asProperties() {
            Properties p = new Properties();
            p.setProperty(KEY_MESSAGE_DURATION, String.valueOf(timeTaken));
            p.setProperty(KEY_MESSAGE_START, String.valueOf(startTime));
            p.setProperty(KEY_MESSAGE_ID, messageId);
            p.setProperty(KEY_MESSAGE_END, String.valueOf(endTime));
            p.setProperty(KEY_MESSAGE_SUCCESS, String.valueOf(wasSuccessful));
            return p;
        }
    }

    private class CleanupTask implements Runnable {

        /** @see java.lang.Runnable#run() */
        @Override
        public void run() {
            long now = System.currentTimeMillis();
            List<String> keys = new ArrayList<>(currentMessages.keySet());
            for (String key : keys) {
                MessageThroughputStat c = currentMessages.get(key);
                if (c != null) {
                    if (Math.abs(now - c.startTime) > notifyThreshold()) {
                        // the threshold has been exceeded even though we haven't notified on the message.
                        // Let's just notify on it, and discard it.
                        sendNotification(c);
                        currentMessages.remove(key);
                    }
                }
            }
        }
    }

    private static class JmxFactory extends RuntimeInfoComponentFactory {

        @Override
        protected boolean isSupported(AdaptrisComponent e) {
            if (e != null && e instanceof SlowMessageNotification) {
                return !isEmpty(((SlowMessageNotification) e).getUniqueId());
            }
            return false;
        }

        @Override
        protected RuntimeInfoComponent createComponent(ParentRuntimeInfoComponent parent, AdaptrisComponent e)
                throws MalformedObjectNameException {
            return new InterceptorNotification((WorkflowManager) parent, (SlowMessageNotification) e);
        }

    }

}