edu.wisc.jmeter.dao.JdbcMonitorDao.java Source code

Java tutorial

Introduction

Here is the source code for edu.wisc.jmeter.dao.JdbcMonitorDao.java

Source

/**
 * Copyright (c) 2000-2009, Jasig, Inc.
 * See license distributed with this file and available online at
 * https://www.ja-sig.org/svn/jasig-parent/tags/rel-10/license-header.txt
 */

package edu.wisc.jmeter.dao;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

import javax.sql.DataSource;

import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import edu.wisc.jmeter.HostStatus;
import edu.wisc.jmeter.Notification;
import edu.wisc.jmeter.Status;

/**
 * @author Eric Dalquist
 * @version $Revision: 1.8 $
 */
public class JdbcMonitorDao implements InitializingBean, DisposableBean, MonitorDao {
    private static final Logger log = LoggingManager.getLoggerForClass();

    private static final Map<String, String> TABLE_CONFIG;

    static {
        final Map<String, String> tableConfigBuilder = new HashMap<String, String>();

        tableConfigBuilder.put("MONITOR_HOST_STATUS",
                "CREATE TABLE MONITOR_HOST_STATUS (\n" + "    HOST_NAME VARCHAR2(500),\n"
                        + "    STATUS VARCHAR2(50) NOT NULL,\n" + "    FAILURE_COUNT NUMBER,\n"
                        + "    MESSAGE_COUNT NUMBER,\n" + "    LAST_NOTIFICATION TIMESTAMP,\n"
                        + "    LAST_UPDATED TIMESTAMP,\n"
                        + "    CONSTRAINT PK_MONITOR_HOST_STATUS PRIMARY KEY (HOST_NAME)\n" + ")");

        tableConfigBuilder.put("MONITOR_LOG",
                "CREATE TABLE MONITOR_LOG (\n" + "    HOST_NAME VARCHAR2(500),\n" + "    LABEL VARCHAR2(2000),\n"
                        + "    LAST_SAMPLE TIMESTAMP,\n" + "    DURATION NUMBER,\n" + "    SUCCESS VARCHAR2(10),\n"
                        + "    CONSTRAINT PK_MONITOR_LOG PRIMARY KEY (HOST_NAME, LABEL)\n" + ")");

        tableConfigBuilder.put("MONITOR_ERRORS",
                "CREATE TABLE MONITOR_ERRORS (\n" + "    HOST_NAME VARCHAR2(500),\n" + "    LABEL VARCHAR2(2000),\n"
                        + "    FAILURE_DATE TIMESTAMP,\n" + "    STATUS VARCHAR2(50),\n"
                        + "    EMAIL_SUBJECT VARCHAR2(1000),\n" + "    EMAIL_BODY VARCHAR2(4000),\n"
                        + "    EMAIL_SENT VARCHAR2(10)\n" + ")");

        TABLE_CONFIG = Collections.unmodifiableMap(tableConfigBuilder);
    }

    private final ConcurrentMap<String, Object> hostMutexMap = new ConcurrentHashMap<String, Object>();
    private final Map<String, HostStatus> hostStatusCache = new ConcurrentHashMap<String, HostStatus>();
    private Timer purgingTimer;

