org.hyperic.hq.measurement.server.session.DataManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperic.hq.measurement.server.session.DataManagerImpl.java

Source

/*
 * NOTE: This copyright does *not* cover user programs that use HQ
 * 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-2010], Hyperic, Inc.
 * This file is part of HQ.
 *
 * HQ 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.measurement.server.session;

import java.math.BigDecimal;
import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import javax.annotation.PostConstruct;

import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hibernate.dialect.HQDialect;
import org.hyperic.hq.common.ProductProperties;
import org.hyperic.hq.common.SystemException;
import org.hyperic.hq.common.TimeframeBoundriesException;
import org.hyperic.hq.common.shared.HQConstants;
import org.hyperic.hq.common.shared.ServerConfigManager;
import org.hyperic.hq.common.util.MessagePublisher;
import org.hyperic.hq.events.EventConstants;
import org.hyperic.hq.events.ext.RegisteredTriggers;
import org.hyperic.hq.measurement.MeasurementConstants;
import org.hyperic.hq.measurement.TimingVoodoo;
import org.hyperic.hq.measurement.data.MeasurementDataSourceException;
import org.hyperic.hq.measurement.ext.MeasurementEvent;
import org.hyperic.hq.measurement.shared.AvailabilityManager;
import org.hyperic.hq.measurement.shared.DataManager;
import org.hyperic.hq.measurement.shared.HighLowMetricValue;
import org.hyperic.hq.measurement.shared.MeasRange;
import org.hyperic.hq.measurement.shared.MeasRangeObj;
import org.hyperic.hq.measurement.shared.MeasTabManagerUtil;
import org.hyperic.hq.measurement.shared.MeasurementManager;
import org.hyperic.hq.measurement.shared.TopNManager;
import org.hyperic.hq.plugin.system.TopReport;
import org.hyperic.hq.product.MetricValue;
import org.hyperic.hq.stats.ConcurrentStatsCollector;
import org.hyperic.hq.zevents.ZeventEnqueuer;
import org.hyperic.util.TimeUtil;
import org.hyperic.util.jdbc.DBUtil;
import org.hyperic.util.pager.PageControl;
import org.hyperic.util.pager.PageList;
import org.hyperic.util.timer.StopWatch;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * The DataManagerImpl can be used to retrieve measurement data points
 * 
 */
@Service
@Transactional

public class DataManagerImpl implements DataManager {
    private static final String ERR_START = "Begin and end times must be positive";
    private static final String ERR_END = "Start time must be earlier than end time";
    private static final String LOG_CTX = DataManagerImpl.class.getName();
    private final Log log = LogFactory.getLog(LOG_CTX);

    // The boolean system property that makes all events interesting. This
    // property is provided as a testing hook so we can flood the event
    // bus on demand.
    public static final String ALL_EVENTS_INTERESTING_PROP = "org.hq.triggers.all.events.interesting";

    private static final BigDecimal MAX_DB_NUMBER = new BigDecimal("10000000000000000000000");

    private static final long MINUTE = 60 * 1000, HOUR = 60 * MINUTE;
    protected final static long HOUR_IN_MILLI = TimeUnit.MILLISECONDS.convert(1L, TimeUnit.HOURS);
    protected final static long SIX_HOURS_IN_MILLI = TimeUnit.MILLISECONDS.convert(6L, TimeUnit.HOURS);
    protected final static long DAY_IN_MILLI = TimeUnit.MILLISECONDS.convert(1L, TimeUnit.DAYS);

    // Table names
    private static final String TAB_DATA_1H = MeasurementConstants.TAB_DATA_1H;
    private static final String TAB_DATA_6H = MeasurementConstants.TAB_DATA_6H;
    private static final String TAB_DATA_1D = MeasurementConstants.TAB_DATA_1D;
    private static final String TAB_MEAS = MeasurementConstants.TAB_MEAS;
    private static final String DATA_MANAGER_INSERT_TIME = ConcurrentStatsCollector.DATA_MANAGER_INSERT_TIME;

    // Error strings
    private static final String ERR_INTERVAL = "Interval cannot be larger than the time range";

    private static final String PLSQL = "BEGIN " + "INSERT INTO :table (measurement_id, timestamp, value) "
            + "VALUES(?, ?, ?); " + "EXCEPTION WHEN DUP_VAL_ON_INDEX THEN " + "UPDATE :table SET VALUE = ? "
            + "WHERE timestamp = ? and measurement_id = ?; " + "END; ";

    // Save some typing
    private static final int IND_MIN = MeasurementConstants.IND_MIN;
    private static final int IND_AVG = MeasurementConstants.IND_AVG;
    private static final int IND_MAX = MeasurementConstants.IND_MAX;
    private static final int IND_CFG_COUNT = MeasurementConstants.IND_CFG_COUNT;

    private final DBUtil dbUtil;

    // Pager class name
    private boolean confDefaultsLoaded = false;

    // Purge intervals, loaded once on first invocation.
    private long purgeRaw, purge1h, purge6h, purge1d;

    private static final long HOURS_PER_MEAS_TAB = MeasTabManagerUtil.NUMBER_OF_TABLES_PER_DAY;
    private static final String DATA_MANAGER_RETRIES_TIME = ConcurrentStatsCollector.DATA_MANAGER_RETRIES_TIME;

    private final MeasurementDAO measurementDAO;
    private final MeasurementManager measurementManager;
    private final ServerConfigManager serverConfigManager;
    private final AvailabilityManager availabilityManager;
    private final MetricDataCache metricDataCache;
    private final ZeventEnqueuer zeventManager;
    private final MessagePublisher messagePublisher;
    private final RegisteredTriggers registeredTriggers;
    private final ConcurrentStatsCollector concurrentStatsCollector;
    private final int transactionTimeout;
    private final TopNManager topNManager;

    @Autowired
    public DataManagerImpl(DBUtil dbUtil, MeasurementDAO measurementDAO, MeasurementManager measurementManager,
            ServerConfigManager serverConfigManager, AvailabilityManager availabilityManager,
            MetricDataCache metricDataCache, ZeventEnqueuer zeventManager, MessagePublisher messagePublisher,
            RegisteredTriggers registeredTriggers, ConcurrentStatsCollector concurrentStatsCollector,
            HibernateTransactionManager transactionManager, TopNManager topNManager) {
        this.dbUtil = dbUtil;
        this.measurementDAO = measurementDAO;
        this.measurementManager = measurementManager;
        this.serverConfigManager = serverConfigManager;
        this.availabilityManager = availabilityManager;
        this.metricDataCache = metricDataCache;
        this.zeventManager = zeventManager;
        this.messagePublisher = messagePublisher;
        this.registeredTriggers = registeredTriggers;
        this.concurrentStatsCollector = concurrentStatsCollector;
        this.transactionTimeout = transactionManager.getDefaultTimeout();
        this.topNManager = topNManager;
    }

    @PostConstruct
    public void initStatsCollector() {
        concurrentStatsCollector.register(DATA_MANAGER_INSERT_TIME);
        concurrentStatsCollector.register(DATA_MANAGER_RETRIES_TIME);
    }

    private double getValue(ResultSet rs) throws SQLException {
        double val = rs.getDouble("value");

        if (rs.wasNull()) {
            val = Double.NaN;
        }

        return val;
    }

    private void checkTimeArguments(long begin, long end) throws IllegalArgumentException {
        if (begin > end) {
            throw new IllegalArgumentException(ERR_END);
        }

        if (begin < 0) {
            throw new IllegalArgumentException(ERR_START);
        }
    }

    private HighLowMetricValue getMetricValue(ResultSet rs) throws SQLException {
        long timestamp = rs.getLong("timestamp");
        double value = this.getValue(rs);

        if (!Double.isNaN(value)) {
            try {
                double high = rs.getDouble("peak");
                double low = rs.getDouble("low");
                return new HighLowMetricValue(value, high, low, timestamp);
            } catch (SQLException e) {
                // Peak and low columns do not exist
            }
        }
        return new HighLowMetricValue(value, timestamp);
    }

    // Returns the next index to be used
    private int setStatementArguments(PreparedStatement stmt, int start, Integer[] ids) throws SQLException {
        // Set ID's
        int i = start;
        for (Integer id : ids) {
            stmt.setInt(i++, id.intValue());
        }

        return i;
    }

    private void checkTimeArguments(long begin, long end, long interval) throws IllegalArgumentException {

        checkTimeArguments(begin, end);

        if (interval > (end - begin)) {
            throw new IllegalArgumentException(ERR_INTERVAL);
        }
    }

    /**
     * Save the new MetricValue to the database
     * 
     * @param mv the new MetricValue
     * @throws NumberFormatException if the value from the
     *         DataPoint.getMetricValue() cannot instantiate a BigDecimal
     * 
     * 
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addData(Integer mid, MetricValue mv, boolean overwrite) {

        Measurement meas = measurementManager.getMeasurement(mid);
        List<DataPoint> pts = Collections.singletonList(new DataPoint(meas.getId(), mv));

        addData(pts, overwrite);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addData(List<DataPoint> data, String aggTable, Connection conn) throws Exception {
        try {
            insertDataWithOneInsert(data, aggTable, conn);
            conn.commit();
        } catch (Exception e) {
            conn.rollback();
            throw e;
        } finally {
            DBUtil.closeConnection(LOG_CTX, conn);
        }
    }

    private void insertDataWithOneInsert(List<DataPoint> dpts, String table, Connection conn) {
        Statement stmt = null;
        ResultSet rs = null;
        try {
            StringBuilder values = new StringBuilder();
            for (DataPoint pt : dpts) {
                Integer metricId = pt.getMeasurementId();
                HighLowMetricValue metricVal = (HighLowMetricValue) pt.getMetricValue();
                BigDecimal val = new BigDecimal(metricVal.getValue());
                BigDecimal highVal = new BigDecimal(metricVal.getHighValue());
                BigDecimal lowVal = new BigDecimal(metricVal.getLowValue());
                values.append("(").append(metricId.intValue()).append(", ").append(metricVal.getTimestamp())
                        .append(", ").append(getDecimalInRange(val, metricId)).append(", ")
                        .append(getDecimalInRange(lowVal, metricId)).append(", ")
                        .append(getDecimalInRange(highVal, metricId)).append("),");
            }
            String sql = "insert into " + table + " (measurement_id, timestamp, value, minvalue, maxvalue)"
                    + " values " + values.substring(0, values.length() - 1);
            stmt = conn.createStatement();
            stmt.executeUpdate(sql);
        } catch (SQLException e) {
            // If there is a SQLException, then none of the data points
            // should be inserted. Roll back the txn.
            log.error("an unexpected SQL exception has occured inserting datapoints to the " + table + " table:\n",
                    e);
        } finally {
            DBUtil.closeJDBCObjects(LOG_CTX, null, stmt, rs);
        }
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public boolean addData(List<DataPoint> data) {
        return this._addData(data, safeGetConnection());
    }

    public boolean addData(List<DataPoint> data, Connection conn) {
        return this._addData(data, safeGetConnection());
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public boolean addTopData(List<TopNData> topNData) {
        return this._addTopData(topNData, safeGetConnection());
    }

    /**
     * Write metric data points to the DB with transaction
     * 
     * @param data a list of {@link DataPoint}s
     * @throws NumberFormatException if the value from the
     *         DataPoint.getMetricValue() cannot instantiate a BigDecimal
     * 
     * 
     */
    protected boolean _addData(List<DataPoint> data, Connection conn) {
        if (shouldAbortDataInsertion(data)) {
            return true;
        }

        data = enforceUnmodifiable(data);

        log.debug("Attempting to insert data in a single transaction.");

        HQDialect dialect = measurementDAO.getHQDialect();
        boolean succeeded = false;
        final boolean debug = log.isDebugEnabled();

        if (conn == null) {
            return false;
        }

        try {
            boolean autocommit = conn.getAutoCommit();

            try {
                final long start = System.currentTimeMillis();
                conn.setAutoCommit(false);
                if (dialect.supportsMultiInsertStmt()) {
                    succeeded = insertDataWithOneInsert(data, conn);
                } else {
                    succeeded = insertDataInBatch(data, conn);
                }

                if (succeeded) {
                    conn.commit();
                    final long end = System.currentTimeMillis();
                    if (debug) {
                        log.debug("Inserting data in a single transaction " + "succeeded");
                        log.debug("Data Insertion process took " + (end - start) + " ms");
                    }

                    concurrentStatsCollector.addStat(end - start, DATA_MANAGER_INSERT_TIME);
                    sendMetricEvents(data);
                } else {
                    if (debug) {
                        log.debug("Inserting data in a single transaction failed." + "  Rolling back transaction.");
                    }
                    conn.rollback();
                    conn.setAutoCommit(true);
                    List<DataPoint> processed = addDataWithCommits(data, true, conn);
                    final long end = System.currentTimeMillis();

                    concurrentStatsCollector.addStat(end - start, DATA_MANAGER_INSERT_TIME);
                    sendMetricEvents(processed);
                    if (debug) {
                        log.debug("Data Insertion process took " + (end - start) + " ms");
                    }
                }

            } catch (SQLException e) {
                conn.rollback();
                throw e;
            } finally {
                conn.setAutoCommit(autocommit);
            }
        } catch (SQLException e) {
            log.debug("Transaction failed around inserting metric data.", e);
        } finally {
            DBUtil.closeConnection(LOG_CTX, conn);
        }
        return succeeded;
    }

