org.hyperic.hq.notifications.EndpointQueue.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperic.hq.notifications.EndpointQueue.java

Source

/*
 * NOTE: This copyright does *not* cover user programs that use Hyperic
 * program services by normal system calls through the application
 * program interfaces provided as part of the Hyperic Plug-in Development
 * Kit or the Hyperic Client Development Kit - this is merely considered
 * normal use of the program, and does *not* fall under the heading of
 * "derived work".
 *
 * Copyright (C) [2004-2013], VMware, Inc.
 * This file is part of Hyperic.
 *
 * Hyperic is free software; you can redistribute it and/or modify
 * it under the terms version 2 of the GNU General Public License as
 * published by the Free Software Foundation. This program is distributed
 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA.
 */
package org.hyperic.hq.notifications;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.PostConstruct;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.context.Bootstrap;
import org.hyperic.hq.notifications.filtering.MetricDestinationEvaluator;
import org.hyperic.hq.notifications.filtering.ResourceDestinationEvaluator;
import org.hyperic.hq.notifications.model.BaseNotification;
import org.hyperic.hq.notifications.model.InternalResourceDetailsType;
import org.hyperic.hq.stats.ConcurrentStatsCollector;
import org.hyperic.util.Transformer;
import org.hyperic.util.stats.StatCollector;
import org.hyperic.util.stats.StatUnreachableException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;

@Component
public class EndpointQueue {
    private final Log log = LogFactory.getLog(EndpointQueue.class);
    // XXX should make this configurable in some way
    private static final int QUEUE_LIMIT = 100000;
    private static final long TASK_INTERVAL = 30000;
    private static final String NOTIFICATIONS_PUBLISHED_TO_ENDPOINT = ConcurrentStatsCollector.NOTIFICATIONS_PUBLISHED_TO_ENDPOINT;
    private static final String NOTIFICATIONS_PUBLISHED_TO_ENDPOINT_TIME = ConcurrentStatsCollector.NOTIFICATIONS_PUBLISHED_TO_ENDPOINT_TIME;
    private static final String NOTIFICATION_TOTAL_QUEUE_SIZE = ConcurrentStatsCollector.NOTIFICATION_TOTAL_QUEUE_SIZE;
    // XXX should make this configurable in some way
    private static final int BATCH_SIZE = 25000;
    @Autowired
    private ThreadPoolTaskScheduler notificationExecutor;
    @Autowired
    private ConcurrentStatsCollector concurrentStatsCollector;

    protected final static long EXPIRATION_DURATION = 10 * 60 * 1000;
    MetricDestinationEvaluator metricEvaluator;
    ResourceDestinationEvaluator resourceEvaluator;

    private final Map<String, AccumulatedRegistrationData> registrationData = new HashMap<String, AccumulatedRegistrationData>();
    private final AtomicInteger numConsumers = new AtomicInteger(0);

    public void register(NotificationEndpoint endpoint,
            Transformer<InternalNotificationReport, String> transformer) {
        register(endpoint, null, transformer);
    }

    public int getNumConsumers() {
        return numConsumers.get();
    }

    @PostConstruct
    public void init() {
        metricEvaluator = (MetricDestinationEvaluator) Bootstrap.getBean("metricDestinationEvaluator");
        resourceEvaluator = (ResourceDestinationEvaluator) Bootstrap.getBean("resourceDestinationEvaluator");
        concurrentStatsCollector.register(NOTIFICATIONS_PUBLISHED_TO_ENDPOINT);
        concurrentStatsCollector.register(NOTIFICATIONS_PUBLISHED_TO_ENDPOINT_TIME);
        concurrentStatsCollector.register(new StatCollector() {
            public long getVal() throws StatUnreachableException {
                synchronized (registrationData) {
                    long rtn = 0;
                    for (final AccumulatedRegistrationData data : registrationData.values()) {
                        rtn += data.getAccumulatedNotificationsQueue().size();
                    }
                    return rtn;
                }
            }

            public String getId() {
                return NOTIFICATION_TOTAL_QUEUE_SIZE;
            }
        });
    }