    //Purge times are in milliseconds
    private final long purgeStatusCache = TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES);
    private final long purgeOldFailure;
    private final long purgeOldStatus;
    private final NamedParameterJdbcTemplate jdbcTemplate;
    private final TransactionTemplate transactionTemplate;

    public JdbcMonitorDao(DataSource dataSource, int purgeOldFailures, int purgeOldStatus) {
        this.jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);

        final DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(
                dataSource);
        dataSourceTransactionManager.afterPropertiesSet();

        this.transactionTemplate = new TransactionTemplate(dataSourceTransactionManager);
        this.transactionTemplate.afterPropertiesSet();

        this.purgeOldFailure = TimeUnit.MILLISECONDS.convert(purgeOldFailures, TimeUnit.MINUTES);
        this.purgeOldStatus = TimeUnit.MILLISECONDS.convert(purgeOldStatus, TimeUnit.MINUTES);
    }

    public JdbcMonitorDao(JdbcTemplate jdbcTemplate, PlatformTransactionManager transactionManager,
            int purgeOldFailures, int purgeOldStatus) {
        this.jdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate);
        this.transactionTemplate = new TransactionTemplate(transactionManager);

        this.purgeOldFailure = TimeUnit.MILLISECONDS.convert(purgeOldFailures, TimeUnit.MINUTES);
        this.purgeOldStatus = TimeUnit.MILLISECONDS.convert(purgeOldStatus, TimeUnit.MINUTES);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.setupTables();

        this.purgingTimer = new Timer("JdbcMonitorDao_PurgingTimer", true);
        this.purgingTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                purgeFailureLog(new Date(System.currentTimeMillis() - purgeOldFailure));
                purgeRequestLog(new Date(System.currentTimeMillis() - purgeOldStatus));
                purgeStatusCache(new Date(System.currentTimeMillis() - purgeStatusCache)); //HostStatus objects can be rebuilt, don't hold stuff older than 5 minutes
            }
        }, 1000 * 60, //Run 1 minute after starting 
                1000 * 60 * 5); //Repeat every 5 minutes
    }

    private void setupTables() {
        final JdbcOperations jdbcOperations = this.jdbcTemplate.getJdbcOperations();

        for (final Map.Entry<String, String> tableConfigEntry : TABLE_CONFIG.entrySet()) {
            jdbcOperations.execute(new ConnectionCallback<Object>() {
                @Override
                public Object doInConnection(Connection con) throws SQLException, DataAccessException {
                    final DatabaseMetaData metaData = con.getMetaData();

                    final String tableName = tableConfigEntry.getKey();
                    final ResultSet tables = metaData.getTables(null, null, tableName, null);
                    try {
                        if (!tables.next()) {
                            log.warn("'" + tableName + "' table does not exist, creating.");
                            jdbcOperations.update(tableConfigEntry.getValue());
                        } else {
                            log.info("'" + tableName + "' table already exists, skipping.");
                        }
                    } finally {
                        tables.close();
                    }

                    return null;
                }
            });
        }
    }

    @Override
    public void destroy() throws Exception {
        this.purgingTimer.cancel();
        this.purgingTimer = null;
    }

    @Override
    public void purgeStatusCache(final Date before) {
        int removedStatuses = 0;
        for (final Iterator<Map.Entry<String, HostStatus>> hostStatusIterator = hostStatusCache.entrySet()
                .iterator(); hostStatusIterator.hasNext();) {
            final Entry<String, HostStatus> hostStatusEntry = hostStatusIterator.next();
            final HostStatus hostStatus = hostStatusEntry.getValue();
            if (hostStatus.getLastUpdated().before(before)) {
                hostMutexMap.remove(hostStatusEntry.getKey());
                hostStatusIterator.remove();
                removedStatuses++;
            }
        }
        if (removedStatuses > 0) {
            log.info("Purged " + removedStatuses + " HostStatus objects older than " + before + " from memory");
        }
    }

    @Override
    public void purgeRequestLog(final String host, final Date before) {
        final Map<String, Object> params = new LinkedHashMap<String, Object>();
        params.put("before", before);
        params.put("host", host);

        this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                final int purgedRequests = jdbcTemplate.update(
                        "DELETE FROM MONITOR_LOG " + "WHERE HOST_NAME = :host AND LAST_SAMPLE < :before", params);
                if (purgedRequests > 0) {
                    log.info("Purged " + purgedRequests + " requests for " + host + " older than " + before
                            + " from database");
                }
            }
        });
    }

    @Override
    public void purgeRequestLog(final Date before) {
        final Map<String, Object> params = new LinkedHashMap<String, Object>();
        params.put("before", before);

        this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                final int purgedRequests = jdbcTemplate
                        .update("DELETE FROM MONITOR_LOG " + "WHERE LAST_SAMPLE < :before", params);
                if (purgedRequests > 0) {
                    log.info("Purged " + purgedRequests + " requests older than " + before + " from database");
                }

                final int purgedStatuses = jdbcTemplate
                        .update("DELETE FROM MONITOR_HOST_STATUS " + "WHERE LAST_UPDATED < :before", params);
                if (purgedStatuses > 0) {
                    log.info("Purged " + purgedStatuses + " statuses older than " + before + " from database");
                }
            }
        });
    }

    @Override
    public void purgeFailureLog(final Date before) {
        final Map<String, Object> params = new LinkedHashMap<String, Object>();
        params.put("before", before);

        this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                final int purged = jdbcTemplate
                        .update("DELETE FROM MONITOR_ERRORS " + "WHERE FAILURE_DATE < :before", params);

                if (purged > 0) {
                    log.info("Purged " + purged + " failures older than " + before + " from database");
                }
            }
        });
    }

    @Override
    public HostStatus getHostStatus(final String hostName) {
        final Object lock = this.getHostLock(hostName);

        synchronized (lock) {
            HostStatus hostStatus = this.hostStatusCache.get(hostName);
            if (hostStatus != null) {
                return hostStatus;
            }

            final Map<String, Object> params = new LinkedHashMap<String, Object>();
            params.put("hostName", hostName);

            try {
                hostStatus = this.transactionTemplate.execute(new TransactionCallback<HostStatus>() {
                    @Override
                    public HostStatus doInTransaction(TransactionStatus transactionStatus) {
                        final List<HostStatus> results = jdbcTemplate.query(
                                "SELECT STATUS, FAILURE_COUNT, MESSAGE_COUNT, LAST_NOTIFICATION, LAST_UPDATED "
                                        + "FROM MONITOR_HOST_STATUS " + "WHERE HOST_NAME = :hostName",
                                params, new RowMapper<HostStatus>() {
                                    @Override
                                    public HostStatus mapRow(ResultSet rs, int row) throws SQLException {
                                        final HostStatus hostStatus = new HostStatus();

                                        hostStatus.setHost(hostName);
                                        hostStatus.setStatus(Status.valueOf(rs.getString("STATUS")));
                                        hostStatus.setFailureCount(rs.getInt("FAILURE_COUNT"));
                                        hostStatus.setMessageCount(rs.getInt("MESSAGE_COUNT"));
                                        hostStatus.setLastMessageSent(rs.getTimestamp("LAST_NOTIFICATION"));
                                        hostStatus.setLastUpdated(rs.getTimestamp("LAST_UPDATED"));

                                        return hostStatus;
                                    }
                                });

                        HostStatus hostStatus = DataAccessUtils.singleResult(results);
                        if (hostStatus != null) {
                            return hostStatus;
                        }

                        hostStatus = new HostStatus();
                        hostStatus.setHost(hostName);
                        hostStatus.setLastUpdated(new Date());

                        params.put("status", hostStatus.getStatus().toString());
                        params.put("failureCount", hostStatus.getFailureCount());
                        params.put("messageCount", hostStatus.getMessageCount());
                        params.put("lastNotification", hostStatus.getLastMessageSent());
                        params.put("lastUpdated", hostStatus.getLastUpdated());

                        jdbcTemplate.update(
                                "INSERT INTO MONITOR_HOST_STATUS (HOST_NAME, STATUS, FAILURE_COUNT, MESSAGE_COUNT, LAST_NOTIFICATION, LAST_UPDATED) "
                                        + "VALUES (:hostName, :status, :failureCount, :messageCount, :lastNotification, :lastUpdated)",
                                params);

                        return hostStatus;
                    }
                });
            } catch (RuntimeException re) {
                //Want things to still work if the database is broken so create an empty HostStatus to work with in memory only
                if (hostStatus == null) {
                    hostStatus = new HostStatus();
                    hostStatus.setHost(hostName);
                    hostStatus.setLastUpdated(new Date());
                }

                log.warn("Failed to retrieve/create HostStatus via database, using memory storage only", re);
            }

            this.hostStatusCache.put(hostName, hostStatus);
            return hostStatus;
        }
    }

    @Override
    public void storeHostStatus(HostStatus hostStatus) {
        hostStatus.setLastUpdated(new Date());

        final Map<String, Object> params = new LinkedHashMap<String, Object>();
        params.put("hostName", hostStatus.getHost());
        params.put("status", hostStatus.getStatus().toString());
        params.put("failureCount", hostStatus.getFailureCount());
        params.put("messageCount", hostStatus.getMessageCount());
        params.put("lastNotification", hostStatus.getLastMessageSent());
        params.put("lastUpdated", hostStatus.getLastUpdated());

        this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                jdbcTemplate.update("UPDATE MONITOR_HOST_STATUS " + "SET " + "STATUS = :status, "
                        + "FAILURE_COUNT = :failureCount, " + "MESSAGE_COUNT = :messageCount, "
                        + "LAST_NOTIFICATION = :lastNotification," + "LAST_UPDATED =  :lastUpdated "
                        + "WHERE HOST_NAME = :hostName", params);
            }
        });
    }

    @Override
    public void logFailure(String hostName, String label, Date requestTimestamp, Status status, String subject,
            String body, Notification sentEmail) {
        final Map<String, Object> params = new LinkedHashMap<String, Object>();
        params.put("hostName", hostName);
        params.put("label", label);
        params.put("failureDate", requestTimestamp);
        params.put("status", status.toString());
        params.put("emailSubject", subject);
        params.put("emailBody", body);
        params.put("emailSent", sentEmail.toString());

        this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                jdbcTemplate.update(
                        "INSERT INTO MONITOR_ERRORS (HOST_NAME, LABEL, FAILURE_DATE, STATUS, EMAIL_SUBJECT, EMAIL_BODY, EMAIL_SENT) "
                                + "VALUES (:hostName, :label, :failureDate, :status, :emailSubject, :emailBody, :emailSent)",
                        params);
            }
        });
    }

    @Override
    public void logRequest(String hostName, String label, Date requestTimestamp, long duration,
            boolean successful) {
        final Map<String, Object> params = new LinkedHashMap<String, Object>();
        params.put("hostName", hostName);
        params.put("label", label);
        params.put("lastSample", requestTimestamp);
        params.put("successful", Boolean.toString(successful));
        params.put("duration", duration);

        this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                final int affected = jdbcTemplate.update(
                        "UPDATE MONITOR_LOG " + "SET " + "LAST_SAMPLE = :lastSample, " + "DURATION = :duration, "
                                + "SUCCESS = :successful " + "WHERE HOST_NAME = :hostName AND LABEL = :label",
                        params);

                if (affected == 0) {
                    jdbcTemplate
                            .update("INSERT INTO MONITOR_LOG (HOST_NAME, LABEL, LAST_SAMPLE, DURATION, SUCCESS) "
                                    + "VALUES (:hostName, :label, :lastSample, :duration, :successful)", params);
                }
            }
        });
    }

    @Override
    public void logRequestAndStatus(final HostStatus hostStatus, final String label, final Date requestTimestamp,
            final long duration, final boolean successful) {
        this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                storeHostStatus(hostStatus);
                logRequest(hostStatus.getHost(), label, requestTimestamp, duration, successful);
            }
        });
    }

    @Override
    public void logFailureAndStatus(final HostStatus hostStatus, final String label, final Date requestTimestamp,
            final Status status, final String subject, final String body, final Notification sentEmail) {
        try {
            this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    storeHostStatus(hostStatus);
                    logFailure(hostStatus.getHost(), label, requestTimestamp, status, subject, body, sentEmail);
                }
            });
        } catch (RuntimeException re) {
            log.warn("Failed to log failure and status to database", re);
        }
    }

    protected Object getHostLock(String hostName) {
        Object lock = this.hostMutexMap.get(hostName);
        if (lock == null) {
            lock = new Object();
            final Object existingLock = this.hostMutexMap.putIfAbsent(hostName, lock);
            if (existingLock != null) {
                //Another thread created the lock before us, use the _one_ instance from the Map
                return existingLock;
            }
        }
        return lock;
    }

    protected final void clearHostStatusCache() {
        this.hostStatusCache.clear();
    }
}