    protected boolean _addTopData(List<TopNData> topNData, Connection conn) {

        if (log.isDebugEnabled()) {
            log.debug("Attempting to insert topN data in a single transaction.");
        }
        boolean succeeded = false;
        final boolean debug = log.isDebugEnabled();

        if (conn == null) {
            return false;
        }

        try {
            boolean autocommit = conn.getAutoCommit();

            try {
                final long start = System.currentTimeMillis();
                conn.setAutoCommit(false);

                succeeded = insertTopData(conn, topNData, false);

                if (succeeded) {
                    conn.commit();
                    final long end = System.currentTimeMillis();
                    if (debug) {
                        log.debug("Inserting TopN data in a single transaction " + "succeeded");
                        log.debug("TopN Data Insertion process took " + (end - start) + " ms");
                    }

                    concurrentStatsCollector.addStat(end - start, DATA_MANAGER_INSERT_TIME);
                } else {
                    if (debug) {
                        log.debug("Inserting TopN data in a single transaction failed."
                                + "  Rolling back transaction" + ".");
                    }
                    conn.rollback();
                }

            } catch (SQLException e) {
                conn.rollback();
                throw e;
            } finally {
                conn.setAutoCommit(autocommit);
            }
        } catch (SQLException e) {
            log.debug("Transaction failed around inserting TopN data.", e);
        } finally {
            DBUtil.closeConnection(LOG_CTX, conn);
        }
        return succeeded;
    }

    /**
     * Write metric datapoints to the DB without transaction
     * 
     * @param data a list of {@link DataPoint}s
     * @param overwrite If true, attempt to over-write values when an insert of
     *        the data fails (i.e. it already exists). You may not want to
     *        over-write values when, for instance, the back filler is inserting
     *        data.
     * @throws NumberFormatException if the value from the
     *         DataPoint.getMetricValue() cannot instantiate a BigDecimal
     * 
     * 
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addData(List<DataPoint> data, boolean overwrite) {
        /**
         * We have to account for 2 types of metric data insertion here: 1 - New
         * data, using 'insert' 2 - Old data, using 'update'
         * 
         * We optimize the amount of DB roundtrips here by executing in batch,
         * however there are some serious gotchas:
         * 
         * 1 - If the 'insert' batch update fails, a BatchUpdateException can be
         * thrown instead of just returning an error within the executeBatch()
         * array. 2 - This is further complicated by the fact that some drivers
         * will throw the exception at the first instance of an error, and some
         * will continue with the rest of the batch.
         */
        if (shouldAbortDataInsertion(data)) {
            return;
        }

        log.debug("Attempting to insert/update data outside a transaction.");

        data = enforceUnmodifiable(data);

        Connection conn = safeGetConnection();

        if (conn == null) {
            log.debug("Inserting/Updating data outside a transaction failed.");
            return;
        }