    public void register(NotificationEndpoint endpoint, InternalResourceDetailsType resourceDetailsType,
            Transformer<InternalNotificationReport, String> transformer) {
        final boolean debug = log.isDebugEnabled();
        final AccumulatedRegistrationData data = new AccumulatedRegistrationData(endpoint, QUEUE_LIMIT,
                resourceDetailsType, registrationData);
        synchronized (registrationData) {
            if (registrationData.containsKey(endpoint.getRegistrationId())) {
                if (debug)
                    log.debug("can not register endpoint=" + endpoint + " twice");
                return;
            }
            final String regId = endpoint.getRegistrationId();
            registrationData.put(regId, data);
            numConsumers.incrementAndGet();
            schedule(endpoint, data, transformer);
        }
        if (log.isDebugEnabled()) {
            log.debug("new notification registration=" + endpoint);
        }
    }

    private void schedule(final NotificationEndpoint endpoint, final AccumulatedRegistrationData data,
            final Transformer<InternalNotificationReport, String> transformer) {
        if (!endpoint.canPublish()) {
            return;
        }
        final Runnable task = new Runnable() {
            protected long firstFailure = Long.MAX_VALUE;
            protected boolean isFailedLastPostage = false;

            public void run() {
                int size = 0;
                long totalTime = 0;
                try {
                    final String registrationId = endpoint.getRegistrationId();
                    InternalNotificationReport report = null;
                    final long start = System.currentTimeMillis();
                    final Collection<InternalAndExternalNotificationReports> messages = new ArrayList<InternalAndExternalNotificationReports>();
                    List<InternalNotificationReport> reports = new ArrayList<InternalNotificationReport>();
                    while (report == null || !report.getNotifications().isEmpty()) {
                        report = poll(registrationId, BATCH_SIZE);
                        reports.add(report);
                        final String toPublish = transformer.transform(report);
                        messages.add(new InternalAndExternalNotificationReports(report, toPublish));
                        size += report.getNotifications().size();
                    }
                    List<InternalNotificationReport> failedReports = new ArrayList<InternalNotificationReport>();
                    EndpointStatus batchPostingStatus = endpoint.publishMessagesInBatch(messages, failedReports);
                    checkResultStatus(batchPostingStatus, failedReports);
                    totalTime = System.currentTimeMillis() - start;
                } catch (Throwable t) {
                    log.error(t, t);
                } finally {
                    concurrentStatsCollector.addStat(size, NOTIFICATIONS_PUBLISHED_TO_ENDPOINT);
                    concurrentStatsCollector.addStat(totalTime, NOTIFICATIONS_PUBLISHED_TO_ENDPOINT_TIME);
                }
            }

            private void checkResultStatus(EndpointStatus batchPostingStatus,
                    List<InternalNotificationReport> failedReports) {
                if (!batchPostingStatus.isEmpty()) {
                    // retry the reports which were failed to be published 
                    for (InternalNotificationReport failedReport : failedReports) {
                        @SuppressWarnings("unchecked")
                        List<BaseNotification> failedNotifications = (List<BaseNotification>) failedReport
                                .getNotifications();
                        Map<NotificationEndpoint, Collection<BaseNotification>> map = new HashMap<NotificationEndpoint, Collection<BaseNotification>>();
                        map.put(endpoint, failedNotifications);
                        publishAsync(map);
                    }
                    data.merge(batchPostingStatus);
                    // if the last try was a failure, it means that problem sending notifications
                    // to the endpoint has happened before finishing the whole messages transmission
                    if (batchPostingStatus.getLast().isSuccessful()) {
                        this.isFailedLastPostage = false;
                    } else {
                        if (!this.isFailedLastPostage) {
                            this.isFailedLastPostage = true;
                            this.firstFailure = batchPostingStatus.getLast().getTime();
                        }
                        if (System.currentTimeMillis() - this.firstFailure >= EXPIRATION_DURATION) {
                            unregister(endpoint.getRegistrationId());
                            metricEvaluator.unregisterAll(endpoint);
                            resourceEvaluator.unregisterAll(endpoint);
                        }
                    }
                }
            }
        };
        final Date start = new Date(System.currentTimeMillis() + TASK_INTERVAL);
        ScheduledFuture<?> schedule = notificationExecutor.scheduleWithFixedDelay(task, start, TASK_INTERVAL);
        data.setSchedule(schedule);
    }

    public NotificationEndpoint unregister(String regID) {
        AccumulatedRegistrationData data = null;
        synchronized (registrationData) {
            // don't delete the data, we want to be able to access the endpoint by registrationId
            data = registrationData.get(regID);
            if (data == null || !data.isValid()) {
                if (log.isDebugEnabled()) {
                    log.debug((data == null ? "No queue assigned" : "Queue is already invalid") + " for regId: "
                            + regID);
                }
                return null;
            }
            numConsumers.decrementAndGet();
            data.markInvalid();
            data.clear();
            final ScheduledFuture<?> schedule = data.getSchedule();
            if (schedule != null) {
                schedule.cancel(true);
            }
            if (log.isDebugEnabled()) {
                log.debug("Removing the queue assigned for regId: " + regID);
            }
        }
        return data.getNotificationEndpoint();
    }

    public InternalNotificationReport poll(String registrationId) {
        return poll(registrationId, Integer.MAX_VALUE);
    }

    public InternalNotificationReport poll(String registrationId, int maxSize) {
        final InternalNotificationReport rtn = new InternalNotificationReport();
        final List<BaseNotification> notifications = new ArrayList<BaseNotification>();
        synchronized (registrationData) {
            AccumulatedRegistrationData data = registrationData.get(registrationId);
            if (data == null || !data.isValid()) {
                return rtn;
            }
            data.drainTo(notifications, maxSize);
            rtn.setNotifications(notifications);
            rtn.setResourceDetailsType(data.getResourceContentType());
            return rtn;
        }
    }

    public <T extends BaseNotification> void publishAsync(Map<NotificationEndpoint, Collection<T>> map) {
        synchronized (registrationData) {
            for (final Entry<NotificationEndpoint, Collection<T>> entry : map.entrySet()) {
                final NotificationEndpoint endpoint = entry.getKey();
                final Collection<T> list = entry.getValue();
                final AccumulatedRegistrationData data = registrationData.get(endpoint.getRegistrationId());
                if (data != null) {
                    data.addAll(list);
                }
            }
        }
    }

    public NotificationEndpoint getEndpoint(String registrationID) {
        if (registrationID == null) {
            return null;
        }
        synchronized (registrationData) {
            AccumulatedRegistrationData data = registrationData.get(registrationID);
            if (data != null) {
                return data.getNotificationEndpoint();
            }
        }
        return null;
    }

    public EndpointAndRegStatus getEndpointAndRegStatus(String registrationID) {
        synchronized (registrationData) {
            AccumulatedRegistrationData ard = this.registrationData.get(registrationID);
            if (ard != null) {
                RegistrationStatus regStat = new RegistrationStatus();
                regStat.setCreationTime(ard.getCreationTime());
                regStat.setValid(ard.isValid());
                EndpointStatus endpointStatus = null;
                EndpointStatus ardStatus = ard.getEndpointStatus();
                if (ardStatus != null) {
                    endpointStatus = new EndpointStatus(ardStatus);
                }
                return new EndpointAndRegStatus(endpointStatus, regStat);
            } else {
                this.log.error("there is no AccumulatedRegistrationData for registration " + registrationID);
            }
        }
        return null;
    }

    public static class EndpointAndRegStatus {
        protected EndpointStatus endpointStatus;
        protected RegistrationStatus regStat;

        public EndpointAndRegStatus(EndpointStatus endpointStatus, RegistrationStatus regStat) {
            super();
            this.endpointStatus = endpointStatus;
            this.regStat = regStat;
        }

        public EndpointStatus getEndpointStatus() {
            return endpointStatus;
        }

        public RegistrationStatus getRegStatus() {
            return regStat;
        }
    }
}