        try {
            boolean autocommit = conn.getAutoCommit();

            try {
                conn.setAutoCommit(true);
                addDataWithCommits(data, overwrite, conn);
            } finally {
                conn.setAutoCommit(autocommit);
            }
        } catch (SQLException e) {
            log.debug("Inserting/Updating data outside a transaction failed "
                    + "because autocommit management failed.", e);
        } finally {
            DBUtil.closeConnection(LOG_CTX, conn);
        }
    }

    private List<DataPoint> addDataWithCommits(List<DataPoint> data, boolean overwrite, Connection conn) {
        final StopWatch watch = new StopWatch();
        try {
            return _addDataWithCommits(data, overwrite, conn);
        } finally {
            concurrentStatsCollector.addStat(watch.getElapsed(), DATA_MANAGER_RETRIES_TIME);
        }
    }

    private List<DataPoint> _addDataWithCommits(List<DataPoint> data, boolean overwrite, Connection conn) {
        Set<DataPoint> failedToSaveMetrics = new HashSet<DataPoint>();
        List<DataPoint> left = data;
        while (!left.isEmpty()) {
            int numLeft = left.size();
            if (log.isDebugEnabled()) {
                log.debug("Attempting to insert " + numLeft + " points");
            }

            try {
                left = insertData(conn, left, true);
            } catch (SQLException e) {
                assert false : "The SQLException should not happen: " + e;
            }

            if (log.isDebugEnabled()) {
                log.debug("Num left = " + left.size());
            }

            if (left.isEmpty()) {
                break;
            }

            if (!overwrite) {
                if (log.isDebugEnabled()) {
                    log.debug("We are not updating the remaining " + left.size() + " points");
                }
                failedToSaveMetrics.addAll(left);
                break;
            }

            // The insert couldn't insert everything, so attempt to update
            // the things that are left
            if (log.isDebugEnabled()) {
                log.debug("Sending " + left.size() + " data points to update");
            }

            left = updateData(conn, left);

            if (left.isEmpty()) {
                break;
            }

            if (log.isDebugEnabled()) {
                log.debug("Update left " + left.size() + " points to process");
            }

            if (numLeft == left.size()) {
                DataPoint remPt = left.remove(0);
                failedToSaveMetrics.add(remPt);
                // There are some entries that we weren't able to do
                // anything about ... that sucks.
                log.warn("Unable to do anything about " + numLeft + " data points.  Sorry.");
                log.warn("Throwing away data point " + remPt);
            }
        }

        log.debug("Inserting/Updating data outside a transaction finished.");

        return removeMetricsFromList(data, failedToSaveMetrics);
    }

    private boolean shouldAbortDataInsertion(List<?> data) {
        if (data.isEmpty()) {
            log.debug("Aborting data insertion since data list is empty. This is ok.");
            return true;
        } else {
            return false;
        }
    }

    private <T> List<T> enforceUnmodifiable(List<T> aList) {
        return Collections.unmodifiableList(aList);
    }

    private List<DataPoint> removeMetricsFromList(List<DataPoint> data, Set<DataPoint> metricsToRemove) {
        if (metricsToRemove.isEmpty()) {
            return data;
        }

        Set<DataPoint> allMetrics = new HashSet<DataPoint>(data);
        allMetrics.removeAll(metricsToRemove);
        return new ArrayList<DataPoint>(allMetrics);
    }

    /**
     * Convert a decimal value to something suitable for being thrown into the
     * database with a NUMERIC(24,5) definition
     */
    private BigDecimal getDecimalInRange(BigDecimal val, Integer metricId) {
        val = val.setScale(5, BigDecimal.ROUND_HALF_EVEN);
        if (val.compareTo(MAX_DB_NUMBER) == 1) {
            log.warn("Value [" + val + "] for metric id=" + metricId
                    + "is too big to put into the DB.  Truncating to [" + MAX_DB_NUMBER + "]");
            return MAX_DB_NUMBER;
        }

        return val;
    }

    private void sendMetricEvents(List<DataPoint> data) {
        if (data.isEmpty()) {
            return;
        }

        // Finally, for all the data which we put into the system, make sure
        // we update our internal cache, kick off the events, etc.
        final boolean debug = log.isDebugEnabled();
        final StopWatch watch = new StopWatch();
        if (debug) {
            watch.markTimeBegin("analyzeMetricData");
        }
        analyzeMetricData(data);
        if (debug) {
            watch.markTimeEnd("analyzeMetricData");
        }

        Collection<DataPoint> cachedData = updateMetricDataCache(data);
        sendDataToEventHandlers(cachedData);
        if (debug) {
            log.debug(watch);
        }
    }

    private void analyzeMetricData(List<DataPoint> data) {
        Analyzer analyzer = getAnalyzer();
        if (analyzer != null) {
            for (DataPoint dp : data) {
                analyzer.analyzeMetricValue(dp.getMeasurementId(), dp.getMetricValue());
            }
        }
    }

    private Collection<DataPoint> updateMetricDataCache(List<DataPoint> data) {

        return metricDataCache.bulkAdd(data);
    }

    private void sendDataToEventHandlers(Collection<DataPoint> data) {
        ArrayList<MeasurementEvent> events = new ArrayList<MeasurementEvent>();
        List<MeasurementZevent> zevents = new ArrayList<MeasurementZevent>();

        boolean allEventsInteresting = Boolean.getBoolean(ALL_EVENTS_INTERESTING_PROP);

        for (DataPoint dp : data) {

            Integer metricId = dp.getMeasurementId();
            MetricValue val = dp.getMetricValue();
            MeasurementEvent event = new MeasurementEvent(metricId, val);

            if (registeredTriggers.isTriggerInterested(event) || allEventsInteresting) {
                measurementManager.buildMeasurementEvent(event);
                events.add(event);
            }

            zevents.add(new MeasurementZevent(metricId.intValue(), val));
        }

        if (!events.isEmpty()) {
            messagePublisher.publishMessage(EventConstants.EVENTS_TOPIC, events);
        }

        if (!zevents.isEmpty()) {
            try {
                // XXX: Shouldn't this be a transactional queueing?
                zeventManager.enqueueEvents(zevents);
            } catch (InterruptedException e) {
                log.warn("Interrupted while sending events.  Some data may " + "be lost");
            }
        }
    }

    private List<DataPoint> getRemainingDataPoints(List<DataPoint> data, int[] execInfo) {
        List<DataPoint> res = new ArrayList<DataPoint>();
        int idx = 0;

        // this is the case for mysql
        if (execInfo.length == 0) {
            return res;
        }

        for (Iterator<DataPoint> i = data.iterator(); i.hasNext(); idx++) {
            DataPoint pt = i.next();

            if (execInfo[idx] == Statement.EXECUTE_FAILED) {
                res.add(pt);
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("Need to deal with " + res.size() + " unhandled " + "data points (out of " + execInfo.length
                    + ")");
        }
        return res;
    }

    private List<DataPoint> getRemainingDataPointsAfterBatchFail(List<DataPoint> data, int[] counts) {
        List<DataPoint> res = new ArrayList<DataPoint>();
        Iterator<DataPoint> i = data.iterator();
        int idx;

        for (idx = 0; idx < counts.length; idx++) {
            DataPoint pt = i.next();

            if (counts[idx] == Statement.EXECUTE_FAILED) {
                res.add(pt);
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("Need to deal with " + res.size() + " unhandled " + "data points (out of " + counts.length
                    + ").  " + "datasize=" + data.size());
        }

        // It's also possible that counts[] is not as long as the list
        // of data points, so we have to return all the un-processed points
        if (data.size() != counts.length) {
            res.addAll(data.subList(idx, data.size()));
        }
        return res;
    }

    /**
     * Insert the metric data points to the DB with one insert statement. This
     * should only be invoked when the DB supports multi-insert statements.
     * 
     * @param data a list of {@link DataPoint}s
     * @return <code>true</code> if the multi-insert succeeded;
     *         <code>false</code> otherwise.
     */
    private boolean insertDataWithOneInsert(List<DataPoint> data, Connection conn) {
        Statement stmt = null;
        final Map<String, Set<DataPoint>> buckets = MeasRangeObj.getInstance().bucketDataEliminateDups(data);
        final boolean debug = log.isDebugEnabled();
        String sql = "";
        final HQDialect dialect = measurementDAO.getHQDialect();
        final boolean supportsAsyncCommit = dialect.supportsAsyncCommit();
        try {
            for (Entry<String, Set<DataPoint>> entry : buckets.entrySet()) {
                final String table = entry.getKey();
                final Set<DataPoint> dpts = entry.getValue();
                final int dptsSize = dpts.size();
                final StringBuilder values = new StringBuilder(dptsSize * 15);
                int rows = 0;
                for (final DataPoint pt : dpts) {
                    final Integer metricId = pt.getMeasurementId();
                    final MetricValue val = pt.getMetricValue();
                    final BigDecimal bigDec = new BigDecimal(val.getValue());
                    rows++;
                    values.append("(").append(val.getTimestamp()).append(",").append(metricId.intValue())
                            .append(",").append(getDecimalInRange(bigDec, metricId)).append(")");
                    if (rows < dptsSize) {
                        values.append(",");
                    }
                }
                sql = "insert into " + table + " (timestamp, measurement_id, value) values " + values;
                stmt = conn.createStatement();
                if (supportsAsyncCommit) {
                    stmt.execute(dialect.getSetAsyncCommitStmt(false));
                }
                final int rowsUpdated = stmt.executeUpdate(sql);
                if (supportsAsyncCommit) {
                    stmt.execute(dialect.getSetAsyncCommitStmt(true));
                }
                stmt.close();
                stmt = null;
                if (debug) {
                    log.debug("Inserted " + rowsUpdated + " rows into " + table + " (attempted " + rows + " rows)");
                }
                if (rowsUpdated < rows) {
                    return false;
                }
            }
        } catch (SQLException e) {
            // If there is a SQLException, then none of the data points
            // should be inserted. Roll back the txn.
            if (debug) {
                log.debug("Error inserting data with one insert stmt: " + e
                        + ".  Server will retry the insert in degraded mode.  sql=\n" + sql);
            }
            return false;
        } finally {
            DBUtil.closeStatement(LOG_CTX, stmt);
        }
        return true;
    }

    /**
     * Insert the metric data points to the DB in batch.
     * 
     * @param data a list of {@link DataPoint}s
     * @return <code>true</code> if the batch insert succeeded;
     *         <code>false</code> otherwise.
     */
    private boolean insertDataInBatch(List<DataPoint> data, Connection conn) {
        List<DataPoint> left = data;

        try {
            if (log.isDebugEnabled()) {
                log.debug("Attempting to insert " + left.size() + " points");
            }
            left = insertData(conn, left, false);
            if (log.isDebugEnabled()) {
                log.debug("Num left = " + left.size());
            }

            if (!left.isEmpty()) {
                if (log.isDebugEnabled()) {
                    log.debug("Need to update " + left.size() + " data points.");
                    log.debug("Data points to update: " + left);
                }

                return false;
            }
        } catch (SQLException e) {
            // If there is a SQLException, then none of the data points
            // should be inserted. Roll back the txn.
            if (log.isDebugEnabled()) {
                log.debug("Error while inserting data in batch (this is ok) " + e.getMessage());
            }
            return false;
        }

        return true;
    }

    /**
     * Retrieve a DB connection.
     * 
     * @return The connection or <code>null</code>.
     */
    private Connection safeGetConnection() {
        Connection conn = null;

        try {
            // XXX: may want to explore grabbing a connection directly from the transactionManager
            conn = dbUtil.getConnection();
        } catch (SQLException e) {
            log.error("Failed to retrieve connection from data source", e);
        }

        return conn;
    }

    /**
     * This method inserts data into the data table. If any data points in the
     * list fail to get added (e.g. because of a constraint violation), it will
     * be returned in the result list.
     * 
     * @param conn The connection.
     * @param data The data points to insert.
     * @param continueOnSQLException <code>true</code> to continue inserting the
     *        rest of the data points even after a <code>SQLException</code>
     *        occurs; <code>false</code> to throw the <code>SQLException</code>.
     * @return The list of data points that were not inserted.
     * @throws SQLException only if there is an exception for one of the data
     *         point batch inserts and <code>continueOnSQLException</code> is
     *         set to <code>false</code>.
     */
    private List<DataPoint> insertData(Connection conn, List<DataPoint> data, boolean continueOnSQLException)
            throws SQLException {
        PreparedStatement stmt = null;
        final List<DataPoint> left = new ArrayList<DataPoint>();
        final Map<String, List<DataPoint>> buckets = MeasRangeObj.getInstance().bucketData(data);
        final HQDialect dialect = measurementDAO.getHQDialect();
        final boolean supportsDupInsStmt = dialect.supportsDuplicateInsertStmt();
        final boolean supportsPLSQL = dialect.supportsPLSQL();
        final StringBuilder buf = new StringBuilder();
        for (final Entry<String, List<DataPoint>> entry : buckets.entrySet()) {
            buf.setLength(0);
            final String table = entry.getKey();
            final List<DataPoint> dpts = entry.getValue();
            try {
                if (supportsDupInsStmt) {
                    stmt = conn.prepareStatement(buf.append("INSERT INTO ").append(table)
                            .append(" (measurement_id, timestamp, value) VALUES (?, ?, ?)")
                            .append(" ON DUPLICATE KEY UPDATE value = ?").toString());
                } else if (supportsPLSQL) {
                    final String sql = PLSQL.replaceAll(":table", table);
                    stmt = conn.prepareStatement(sql);
                } else {
                    stmt = conn.prepareStatement(buf.append("INSERT INTO ").append(table)
                            .append(" (measurement_id, timestamp, value) VALUES (?, ?, ?)").toString());
                }
                // TODO need to set synchronous commit to off
                for (DataPoint pt : dpts) {
                    Integer metricId = pt.getMeasurementId();
                    MetricValue val = pt.getMetricValue();
                    BigDecimal bigDec;
                    bigDec = new BigDecimal(val.getValue());
                    stmt.setInt(1, metricId.intValue());
                    stmt.setLong(2, val.getTimestamp());
                    stmt.setBigDecimal(3, getDecimalInRange(bigDec, metricId));
                    if (supportsDupInsStmt) {
                        stmt.setBigDecimal(4, getDecimalInRange(bigDec, metricId));
                    } else if (supportsPLSQL) {
                        stmt.setBigDecimal(4, getDecimalInRange(bigDec, metricId));
                        stmt.setLong(5, val.getTimestamp());
                        stmt.setInt(6, metricId.intValue());
                    }
                    stmt.addBatch();
                }
                int[] execInfo = stmt.executeBatch();
                left.addAll(getRemainingDataPoints(dpts, execInfo));
            } catch (BatchUpdateException e) {
                if (!continueOnSQLException) {
                    throw e;
                }
                left.addAll(getRemainingDataPointsAfterBatchFail(dpts, e.getUpdateCounts()));
            } catch (SQLException e) {
                if (!continueOnSQLException) {
                    throw e;
                }
                // If the batch insert is not within a transaction, then we
                // don't know which of the inserts completed successfully.
                // Assume they all failed.
                left.addAll(dpts);
                if (log.isDebugEnabled()) {
                    log.debug("A general SQLException occurred during the insert. " + "Assuming that none of the "
                            + dpts.size() + " data points were inserted.", e);
                }
            } finally {
                DBUtil.closeStatement(LOG_CTX, stmt);
            }
        }
        return left;
    }

    public int purgeTopNData(Date timeToKeep) {
        PreparedStatement ps = null;
        int topNDeleted = -1;
        Connection conn = safeGetConnection();
        try {
            ps = conn.prepareStatement("select drop_old_partitions(?, ?);");
            ps.setString(1, TopNData.class.getSimpleName());
            ps.setTimestamp(2, new Timestamp(timeToKeep.getTime()));
            ResultSet rs = ps.executeQuery();
            rs.next();
            topNDeleted = rs.getInt(1);
        } catch (SQLException e) {
            log.error("Problem purging old TopN Data", e);
        } finally {
            DBUtil.closeStatement(LOG_CTX, ps);
        }
        return topNDeleted;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.hyperic.hq.measurement.shared.DataManager#getTopNData(int, long)
     */
    public TopNData getTopNData(int resourceId, long time) {
        Statement stmt = null;
        Connection conn = safeGetConnection();
        StringBuilder builder = new StringBuilder();
        try {
            builder.append("SELECT data FROM TOPNDATA TOP WHERE TOP.resourceid='").append(resourceId)
                    .append("' AND TOP.time = '").append(new java.sql.Timestamp(time)).append("'");

            stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(builder.toString());
            while (rs.next()) {
                TopNData data = new TopNData();
                data.setData(rs.getBytes("data"));
                data.setResourceId(resourceId);
                data.setTime(new Date(time));
                return data;
            }

        } catch (SQLException e) {
            log.error("Problem fetching TOP data", e);
        } finally {
            DBUtil.closeStatement(LOG_CTX, stmt);
        }
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.hyperic.hq.measurement.shared.DataManager#getTopNDataAsString(int,
     * long)
     */
    @Transactional(readOnly = true)
    public TopReport getTopReport(int resourceId, long time) {
        TopNData data = getTopNData(resourceId, time);
        if (data == null) {
            return null;
        }
        try {
            byte[] unCompressedData = topNManager.uncompressData(data.getData());
            return TopReport.fromSerializedForm(unCompressedData);
        } catch (Exception e) {
            log.error("Error un serializing TopN data", e);
        }
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.hyperic.hq.measurement.shared.DataManager#getLatestAvailableTopDataTimes
     * (int, int)
     */
    @Transactional(readOnly = true)
    public List<Long> getLatestAvailableTopDataTimes(int resourceId, int count) {
        List<Long> times = new ArrayList<Long>();
        Statement stmt = null;
        Connection conn = safeGetConnection();
        StringBuilder builder = new StringBuilder();
        try {
            builder.append("SELECT TIME FROM TOPNDATA TOP WHERE TOP.resourceid='").append(resourceId)
                    .append("' ORDER BY time DESC LIMIT ").append(count);

            stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(builder.toString());
            while (rs.next()) {
                times.add(rs.getTimestamp("time").getTime());
            }

        } catch (SQLException e) {
            log.error("Problem fetching TOP data", e);
            return times;
        } finally {
            DBUtil.closeStatement(LOG_CTX, stmt);
        }
        return times;

    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.hyperic.hq.measurement.shared.DataManager#getAvailableTopDataTimes
     * (int, long, long)
     */
    @Transactional(readOnly = true)
    public List<Long> getAvailableTopDataTimes(int resourceId, long from, long to) {
        List<Long> times = new ArrayList<Long>();
        Statement stmt = null;
        Connection conn = safeGetConnection();
        StringBuilder builder = new StringBuilder();
        try {
            builder.append("SELECT TIME FROM TOPNDATA TOP WHERE TOP.resourceid='").append(resourceId)
                    .append("' AND TOP.time BETWEEN '").append(new java.sql.Timestamp(from)).append("' AND '")
                    .append(new java.sql.Timestamp(to)).append("'");

            stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(builder.toString());
            while (rs.next()) {
                times.add(rs.getTimestamp("time").getTime());
            }

        } catch (SQLException e) {
            log.error("Problem fetching TOP data", e);
            return times;
        } finally {
            DBUtil.closeStatement(LOG_CTX, stmt);
        }
        return times;

    }

    private boolean insertTopData(Connection conn, List<TopNData> topNData, boolean continueOnSQLException)
            throws SQLException {
        if (log.isDebugEnabled()) {
            log.debug("Inserting Top Processes data - '" + topNData + "'");
        }
        PreparedStatement stmt = null;
        final StringBuilder buf = new StringBuilder();

        Map<Date, List<TopNData>> dayToDataMap = mapDayToData(topNData);

        for (Entry<Date, List<TopNData>> entry : dayToDataMap.entrySet()) {
            try {

                String partitionName = getAndCreatePartition(TopNData.class.getSimpleName(), entry.getKey(), conn);
                stmt = conn.prepareStatement(buf.append("INSERT INTO ").append(partitionName)
                        .append(" (resourceId, time, data) VALUES (?, ?, ?)").toString());

                for (TopNData data : entry.getValue()) {
                    stmt.setInt(1, data.getResourceId());
                    stmt.setTimestamp(2, new java.sql.Timestamp(data.getTime().getTime()));
                    stmt.setBytes(3, data.getData());
                    stmt.addBatch();
                }
                int[] execInfo = stmt.executeBatch();

            } catch (BatchUpdateException e) {
                if (!continueOnSQLException) {
                    throw e;
                }
            } catch (SQLException e) {
                if (!continueOnSQLException) {
                    throw e;
                }
                if (log.isDebugEnabled()) {
                    log.debug("A general SQLException occurred during the insert of TopN. ", e);
                }

            } finally {
                DBUtil.closeStatement(LOG_CTX, stmt);
            }

        }
        return true;
    }

    private Map<Date, List<TopNData>> mapDayToData(List<TopNData> topNData) {
        Map<Date, List<TopNData>> dayToDataMap = new HashMap<Date, List<TopNData>>();
        for (TopNData data : topNData) {
            Date day = DateUtils.truncate(data.getTime(), Calendar.DAY_OF_MONTH);
            List<TopNData> datas = dayToDataMap.get(day);
            if (datas == null) {
                dayToDataMap.put(day, new LinkedList<TopNData>());
            }
            dayToDataMap.get(day).add(data);
        }
        return dayToDataMap;
    }

    private String getAndCreatePartition(final String tableName, final Date time, final Connection conn)
            throws SQLException {
        PreparedStatement ps = null;
        try {
            ps = conn.prepareStatement("select get_and_create_partition(?,?);");
            ps.setString(1, tableName);
            ps.setTimestamp(2, new Timestamp(time.getTime()));
            final ResultSet rs = ps.executeQuery();
            rs.next();
            return rs.getString(1);
        } finally {
            DBUtil.closeStatement(LOG_CTX, ps);
        }
    }

    /**
     * This method is called to perform 'updates' for any inserts that failed.
     * 
     * @return The data insert result containing the data points that were not
     *         updated.
     */
    private List<DataPoint> updateData(Connection conn, List<DataPoint> data) {
        PreparedStatement stmt = null;
        List<DataPoint> left = new ArrayList<DataPoint>();
        Map<String, List<DataPoint>> buckets = MeasRangeObj.getInstance().bucketData(data);

        for (Entry<String, List<DataPoint>> entry : buckets.entrySet()) {
            String table = entry.getKey();
            List<DataPoint> dpts = entry.getValue();

            try {
                // TODO need to set synchronous commit to off
                stmt = conn.prepareStatement(
                        "UPDATE " + table + " SET value = ? WHERE timestamp = ? AND measurement_id = ?");
                for (DataPoint pt : dpts) {
                    Integer metricId = pt.getMeasurementId();
                    MetricValue val = pt.getMetricValue();
                    BigDecimal bigDec;
                    bigDec = new BigDecimal(val.getValue());
                    stmt.setBigDecimal(1, getDecimalInRange(bigDec, metricId));
                    stmt.setLong(2, val.getTimestamp());
                    stmt.setInt(3, metricId.intValue());
                    stmt.addBatch();
                }

                int[] execInfo = stmt.executeBatch();
                left.addAll(getRemainingDataPoints(dpts, execInfo));
            } catch (BatchUpdateException e) {
                left.addAll(getRemainingDataPointsAfterBatchFail(dpts, e.getUpdateCounts()));
            } catch (SQLException e) {
                // If the batch update is not within a transaction, then we
                // don't know which of the updates completed successfully.
                // Assume they all failed.
                left.addAll(dpts);

                if (log.isDebugEnabled()) {
                    log.debug("A general SQLException occurred during the update. " + "Assuming that none of the "
                            + dpts.size() + " data points were updated.", e);
                }
            } finally {
                DBUtil.closeStatement(LOG_CTX, stmt);
            }
        }
        return left;
    }

    /**
     * Get the server purge configuration and storage option, loaded on startup.
     */
    private void loadConfigDefaults() {

        log.debug("Loading default purge intervals");
        Properties conf;
        try {
            conf = serverConfigManager.getConfig();
        } catch (Exception e) {
            throw new SystemException(e);
        }

        String purgeRawString = conf.getProperty(HQConstants.DataPurgeRaw);
        String purge1hString = conf.getProperty(HQConstants.DataPurge1Hour);
        String purge6hString = conf.getProperty(HQConstants.DataPurge6Hour);
        String purge1dString = conf.getProperty(HQConstants.DataPurge1Day);

        try {
            purgeRaw = Long.parseLong(purgeRawString);
            purge1h = Long.parseLong(purge1hString);
            purge6h = Long.parseLong(purge6hString);
            purge1d = Long.parseLong(purge1dString);

            confDefaultsLoaded = true;
        } catch (NumberFormatException e) {
            // Shouldn't happen unless manual edit of config table
            throw new IllegalArgumentException("Invalid purge interval: " + e);
        }
    }

    /**
     *  find the aggregation table with the greatest interval which is smaller than the requested time frame
     *  and which the time frame doesn't cover a range which is even partially after its purge has been done
     *  and in which no more than maxDTPs fit in the requested time frame
     * @throws TimeframeSizeException 
     * @throws TimeframeBoundriesException 
     */
    protected String getDataTable(long begin, long end, Measurement msmt, int maxDTPs)
            throws TimeframeSizeException, TimeframeBoundriesException {
        long tf = end - begin; // the time frame
        long maxInterval = tf / maxDTPs; // the max interval for which maxDTPs DTPs still fit in the time frame
        long msmtInterval = msmt.getInterval();
        if (tf < msmtInterval) {
            MeasurementTemplate tmp = msmt.getTemplate();
            throw new TimeframeSizeException("The requested time frame is of size " + tf
                    + " milliseconds, which is smaller than the time interval of the measurement"
                    + (tmp != null ? (" " + tmp.getName()) : "") + " which is " + msmtInterval + " milliseconds");
        }
        long now = System.currentTimeMillis();
        if ((end > now) || (end < begin)) {
            throw new TimeframeBoundriesException(
                    "The requested time frame" + (end > now ? " ends in the future" : (end < begin ? " and" : ""))
                            + (end < begin ? " ends before it starts" : ""));
        }
        if (!confDefaultsLoaded) {
            loadConfigDefaults();
        }
        if ((msmtInterval >= maxInterval) && (begin >= (now - DataManagerImpl.this.purgeRaw))) {
            return MeasurementUnionStatementBuilder.getUnionStatement(begin, end, msmt.getId().intValue(),
                    measurementDAO.getHQDialect());
        }
        if (tf < HOUR_IN_MILLI) {
            MeasurementTemplate tmp = msmt.getTemplate();
            throw new TimeframeSizeException("The requested time frame is of size " + tf
                    + " milliseconds, which is smaller than the hourly aggregated time interval of the measurement "
                    + (tmp != null ? tmp.getName() : ""));
        }
        if ((HOUR_IN_MILLI >= maxInterval) && (begin >= (now - DataManagerImpl.this.purge1h))) {
            return MeasurementConstants.TAB_DATA_1H;
        }
        if (tf < SIX_HOURS_IN_MILLI) {
            MeasurementTemplate tmp = msmt.getTemplate();
            throw new TimeframeSizeException("The requested time frame is of size " + tf
                    + " milliseconds, which is smaller than the 6-hourly aggregated time interval of the measurement "
                    + (tmp != null ? tmp.getName() : ""));
        }
        if ((SIX_HOURS_IN_MILLI >= maxInterval) && (begin >= (now - DataManagerImpl.this.purge6h))) {
            return MeasurementConstants.TAB_DATA_6H;
        }
        if (tf < DAY_IN_MILLI) {
            MeasurementTemplate tmp = msmt.getTemplate();
            throw new TimeframeSizeException("The requested time frame is of size " + tf
                    + " milliseconds, which is smaller than the daily aggregated time interval of the measurement "
                    + (tmp != null ? tmp.getName() : ""));
        }
        // return daily aggregated data even if the time frame is beyond the purge time or contains more than 400 DTPs
        return MeasurementConstants.TAB_DATA_1D;
    }

    public String getDataTable(long begin, long end, int measId) {
        Integer[] empty = new Integer[1];
        empty[0] = new Integer(measId);
        return getDataTable(begin, end, empty);
    }

    private boolean usesMetricUnion(long begin) {
        long now = System.currentTimeMillis();
        if (MeasTabManagerUtil.getMeasTabStartTime(now - getPurgeRaw()) < begin) {
            return true;
        }
        return false;
    }

    private boolean usesMetricUnion(long begin, long end, boolean useAggressiveRollup) {
        if ((!useAggressiveRollup && usesMetricUnion(begin))
                || (useAggressiveRollup && (((end - begin) / HOUR) < HOURS_PER_MEAS_TAB))) {
            return true;
        }
        return false;
    }

    private String getDataTable(long begin, long end, Integer[] measIds) {
        return getDataTable(begin, end, measIds, false);
    }

    /**
     * @param begin beginning of the time range
     * @param end end of the time range
     * @param useAggressiveRollup will use the rollup tables if
     *      the timerange represents the same timerange as one
     *      metric data table
     */
    private String[] getDataTables(long begin, long end, boolean useAggressiveRollup) {
        long now = System.currentTimeMillis();
        if (!confDefaultsLoaded) {
            loadConfigDefaults();
        }
        if (usesMetricUnion(begin, end, useAggressiveRollup)) {
            return MeasTabManagerUtil.getMetricTables(begin, end);
        } else if ((now - this.purge1h) < begin) {
            return new String[] { TAB_DATA_1H };
        } else if ((now - this.purge6h) < begin) {
            return new String[] { TAB_DATA_6H };
        } else {
            return new String[] { TAB_DATA_1D };
        }
    }

    /**
     * @param begin beginning of the time range
     * @param end end of the time range
     * @param measIds the measurement_ids associated with the query. This is
     *        only used for 'UNION ALL' queries
     * @param useAggressiveRollup will use the rollup tables if the timerange
     *        represents the same timerange as one metric data table
     */
    private String getDataTable(long begin, long end, Integer[] measIds, boolean useAggressiveRollup) {
        long now = System.currentTimeMillis();

        if (!confDefaultsLoaded) {
            loadConfigDefaults();
        }

        if (usesMetricUnion(begin, end, useAggressiveRollup)) {
            return MeasurementUnionStatementBuilder.getUnionStatement(begin, end, measIds,
                    measurementDAO.getHQDialect());
        } else if ((now - this.purge1h) < begin) {
            return TAB_DATA_1H;
        } else if ((now - this.purge6h) < begin) {
            return TAB_DATA_6H;
        } else {
            return TAB_DATA_1D;
        }
    }

    @Transactional(readOnly = true)
    public List<HighLowMetricValue> getHistoricalData(Measurement m, long begin, long end,
            boolean prependAvailUnknowns, int maxDTPs) throws TimeframeSizeException, TimeframeBoundriesException {
        if (m.getTemplate().isAvailability()) {
            return availabilityManager.getHistoricalAvailData(m, begin, end, PageControl.PAGE_ALL,
                    prependAvailUnknowns);
        } else {
            return getNonAvailabilityMetricData(m, begin, end, maxDTPs);
        }
    }

    /**
     * Fetch the list of historical data points given a begin and end time
     * range. Returns a PageList of DataPoints without begin rolled into time
     * windows.
     * 
     * @param m The Measurement
     * @param begin the start of the time range
     * @param end the end of the time range
     * @param prependAvailUnknowns determines whether to prepend AVAIL_UNKNOWN if the
     *        corresponding time window is not accounted for in the database.
     *        Since availability is contiguous this will not occur unless the
     *        time range precedes the first availability point.
     * @return the list of data points
     * 
     */
    @Transactional(readOnly = true)
    public PageList<HighLowMetricValue> getHistoricalData(Measurement m, long begin, long end, PageControl pc,
            boolean prependAvailUnknowns) {
        if (m.getTemplate().isAvailability()) {
            return availabilityManager.getHistoricalAvailData(m, begin, end, pc, prependAvailUnknowns);
        } else {
            return getHistData(m, begin, end, pc);
        }
    }

    /**
     * Fetch the list of historical data points given a begin and end time
     * range. Returns a PageList of DataPoints without begin rolled into time
     * windows.
     * 
     * @param m The Measurement
     * @param begin the start of the time range
     * @param end the end of the time range
     * @return the list of data points
     * 
     */
    @Transactional(readOnly = true)
    public PageList<HighLowMetricValue> getHistoricalData(Measurement m, long begin, long end, PageControl pc) {
        return getHistoricalData(m, begin, end, pc, false);
    }

    private PageList<HighLowMetricValue> getHistData(final Measurement m, long begin, long end,
            final PageControl pc) {
        checkTimeArguments(begin, end);
        begin = TimingVoodoo.roundDownTime(begin, MINUTE);
        end = TimingVoodoo.roundDownTime(end, MINUTE);
        final ArrayList<HighLowMetricValue> history = new ArrayList<HighLowMetricValue>();
        // Get the data points and add to the ArrayList
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        // The table to query from
        final String table = getDataTable(begin, end, m.getId().intValue());
        final HQDialect dialect = measurementDAO.getHQDialect();
        try {
            conn = dbUtil.getConnection();
            stmt = conn.createStatement();
            try {
                final boolean sizeLimit = (pc.getPagesize() != PageControl.SIZE_UNLIMITED);
                final StringBuilder sqlBuf = new StringBuilder().append("SELECT :fields FROM ").append(table)
                        .append(" WHERE timestamp BETWEEN ").append(begin).append(" AND ").append(end)
                        .append(" AND measurement_id=").append(m.getId()).append(" ORDER BY timestamp ")
                        .append(pc.isAscending() ? "ASC" : "DESC");
                Integer total = null;
                if (sizeLimit) {
                    // need to get the total count if there is a limit on the
                    // size. Otherwise we can just take the size of the
                    // resultset
                    rs = stmt.executeQuery(sqlBuf.toString().replace(":fields", "count(*)"));
                    total = (rs.next()) ? new Integer(rs.getInt(1)) : new Integer(1);
                    rs.close();
                    if (total.intValue() == 0) {
                        return new PageList<HighLowMetricValue>();
                    }
                    if (log.isDebugEnabled()) {
                        log.debug("paging: offset= " + pc.getPageEntityIndex() + ", pagesize=" + pc.getPagesize());
                    }
                }
                final int offset = pc.getPageEntityIndex();
                final int limit = pc.getPagesize();
                final String sql = (sizeLimit) ? dialect.getLimitBuf(sqlBuf.toString(), offset, limit)
                        : sqlBuf.toString();
                if (log.isDebugEnabled()) {
                    log.debug(sql);
                }
                final StopWatch timer = new StopWatch();
                rs = stmt.executeQuery(sql.replace(":fields", "value, timestamp"));
                if (log.isTraceEnabled()) {
                    log.trace("getHistoricalData() execute time: " + timer.getElapsed());
                }
                while (rs.next()) {
                    history.add(getMetricValue(rs));
                }
                total = (total == null) ? new Integer(history.size()) : total;
                return new PageList<HighLowMetricValue>(history, total.intValue());
            } catch (SQLException e) {
                throw new SystemException("Can't lookup historical data for " + m, e);
            }
        } catch (SQLException e) {
            throw new SystemException("Can't open connection", e);
        } finally {
            DBUtil.closeJDBCObjects(LOG_CTX, conn, stmt, rs);
        }
    }

    private List<HighLowMetricValue> getNonAvailabilityMetricData(final Measurement m, long begin, long end,
            int maxDTPs) throws TimeframeSizeException, TimeframeBoundriesException {
        checkTimeArguments(begin, end);
        begin = TimingVoodoo.roundDownTime(begin, MINUTE);
        end = TimingVoodoo.roundDownTime(end, MINUTE);
        final ArrayList<HighLowMetricValue> history = new ArrayList<HighLowMetricValue>();
        // Get the data points and add to the ArrayList
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        // The table to query from
        final String table = getDataTable(begin, end, m, maxDTPs);
        try {
            conn = dbUtil.getConnection();
            stmt = conn.createStatement();
            try {
                final StringBuilder sqlBuf = new StringBuilder().append("SELECT ")
                        .append((table.equals(TAB_DATA_1H) || table.equals(TAB_DATA_6H)
                                || table.equals(TAB_DATA_1D)) ? ("maxvalue as peak, minvalue as low, ") : "")
                        .append("value, timestamp FROM ").append(table).append(" WHERE timestamp BETWEEN ")
                        .append(begin).append(" AND ").append(end).append(" AND measurement_id=").append(m.getId())
                        .append(" ORDER BY timestamp ").append("ASC");
                final String sql = sqlBuf.toString();
                if (log.isDebugEnabled()) {
                    log.debug(sql);
                }
                rs = stmt.executeQuery(sql);
                while (rs.next()) {
                    history.add(getMetricValue(rs));
                }
                return new ArrayList<HighLowMetricValue>(history);
            } catch (SQLException e) {
                throw new SystemException("Can't lookup historical data for " + m, e);
            }
        } catch (SQLException e) {
            throw new SystemException("Can't open connection", e);
        } finally {
            DBUtil.closeJDBCObjects(LOG_CTX, conn, stmt, rs);
        }
    }

    public Collection<HighLowMetricValue> getRawData(Measurement m, long begin, long end,
            AtomicLong publishedInterval) {
        final long interval = m.getInterval();
        begin = TimingVoodoo.roundDownTime(begin, interval);
        end = TimingVoodoo.roundDownTime(end, interval);
        Collection<HighLowMetricValue> points;
        if (m.getTemplate().isAvailability()) {
            points = availabilityManager.getHistoricalAvailData(new Integer[] { m.getId() }, begin, end, interval,
                    PageControl.PAGE_ALL, true);
            publishedInterval.set(interval);
        } else {
            points = getRawDataPoints(m, begin, end, publishedInterval);
        }
        return points;
    }

    private TreeSet<HighLowMetricValue> getRawDataPoints(Measurement m, long begin, long end,
            AtomicLong publishedInterval) {
        final StringBuilder sqlBuf = getRawDataSql(m, begin, end, publishedInterval);
        final TreeSet<HighLowMetricValue> rtn = new TreeSet<HighLowMetricValue>(getTimestampComparator());
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            conn = safeGetConnection();
            stmt = conn.createStatement();
            rs = stmt.executeQuery(sqlBuf.toString());
            final int valCol = rs.findColumn("value");
            final int timestampCol = rs.findColumn("timestamp");
            while (rs.next()) {
                final double val = rs.getDouble(valCol);
                final long timestamp = rs.getLong(timestampCol);
                rtn.add(new HighLowMetricValue(val, timestamp));
            }
        } catch (SQLException e) {
            throw new SystemException(e);
        } finally {
            DBUtil.closeJDBCObjects(LOG_CTX, conn, stmt, rs);
        }
        return rtn;
    }

    private StringBuilder getRawDataSql(Measurement m, long begin, long end, AtomicLong publishedInterval) {
        final String sql = new StringBuilder(128).append("SELECT value, timestamp FROM :table")
                .append(" WHERE timestamp BETWEEN ").append(begin).append(" AND ").append(end)
                .append(" AND measurement_id=").append(m.getId()).toString();
        final String[] tables = getDataTables(begin, end, false);
        if (tables.length == 1) {
            if (tables[0].equals(TAB_DATA_1H)) {
                publishedInterval.set(HOUR);
            } else if (tables[0].equals(TAB_DATA_6H)) {
                publishedInterval.set(HOUR * 6);
            } else if (tables[0].equals(TAB_DATA_1D)) {
                publishedInterval.set(HOUR * 24);
            }
        }
        final StringBuilder sqlBuf = new StringBuilder(128 * tables.length);
        for (int i = 0; i < tables.length; i++) {
            sqlBuf.append(sql.replace(":table", tables[i]));
            if (i < (tables.length - 1)) {
                sqlBuf.append(" UNION ALL ");
            }
        }
        return sqlBuf;
    }

    private Comparator<MetricValue> getTimestampComparator() {
        return new Comparator<MetricValue>() {
            public int compare(MetricValue arg0, MetricValue arg1) {
                Long point0 = arg0.getTimestamp();
                Long point1 = arg1.getTimestamp();
                return point0.compareTo(point1);
            }
        };
    }

    /**
     * Aggregate data across the given metric IDs, returning max, min, avg, and
     * count of number of unique metric IDs
     * 
     * @param measurements {@link List} of {@link Measurement}s
     * @param begin The start of the time range
     * @param end The end of the time range
     * @return the An array of aggregate values
     * 
     */
    @Transactional(readOnly = true)
    public double[] getAggregateData(final List<Measurement> measurements, final long begin, final long end) {
        checkTimeArguments(begin, end);
        long interval = end - begin;
        interval = (interval == 0) ? 1 : interval;
        final List<HighLowMetricValue> pts = getHistoricalData(measurements, begin, end, interval,
                MeasurementConstants.COLL_TYPE_DYNAMIC, false, PageControl.PAGE_ALL);
        return getAggData(pts);
    }

    @Transactional(readOnly = true)
    public Map<Integer, double[]> getAggregateDataAndAvailUpByMetric(final List<Measurement> measurements,
            final long begin, final long end) throws SQLException {
        List<Integer> avids = new ArrayList<Integer>();
        List<Integer> mids = new ArrayList<Integer>();
        for (Measurement meas : measurements) {

            MeasurementTemplate t = meas.getTemplate();
            if (t.isAvailability()) {
                avids.add(meas.getId());
            } else {
                mids.add(meas.getId());
            }
        }
        Map<Integer, double[]> rtn = getAggDataByMetric(mids.toArray(new Integer[0]), begin, end, false);
        rtn.putAll(availabilityManager.getAggregateDataAndAvailUpByMetric(avids, begin, end));
        return rtn;
    }

    /**
     * Fetch the list of historical data points, grouped by template, given a
     * begin and end time range. Does not return an entry for templates with no
     * associated data. PLEASE NOTE: The
     * {@link MeasurementConstants.IND_LAST_TIME} index in the {@link double[]}
     * part of the returned map does not contain the real last value. Instead it
     * is an averaged value calculated from the last 1/60 of the specified time
     * range. If this becomes an issue the best way I can think of to solve is
     * to pass in a boolean "getRealLastTime" and issue another query to get the
     * last value if this is set. It is much better than the alternative of
     * always querying the last metric time because not all pages require this
     * value.
     * 
     * @param measurements The List of {@link Measurement}s to query
     * @param begin The start of the time range
     * @param end The end of the time range
     * @see org.hyperic.hq.measurement.server.session.AvailabilityManagerImpl#getHistoricalData()
     * @return the {@link Map} of {@link Integer} to {@link double[]} which
     *         represents templateId to data points
     * 
     */
    @Transactional(readOnly = true)
    public Map<Integer, double[]> getAggregateDataByTemplate(final List<Measurement> measurements, final long begin,
            final long end) {
        // the idea here is to try and match the exact query executed by
        // getHistoricalData() when viewing the metric indicators page.
        // By issuing the same query we are hoping that the db's query
        // cache will optimize performance.
        checkTimeArguments(begin, end);
        final long interval = (end - begin) / 60;
        final List<Integer> availIds = new ArrayList<Integer>();
        final Map<Integer, List<Measurement>> measIdsByTempl = new HashMap<Integer, List<Measurement>>();
        setMeasurementObjects(measurements, availIds, measIdsByTempl);
        final Integer[] avIds = availIds.toArray(new Integer[0]);
        final Map<Integer, double[]> rtn = availabilityManager.getAggregateDataByTemplate(avIds, begin, end);
        rtn.putAll(getAggDataByTempl(measIdsByTempl, begin, end, interval));
        return rtn;
    }

    private Map<Integer, double[]> getAggDataByTempl(final Map<Integer, List<Measurement>> measIdsByTempl,
            final long begin, final long end, final long interval) {
        final HashMap<Integer, double[]> rtn = new HashMap<Integer, double[]>(measIdsByTempl.size());
        for (final Map.Entry<Integer, List<Measurement>> entry : measIdsByTempl.entrySet()) {
            final Integer tid = entry.getKey();
            final List<Measurement> meas = entry.getValue();
            final List<HighLowMetricValue> pts = getHistoricalData(meas, begin, end, interval,
                    MeasurementConstants.COLL_TYPE_DYNAMIC, false, PageControl.PAGE_ALL);
            final double[] aggData = getAggData(pts);
            if (aggData == null) {
                continue;
            }
            rtn.put(tid, aggData);
        }
        return rtn;
    }

    /**
     * @param measurements      source measurements list
     * @param availIds          measurements from the source list of type availability
     * @param measIdsByTempl    measurements from the source list which are not of type availability, sorted as per their type
     */
    private final void setMeasurementObjects(final List<Measurement> measurements, final List<Integer> availIds,
            final Map<Integer, List<Measurement>> measIdsByTempl) {
        for (Measurement m : measurements) {

            final MeasurementTemplate t = m.getTemplate();
            if (m.getTemplate().isAvailability()) {
                availIds.add(m.getId());
            } else {
                final Integer tid = t.getId();
                List<Measurement> list;
                if (null == (list = measIdsByTempl.get(tid))) {
                    list = new ArrayList<Measurement>();
                    measIdsByTempl.put(tid, list);
                }
                list.add(m);
            }
        }
    }

    private final double[] getAggData(final List<HighLowMetricValue> historicalData) {
        if (historicalData.size() == 0) {
            return null;
        }
        double high = Double.MIN_VALUE;
        double low = Double.MAX_VALUE;
        double total = 0;
        Double lastVal = null;
        int count = 0;
        long last = Long.MIN_VALUE;
        for (HighLowMetricValue mv : historicalData) {
            low = Math.min(mv.getLowValue(), low);
            high = Math.max(mv.getHighValue(), high);
            if (mv.getTimestamp() > last) {
                lastVal = new Double(mv.getValue());
            }
            final int c = mv.getCount();
            count = count + c;
            total = ((mv.getValue() * c) + total);
        }
        final double[] data = new double[MeasurementConstants.IND_LAST_TIME + 1];
        data[MeasurementConstants.IND_MIN] = low;
        data[MeasurementConstants.IND_AVG] = total / count;
        data[MeasurementConstants.IND_MAX] = high;
        data[MeasurementConstants.IND_CFG_COUNT] = count;
        if (lastVal != null) {
            data[MeasurementConstants.IND_LAST_TIME] = lastVal.doubleValue();
        }
        return data;
    }

    /**
     * Fetch the list of historical data points given a start and stop time
     * range and interval
     * 
     * @param measurements The List of Measurements to query
     * @param begin The start of the time range
     * @param end The end of the time range
     * @param interval Interval for the time range
     * @param type Collection type for the metric
     * @param returnMetricNulls Specifies whether intervals with no data should
     *        be return as {@link HighLowMetricValue} with the value set as
     *        Double.NaN
     * @see org.hyperic.hq.measurement.server.session.AvailabilityManagerImpl#getHistoricalData()
     * @return the list of data points
     * 
     */
    @Transactional(readOnly = true)
    public PageList<HighLowMetricValue> getHistoricalData(final List<Measurement> measurements, long begin,
            long end, long interval, int type, boolean returnMetricNulls, PageControl pc) {
        begin = TimingVoodoo.roundDownTime(begin, MINUTE);
        end = TimingVoodoo.roundUpTime(end, MINUTE);

        final List<Integer> availIds = new ArrayList<Integer>();
        final List<Integer> measIds = new ArrayList<Integer>();
        checkTimeArguments(begin, end, interval);
        interval = (interval == 0) ? 1 : interval;
        for (final Measurement m : measurements) {
            if (m.getTemplate().isAvailability()) {
                availIds.add(m.getId());
            } else {
                measIds.add(m.getId());
            }
        }
        final Integer[] avIds = availIds.toArray(new Integer[0]);
        final PageList<HighLowMetricValue> rtn = availabilityManager.getHistoricalAvailData(avIds, begin, end,
                interval, pc, true);
        final HQDialect dialect = measurementDAO.getHQDialect();
        final int points = (int) ((begin - end) / interval);
        final int maxExprs = (dialect.getMaxExpressions() == -1) ? Integer.MAX_VALUE : dialect.getMaxExpressions();
        for (int i = 0; i < measIds.size(); i += maxExprs) {
            final int last = Math.min(i + maxExprs, measIds.size());
            final List<Integer> sublist = measIds.subList(i, last);
            final Integer[] mids = sublist.toArray(new Integer[0]);
            final Collection<HighLowMetricValue> coll = getHistData(mids, begin, end, interval, returnMetricNulls,
                    null);
            final PageList<HighLowMetricValue> pList = new PageList<HighLowMetricValue>(coll, points);
            merge(rtn, pList);
        }
        return rtn;
    }

    private void merge(int bucket, AggMetricValue[] rtn, AggMetricValue val, long timestamp) {
        AggMetricValue amv = null;

        try {
            amv = rtn[bucket];
            if (amv == null) {
                rtn[bucket] = val;
            } else {
                amv.merge(val);
            }
        } catch (NullPointerException e) {
            log.error("Error has occured in Merge function, bucket size[" + bucket + "] "
                    + "but AggMetricValue array is null, " + e, e);
        } catch (ArrayIndexOutOfBoundsException e) {
            log.error("Error has occured in Merge function, bucket size[" + bucket + "] "
                    + "but AggMetricValue array size is [" + rtn.length + "] ," + e, e);
        }
    }

    private Collection<HighLowMetricValue> getHistData(Integer[] mids, long start, long finish, long windowSize,
            final boolean returnNulls, AtomicLong publishedInterval) {
        final int buckets = (int) ((finish - start) / windowSize);
        final Collection<HighLowMetricValue> rtn = new ArrayList<HighLowMetricValue>(buckets);
        long tmp = start;
        final AggMetricValue[] values = getAggValueSets(mids, start, finish, windowSize, returnNulls,
                publishedInterval);
        for (int ii = 0; ii < values.length; ii++) {
            final AggMetricValue val = values[ii];
            if ((null == val) && returnNulls) {
                rtn.add(new HighLowMetricValue(Double.NaN, tmp + (ii * windowSize)));
                continue;
            } else if (null == val) {
                continue;
            } else {
                rtn.add(val.getHighLowMetricValue());
            }
        }
        return rtn;
    }

    private class AggMetricValue {
        private int count;
        private double max;
        private double min;
        private double sum;
        private final long timestamp;

        private AggMetricValue(long timestamp, double sum, double max, double min, int count) {
            this.timestamp = timestamp;
            this.sum = sum;
            this.max = max;
            this.min = min;
            this.count = count;
        }

        private void merge(AggMetricValue val) {
            count += val.count;
            max = (val.max > max) ? val.max : max;
            min = (val.min < min) ? val.min : min;
            sum += val.sum;
        }

        @SuppressWarnings("unused")
        private void set(double val) {
            count++;
            max = (val > max) ? val : max;
            min = (val < min) ? val : min;
            sum += val;
        }

        private double getAvg() {
            return sum / count;
        }

        private HighLowMetricValue getHighLowMetricValue() {
            HighLowMetricValue rtn = new HighLowMetricValue(getAvg(), max, min, timestamp);
            rtn.setCount(count);
            return rtn;
        }
    }

    private CharSequence getRawDataSql(Integer[] mids, long begin, long end, AtomicLong publishedInterval) {
        if ((mids == null) || (mids.length == 0)) {
            return "";
        }
        if (log.isDebugEnabled()) {
            log.debug("gathering data from begin=" + TimeUtil.toString(begin) + ", end=" + TimeUtil.toString(end));
        }
        final HQDialect dialect = measurementDAO.getHQDialect();
        // XXX I don't like adding the sql hint, when we start testing against mysql 5.5 we should
        //     re-evaluate if this is necessary
        // 1) we shouldn't have to tell the db to explicitly use the Primary key for these
        //    queries, it should just know because we update stats every few hours
        // 2) we only want to use the primary key for bigger queries.  Our tests show
        //    that the primary key performance is very consistent for large queries and smaller
        //    queries.  But for smaller queries the measurement_id index is more effective
        final String hint = (dialect.getMetricDataHint().isEmpty() || (mids.length < 1000)) ? ""
                : " " + dialect.getMetricDataHint();
        final String sql = new StringBuilder(1024 + (mids.length * 5))
                .append("SELECT count(*) as cnt, sum(value) as sumvalue, ")
                .append("min(value) as minvalue, max(value) as maxvalue, timestamp").append(" FROM :table")
                .append(hint).append(" WHERE timestamp BETWEEN ").append(begin).append(" AND ").append(end)
                .append(MeasTabManagerUtil.getMeasInStmt(mids, true)).append(" GROUP BY timestamp").toString();
        final String[] tables = getDataTables(begin, end, false);
        if ((publishedInterval != null) && (tables.length == 1)) {
            if (tables[0].equals(TAB_DATA_1H)) {
                publishedInterval.set(HOUR);
            } else if (tables[0].equals(TAB_DATA_6H)) {
                publishedInterval.set(HOUR * 6);
            } else if (tables[0].equals(TAB_DATA_1D)) {
                publishedInterval.set(HOUR * 24);
            }
        }
        final StringBuilder sqlBuf = new StringBuilder(128 * tables.length);
        for (int i = 0; i < tables.length; i++) {
            sqlBuf.append(sql.replace(":table", tables[i]));
            if (i < (tables.length - 1)) {
                sqlBuf.append(" UNION ALL ");
            }
        }
        return sqlBuf;
    }

    private AggMetricValue[] getAggValueSets(final Integer[] mids, final long start, final long finish,
            final long windowSize, final boolean returnNulls, final AtomicLong publishedInterval) {
        final String[] tables = getDataTables(start, finish, false);
        if (tables.length <= 0) {
            throw new SystemException("ERROR: no data tables represent range " + TimeUtil.toString(start) + " - "
                    + TimeUtil.toString(finish));
        }
        final MeasRange[] ranges = (tables.length > 1) ? MeasTabManagerUtil.getMetricRanges(start, finish)
                : new MeasRange[] { new MeasRange(tables[0], start, finish) };
        final String threadName = Thread.currentThread().getName();
        final List<Thread> threads = new ArrayList<Thread>(ranges.length);
        final Collection<AggMetricValue[]> data = new ArrayList<AggMetricValue[]>(ranges.length);
        final int maxThreads = 4;
        // The result encapsulates the timeframe start -> finish.  The results are gathered
        // via sub-queries.  Each sub-query is from begin -> end
        // start                                                                    finish
        // <----------------------------------------------------------------------------->
        // (begin-end)(begin-end)(begin-end)(begin-end)(begin-end)(begin-end)(begin-end)..
        for (final MeasRange range : ranges) {
            final long min = range.getMinTimestamp();
            final long max = range.getMaxTimestamp();
            final long begin = (min < start) ? start : min;
            final long end = (max > finish) ? finish : max;
            waitForThreads(threads, maxThreads);
            // XXX may want to add a thread pool or a static Executor here so that these
            // queries don't overwhelm the DB
            Thread thread = getNewDataWorkerThread(mids, start, finish, begin, end, windowSize, returnNulls,
                    publishedInterval, threadName, data);
            thread.start();
            threads.add(thread);
        }
        waitForThreads(threads);
        return mergeThreadData(start, finish, windowSize, data);
    }

    /**
     * @param begin - the begin time of the sub window
     * @param end - the end time of the sub window
     * @param start - the start time of the user specified window
     * @param finish - the finish time of the user specified window
     */
    private Thread getNewDataWorkerThread(final Integer[] mids, final long start, final long finish,
            final long begin, final long end, final long windowSize, final boolean returnNulls,
            final AtomicLong publishedInterval, final String threadName, final Collection<AggMetricValue[]> data) {
        final boolean debug = log.isDebugEnabled();
        final Thread thread = new Thread() {
            @Override
            public void run() {
                final StopWatch watch = new StopWatch();
                if (debug) {
                    watch.markTimeBegin(
                            "data gatherer begin=" + TimeUtil.toString(begin) + ", end=" + TimeUtil.toString(end));
                }
                final AggMetricValue[] array = getHistDataSet(mids, start, finish, begin, end, windowSize,
                        returnNulls, publishedInterval, threadName);
                if (debug) {
                    watch.markTimeEnd(
                            "data gatherer begin=" + TimeUtil.toString(begin) + ", end=" + TimeUtil.toString(end));
                    log.debug(watch);
                }
                synchronized (data) {
                    data.add(array);
                }
            }
        };
        return thread;
    }

    private AggMetricValue[] getHistDataSet(Integer[] mids, long start, long finish, long rangeBegin, long rangeEnd,
            long windowSize, final boolean returnNulls, AtomicLong publishedInterval, String threadName) {
        final CharSequence sqlBuf = getRawDataSql(mids, rangeBegin, rangeEnd, publishedInterval);
        final int buckets = (int) ((finish - start) / windowSize);
        final AggMetricValue[] array = new AggMetricValue[buckets];
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            conn = safeGetConnection();
            stmt = conn.createStatement();
            int timeout = stmt.getQueryTimeout();
            if (timeout == 0) {
                stmt.setQueryTimeout(transactionTimeout);
            }
            rs = stmt.executeQuery("/* " + threadName + " */ " + sqlBuf.toString());
            final int sumValCol = rs.findColumn("sumvalue");
            final int countValCol = rs.findColumn("cnt");
            final int minValCol = rs.findColumn("minvalue");
            final int maxValCol = rs.findColumn("maxvalue");
            final int timestampCol = rs.findColumn("timestamp");
            while (rs.next()) {
                final double sum = rs.getDouble(sumValCol);
                final double min = rs.getDouble(minValCol);
                final double max = rs.getDouble(maxValCol);
                final int count = rs.getInt(countValCol);
                final long timestamp = rs.getLong(timestampCol);
                final AggMetricValue val = new AggMetricValue(timestamp, sum, max, min, count);
                if ((timestamp < start) || (timestamp > finish)) {
                    continue;
                }
                final int bucket = (int) (buckets - ((finish - timestamp) / (float) windowSize));
                if (bucket < 0) {
                    continue;
                }
                merge(bucket, array, val, timestamp);
            }
        } catch (SQLException e) {
            throw new SystemException(e);
        } finally {
            DBUtil.closeJDBCObjects(getClass().getName(), conn, stmt, rs);
        }
        return array;
    }

    private AggMetricValue[] mergeThreadData(long start, long finish, long windowSize,
            Collection<AggMetricValue[]> data) {
        final int buckets = (int) ((finish - start) / windowSize);
        final AggMetricValue[] rtn = new AggMetricValue[buckets];
        for (final AggMetricValue[] vals : data) {
            for (int ii = 0; ii < vals.length; ii++) {
                final AggMetricValue val = vals[ii];
                if (val == null) {
                    continue;
                }
                if (rtn[ii] == null) {
                    rtn[ii] = val;
                } else {
                    rtn[ii].merge(val);
                }
            }
        }
        return rtn;
    }

    private void waitForThreads(List<Thread> threads) {
        for (final Thread thread : threads) {
            while (thread.isAlive()) {
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    log.debug(e, e);
                }
            }
        }
    }

    private void waitForThreads(List<Thread> threads, int maxThreads) {
        if (threads.isEmpty() || (threads.size() < maxThreads)) {
            return;
        }
        int i = 0;
        while (threads.size() >= maxThreads) {
            i = ((i >= threads.size()) || (i < 0)) ? 0 : i;
            final Thread thread = threads.get(i);
            try {
                if (thread.isAlive()) {
                    thread.join(100);
                }
                if (!thread.isAlive()) {
                    threads.remove(i);
                } else {
                    i++;
                }
            } catch (InterruptedException e) {
                log.debug(e, e);
            }
        }
    }

    private void merge(PageList<HighLowMetricValue> master, PageList<HighLowMetricValue> toMerge) {
        if (master.size() == 0) {
            master.addAll(toMerge);
            return;
        }
        for (int i = 0; i < master.size(); i++) {
            if (toMerge.size() < (i + 1)) {
                break;
            }
            final HighLowMetricValue val = master.get(i);
            final HighLowMetricValue mval = toMerge.get(i);
            final int mcount = mval.getCount();
            final int count = val.getCount();
            final int tot = count + mcount;
            final double high = ((val.getHighValue() * count) + (mval.getHighValue() * mcount)) / tot;
            val.setHighValue(high);
            final double low = ((val.getLowValue() * count) + (mval.getLowValue() * mcount)) / tot;
            val.setLowValue(low);
            final double value = ((val.getValue() * count) + (mval.getValue() * mcount)) / tot;
            val.setValue(value);
        }
    }

    private long getPurgeRaw() {
        if (!confDefaultsLoaded) {
            loadConfigDefaults();
        }
        return purgeRaw;
    }

    /**
     * 
     * Get the last MetricValue for the given Measurement.
     * 
     * @param m The Measurement
     * @return The MetricValue or null if one does not exist.
     * 
     */
    @Transactional(readOnly = true)
    public MetricValue getLastHistoricalData(Measurement m) {
        if (m.getTemplate().isAvailability()) {
            return availabilityManager.getLastAvail(m);
        } else {
            return getLastHistData(m);
        }
    }

    private MetricValue getLastHistData(Measurement m) {
        // Check the cache

        MetricValue mval = metricDataCache.get(m.getId(), 0);
        if (mval != null) {
            return mval;
        }
        // Get the data points and add to the ArrayList
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;

        try {
            conn = dbUtil.getConnection();

            final String metricUnion = MeasurementUnionStatementBuilder.getUnionStatement(8 * HOUR, m.getId(),
                    measurementDAO.getHQDialect());
            final StringBuilder sqlBuf = new StringBuilder().append("SELECT timestamp, value FROM ")
                    .append(metricUnion).append(", (SELECT MAX(timestamp) AS maxt").append(" FROM ")
                    .append(metricUnion).append(") mt ").append("WHERE measurement_id = ").append(m.getId())
                    .append(" AND timestamp = maxt");

            stmt = conn.createStatement();

            if (log.isDebugEnabled()) {
                log.debug("getLastHistoricalData(): " + sqlBuf);
            }

            rs = stmt.executeQuery(sqlBuf.toString());

            if (rs.next()) {
                MetricValue mv = getMetricValue(rs);
                metricDataCache.add(m.getId(), mv);
                return mv;
            } else {
                // No cached value, nothing in the database
                return null;
            }
        } catch (SQLException e) {
            log.error("Unable to look up historical data for " + m, e);
            throw new SystemException(e);
        } finally {
            DBUtil.closeJDBCObjects(LOG_CTX, conn, stmt, rs);
        }
    }

    /**
     * Fetch the most recent data point for particular Measurements.
     * 
     * @param mids The List of Measurements to query. In the list of
     *        Measurements null values are allowed as placeholders.
     * @param timestamp Only use data points with collection times greater than
     *        the given timestamp.
     * @return A Map of measurement ids to MetricValues. TODO: We should change
     *         this method to now allow NULL values. This is legacy and only
     *         used by the Metric viewer and Availabilty Summary portlets.
     * 
     */
    @Transactional(readOnly = true)
    public Map<Integer, MetricValue> getLastDataPoints(List<Integer> mids, long timestamp) {
        final List<Integer> availIds = new ArrayList<Integer>(mids.size());
        final List<Integer> measurementIds = new ArrayList<Integer>(mids.size());
        for (Integer measId : mids) {
            if (measId == null) {
                // See above.
                measurementIds.add(null);
                continue;
            }
            final Measurement m = measurementDAO.get(measId);
            if (m == null) {
                // See above.
                measurementIds.add(null);
                continue;
            } else if (m.getTemplate().isAvailability()) {
                availIds.add(m.getId());
            } else {
                measurementIds.add(measId);
            }
        }
        final Integer[] avIds = availIds.toArray(new Integer[0]);
        final StopWatch watch = new StopWatch();
        final boolean debug = log.isDebugEnabled();
        if (debug) {
            watch.markTimeBegin("getLastDataPts");
        }
        final Map<Integer, MetricValue> data = getLastDataPts(measurementIds, timestamp);
        if (debug) {
            watch.markTimeEnd("getLastDataPts");
        }
        if (availIds.size() > 0) {
            if (debug) {
                watch.markTimeBegin("getLastAvail");
            }
            data.putAll(availabilityManager.getLastAvail(avIds));
            if (debug) {
                watch.markTimeEnd("getLastAvail");
            }
        }
        if (debug) {
            log.debug(watch);
        }
        return data;
    }

    private Map<Integer, MetricValue> getLastDataPts(Collection<Integer> mids, long timestamp) {
        final int BATCH_SIZE = 500;
        final Map<Integer, MetricValue> rtn = new HashMap<Integer, MetricValue>(mids.size());
        if ((mids == null) || mids.isEmpty()) {
            return rtn;
        }
        // all cached values are inserted into rtn
        // nodata represents values that are not cached
        final Collection<Integer> nodata = getCachedDataPoints(mids, rtn, timestamp);
        ArrayList<Integer> ids = null;
        final boolean debug = log.isDebugEnabled();
        if (nodata.isEmpty()) {
            if (debug) {
                log.debug("got data from cache");
            }
            // since we have all the data from cache (nodata is empty), just return it
            return rtn;
        } else {
            ids = new ArrayList<Integer>(nodata);
        }
        Connection conn = null;
        Statement stmt = null;
        final StopWatch watch = new StopWatch();
        try {
            conn = dbUtil.getConnection();
            stmt = conn.createStatement();
            for (int i = 0; i < ids.size(); i += BATCH_SIZE) {
                final int max = Math.min(ids.size(), i + BATCH_SIZE);
                // Create sub array
                Collection<Integer> subids = ids.subList(i, max);
                if (debug) {
                    watch.markTimeBegin("setDataPoints");
                }
                setDataPoints(rtn, timestamp, subids, stmt);
                if (debug) {
                    watch.markTimeEnd("setDataPoints");
                }
            }
        } catch (SQLException e) {
            throw new SystemException("Cannot get last values", e);
        } finally {
            DBUtil.closeJDBCObjects(LOG_CTX, conn, stmt, null);
            if (debug) {
                log.debug(watch);
            }
        }
        List<DataPoint> dataPoints = convertMetricId2MetricValueMapToDataPoints(rtn);
        updateMetricDataCache(dataPoints);
        return rtn;
    }

    /**
     * Get data points from cache only
     */
    @Transactional(readOnly = true)
    public Collection<Integer> getCachedDataPoints(Collection<Integer> mids, Map<Integer, MetricValue> data,
            long timestamp) {
        ArrayList<Integer> nodata = new ArrayList<Integer>();
        for (Integer mid : mids) {
            if (mid == null) {
                continue;
            }
            final MetricValue mval = metricDataCache.get(mid, timestamp);
            if (mval != null) {
                data.put(mid, mval);
            } else {
                nodata.add(mid);
            }
        }
        return nodata;
    }

    private void setDataPoints(Map<Integer, MetricValue> data, long timestamp, Collection<Integer> measIds,
            Statement stmt) throws SQLException {
        ResultSet rs = null;
        try {
            StringBuilder sqlBuf = getLastDataPointsSQL(timestamp, measIds);
            if (log.isTraceEnabled()) {
                log.trace("getLastDataPoints(): " + sqlBuf);
            }
            rs = stmt.executeQuery(sqlBuf.toString());

            while (rs.next()) {
                Integer mid = new Integer(rs.getInt(1));
                if (!data.containsKey(mid)) {
                    MetricValue mval = getMetricValue(rs);
                    data.put(mid, mval);
                    metricDataCache.add(mid, mval); // Add to cache to avoid
                    // lookup
                }
            }
        } finally {
            DBUtil.closeResultSet(LOG_CTX, rs);
        }
    }

    private StringBuilder getLastDataPointsSQL(long timestamp, Collection<Integer> measIds) {
        String tables = (timestamp != MeasurementConstants.TIMERANGE_UNLIMITED)
                ? MeasurementUnionStatementBuilder.getUnionStatement(timestamp, now(), measIds,
                        measurementDAO.getHQDialect())
                : MeasurementUnionStatementBuilder.getUnionStatement(getPurgeRaw(), measIds,
                        measurementDAO.getHQDialect());

        StringBuilder sqlBuf = new StringBuilder("SELECT measurement_id, value, timestamp" + " FROM " + tables
                + ", " + "(SELECT measurement_id AS id, MAX(timestamp) AS maxt" + " FROM " + tables + " WHERE ")
                        .append(MeasTabManagerUtil.getMeasInStmt(measIds, false));

        if (timestamp != MeasurementConstants.TIMERANGE_UNLIMITED) {
            ;
        }
        sqlBuf.append(" AND timestamp >= ").append(timestamp);

        sqlBuf.append(" GROUP BY measurement_id) mt").append(" WHERE timestamp = maxt AND measurement_id = id");
        return sqlBuf;
    }

    private long now() {
        return System.currentTimeMillis();
    }

    /**
     * Convert the MetricId->MetricValue map to a list of DataPoints.
     * 
     * @param metricId2MetricValueMap The map to convert.
     * @return The list of DataPoints.
     */
    private List<DataPoint> convertMetricId2MetricValueMapToDataPoints(
            Map<Integer, MetricValue> metricId2MetricValueMap) {

        List<DataPoint> dataPoints = new ArrayList<DataPoint>(metricId2MetricValueMap.size());

        for (Entry<Integer, MetricValue> entry : metricId2MetricValueMap.entrySet()) {
            Integer mid = entry.getKey();
            MetricValue mval = entry.getValue();
            dataPoints.add(new DataPoint(mid, mval));
        }

        return dataPoints;
    }

    /**
     * Get a Baseline data.
     * 
     * 
     */
    @Transactional(readOnly = true)
    public double[] getBaselineData(Measurement meas, long begin, long end) {
        if (meas.getTemplate().getAlias().equalsIgnoreCase("availability")) {
            Integer[] mids = new Integer[1];
            Integer id = meas.getId();
            mids[0] = id;
            return availabilityManager.getAggregateData(mids, begin, end).get(id);
        } else {
            return getBaselineMeasData(meas, begin, end);
        }
    }

    private double[] getBaselineMeasData(Measurement meas, long begin, long end) {
        // Check the begin and end times
        checkTimeArguments(begin, end);
        Integer id = meas.getId();

        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;

        // The table to query from
        String table = getDataTable(begin, end, id.intValue());
        try {
            conn = dbUtil.getConnection();

            StringBuilder sqlBuf = new StringBuilder("SELECT MIN(value), AVG(value), MAX(value) FROM ")
                    .append(table).append(" WHERE timestamp BETWEEN ").append(begin).append(" AND ").append(end)
                    .append(" AND measurement_id = ").append(id.intValue());

            stmt = conn.createStatement();
            rs = stmt.executeQuery(sqlBuf.toString());

            rs.next(); // Better have some result
            double[] data = new double[MeasurementConstants.IND_MAX + 1];
            data[MeasurementConstants.IND_MIN] = rs.getDouble(1);
            data[MeasurementConstants.IND_AVG] = rs.getDouble(2);
            data[MeasurementConstants.IND_MAX] = rs.getDouble(3);

            return data;
        } catch (SQLException e) {
            throw new MeasurementDataSourceException("Can't get baseline data for: " + id, e);
        } finally {
            DBUtil.closeJDBCObjects(LOG_CTX, conn, stmt, rs);
        }
    }

    /**
     * Fetch a map of aggregate data values keyed by metrics given a start and
     * stop time range
     * 
     * @param tids The template id's of the Measurement
     * @param iids The instance id's of the Measurement
     * @param begin The start of the time range
     * @param end The end of the time range
     * @param useAggressiveRollup uses a measurement rollup table to fetch the
     *        data if the time range spans more than one data table's max
     *        timerange
     * @return the Map of data points
     * 
     */
    @Transactional(readOnly = true)
    public Map<Integer, double[]> getAggregateDataByMetric(Integer[] tids, Integer[] iids, long begin, long end,
            boolean useAggressiveRollup) {
        checkTimeArguments(begin, end);
        Map<Integer, double[]> rtn = getAggDataByMetric(tids, iids, begin, end, useAggressiveRollup);
        Collection<Measurement> metrics = measurementDAO.findAvailMeasurements(tids, iids);
        if (metrics.size() > 0) {
            Integer[] mids = new Integer[metrics.size()];
            Iterator<Measurement> it = metrics.iterator();
            for (int i = 0; i < mids.length; i++) {
                Measurement m = it.next();
                mids[i] = m.getId();
            }
            rtn.putAll(availabilityManager.getAggregateData(mids, begin, end));
        }
        return rtn;
    }

    private Map<Integer, double[]> getAggDataByMetric(Integer[] tids, Integer[] iids, long begin, long end,
            boolean useAggressiveRollup) {
        // Check the begin and end times
        this.checkTimeArguments(begin, end);
        begin = TimingVoodoo.roundDownTime(begin, MINUTE);
        end = TimingVoodoo.roundDownTime(end, MINUTE);

        // Result set
        HashMap<Integer, double[]> resMap = new HashMap<Integer, double[]>();

        if ((tids.length == 0) || (iids.length == 0)) {
            return resMap;
        }

        // Get the data points and add to the ArrayList
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        StopWatch timer = new StopWatch();

        StringBuffer iconj = new StringBuffer(DBUtil.composeConjunctions("instance_id", iids.length));

        DBUtil.replacePlaceHolders(iconj, iids);
        StringBuilder tconj = new StringBuilder(DBUtil.composeConjunctions("template_id", tids.length));

        try {
            conn = dbUtil.getConnection();
            // The table to query from
            List<Integer> measids = MeasTabManagerUtil.getMeasIds(conn, tids, iids);
            String table = getDataTable(begin, end, measids.toArray(new Integer[0]), useAggressiveRollup);
            // Use the already calculated min, max and average on
            // compressed tables.
            String minMax;
            if (usesMetricUnion(begin, end, useAggressiveRollup)) {
                minMax = " MIN(value), AVG(value), MAX(value) ";
            } else {
                minMax = " MIN(minvalue), AVG(value), MAX(maxvalue) ";
            }
            final String aggregateSQL = "SELECT id, " + minMax + " FROM " + table + "," + TAB_MEAS
                    + " WHERE timestamp BETWEEN ? AND ? AND measurement_id = id " + " AND " + iconj + " AND "
                    + tconj + " GROUP BY id";
            if (log.isTraceEnabled()) {
                log.trace("getAggregateDataByMetric(): " + aggregateSQL);
            }
            stmt = conn.prepareStatement(aggregateSQL);
            int i = 1;
            stmt.setLong(i++, begin);
            stmt.setLong(i++, end);

            i = this.setStatementArguments(stmt, i, tids);

            try {
                rs = stmt.executeQuery();

                while (rs.next()) {
                    double[] data = new double[IND_MAX + 1];

                    Integer mid = new Integer(rs.getInt(1));
                    data[IND_MIN] = rs.getDouble(2);
                    data[IND_AVG] = rs.getDouble(3);
                    data[IND_MAX] = rs.getDouble(4);

                    // Put it into the result map
                    resMap.put(mid, data);
                }
            } finally {
                DBUtil.closeResultSet(LOG_CTX, rs);
            }
            if (log.isTraceEnabled()) {
                log.trace("getAggregateDataByMetric(): Statement query elapsed " + "time: " + timer.getElapsed());
            }
            return resMap;
        } catch (SQLException e) {
            log.debug("getAggregateDataByMetric()", e);
            throw new SystemException(e);
        } finally {
            DBUtil.closeJDBCObjects(LOG_CTX, conn, stmt, null);
        }
    }

    /**
     * Fetch a map of aggregate data values keyed by metrics given a start and
     * stop time range
     * 
     * @param measurements The id's of the Measurement
     * @param begin The start of the time range
     * @param end The end of the time range
     * @param useAggressiveRollup uses a measurement rollup table to fetch the
     *        data if the time range spans more than one data table's max
     *        timerange
     * @return the map of data points
     * 
     */
    @Transactional(readOnly = true)
    public Map<Integer, double[]> getAggregateDataByMetric(List<Measurement> measurements, long begin, long end,
            boolean useAggressiveRollup) {
        checkTimeArguments(begin, end);
        List<Integer> avids = new ArrayList<Integer>();
        List<Integer> mids = new ArrayList<Integer>();
        for (Measurement meas : measurements) {

            MeasurementTemplate t = meas.getTemplate();
            if (t.isAvailability()) {
                avids.add(meas.getId());
            } else {
                mids.add(meas.getId());
            }
        }
        Map<Integer, double[]> rtn = getAggDataByMetric(mids.toArray(new Integer[0]), begin, end,
                useAggressiveRollup);
        rtn.putAll(availabilityManager.getAggregateData(avids.toArray(new Integer[0]), begin, end));
        return rtn;
    }

    private Map<Integer, double[]> getAggDataByMetric(Integer[] mids, long begin, long end,
            boolean useAggressiveRollup) {
        // Check the begin and end times
        this.checkTimeArguments(begin, end);
        begin = TimingVoodoo.roundDownTime(begin, MINUTE);
        end = TimingVoodoo.roundDownTime(end, MINUTE);

        // The table to query from
        String table = getDataTable(begin, end, mids, useAggressiveRollup);
        // Result set
        HashMap<Integer, double[]> resMap = new HashMap<Integer, double[]>();

        if (mids.length == 0) {
            return resMap;
        }

        // Get the data points and add to the ArrayList
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        StopWatch timer = new StopWatch();

        StringBuffer mconj = new StringBuffer(DBUtil.composeConjunctions("measurement_id", mids.length));
        DBUtil.replacePlaceHolders(mconj, mids);

        // Use the already calculated min, max and average on
        // compressed tables.
        String minMax;
        if (usesMetricUnion(begin, end, useAggressiveRollup)) {
            minMax = " MIN(value), AVG(value), MAX(value), ";
        } else {
            minMax = " MIN(minvalue), AVG(value), MAX(maxvalue), ";
        }

        final String aggregateSQL = "SELECT measurement_id, " + minMax + " count(*) " + " FROM " + table
                + " WHERE timestamp BETWEEN ? AND ? AND " + mconj + " GROUP BY measurement_id";

        try {
            conn = dbUtil.getConnection();

            if (log.isTraceEnabled()) {
                log.trace("getAggregateDataByMetric(): " + aggregateSQL);
            }

            stmt = conn.prepareStatement(aggregateSQL);

            int i = 1;
            stmt.setLong(i++, begin);
            stmt.setLong(i++, end);

            try {
                rs = stmt.executeQuery();

                while (rs.next()) {
                    double[] data = new double[IND_CFG_COUNT + 1];
                    Integer mid = new Integer(rs.getInt(1));
                    data[IND_MIN] = rs.getDouble(2);
                    data[IND_AVG] = rs.getDouble(3);
                    data[IND_MAX] = rs.getDouble(4);
                    data[IND_CFG_COUNT] = rs.getDouble(5);

                    // Put it into the result map
                    resMap.put(mid, data);
                }
            } finally {
                DBUtil.closeResultSet(LOG_CTX, rs);
            }

            if (log.isTraceEnabled()) {
                log.trace("getAggregateDataByMetric(): Statement query elapsed " + "time: " + timer.getElapsed());
            }

            return resMap;
        } catch (SQLException e) {
            log.debug("getAggregateDataByMetric()", e);
            throw new SystemException(e);
        } finally {
            DBUtil.closeJDBCObjects(LOG_CTX, conn, stmt, null);
        }
    }

    // TODO remove after HE-54 allows injection
    @Transactional(readOnly = true)
    public Analyzer getAnalyzer() {
        boolean analyze = false;
        try {
            Properties conf = serverConfigManager.getConfig();
            if (conf.containsKey(HQConstants.OOBEnabled)) {
                analyze = Boolean.valueOf(conf.getProperty(HQConstants.OOBEnabled)).booleanValue();
            }
        } catch (Exception e) {
            log.debug("Error looking up server configs", e);
        } finally {
            if (analyze) {
                return (Analyzer) ProductProperties.getPropertyInstance("hyperic.hq.measurement.analyzer");
            }
        }
        return null;
    }

}