uk.ac.soton.itinnovation.ecc.service.services.DataService.java Source code

Java tutorial

Introduction

Here is the source code for uk.ac.soton.itinnovation.ecc.service.services.DataService.java

Source

/////////////////////////////////////////////////////////////////////////
//
//  University of Southampton IT Innovation Centre, 2014
//
// Copyright in this library belongs to the University of Southampton
// IT Innovation Centre of Gamma House, Enterprise Road,
// Chilworth Science Park, Southampton, SO16 7NS, UK.
//
// This software may not be used, sold, licensed, transferred, copied
// or reproduced in whole or in part in any manner or form or in or
// on any media by any person other than in accordance with the terms
// of the Licence Agreement supplied with the software, or otherwise
// without the prior written consent of the copyright owners.
//
// This software is distributed WITHOUT ANY WARRANTY, without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE, except where stated in the Licence Agreement supplied with
// the software.
//
//   Created By : Maxim Bashevoy, Simon Crowle
//   Created Date : 2014-04-02
//   Created for Project : EXPERIMEDIA
//
/////////////////////////////////////////////////////////////////////////
package uk.ac.soton.itinnovation.ecc.service.services;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.common.dataModel.experiment.Experiment;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.common.dataModel.metrics.Entity;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.common.dataModel.metrics.Attribute;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.common.dataModel.metrics.Measurement;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.common.dataModel.metrics.MeasurementSet;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.common.dataModel.metrics.Metric;
import uk.ac.soton.itinnovation.ecc.service.utils.MetricCalculator;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.common.dataModel.metrics.MetricGenerator;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.common.dataModel.metrics.MetricHelper;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.common.dataModel.metrics.Report;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.common.dataModel.monitor.EMClient;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.edm.factory.EDMInterfaceFactory;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.edm.spec.metrics.IMonitoringEDM;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.edm.spec.metrics.NoDataException;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.edm.spec.metrics.dao.IEntityDAO;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.edm.spec.metrics.dao.IExperimentDAO;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.edm.spec.metrics.dao.IMeasurementSetDAO;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.edm.spec.metrics.dao.IMetricGeneratorDAO;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.edm.spec.metrics.dao.IReportDAO;
import uk.ac.soton.itinnovation.ecc.service.domain.DatabaseConfiguration;
import uk.ac.soton.itinnovation.ecc.service.domain.EccAttribute;
import uk.ac.soton.itinnovation.ecc.service.domain.EccClient;
import uk.ac.soton.itinnovation.ecc.service.domain.EccCounterMeasurement;
import uk.ac.soton.itinnovation.ecc.service.domain.EccCounterMeasurementSet;
import uk.ac.soton.itinnovation.ecc.service.domain.EccEntity;
import uk.ac.soton.itinnovation.ecc.service.domain.EccMeasurement;
import uk.ac.soton.itinnovation.ecc.service.domain.EccMeasurementSet;
import uk.ac.soton.itinnovation.ecc.service.utils.EccAttributesComparator;
import uk.ac.soton.itinnovation.ecc.service.utils.EccEntitiesComparator;
import uk.ac.soton.itinnovation.ecc.service.utils.EccMeasurementsComparator;
import uk.ac.soton.itinnovation.experimedia.arch.ecc.em.impl.dataModelEx.EMClientEx;

/**
 * Provides access to data in the database.
 */
@Service("dataService")
public class DataService {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private IMonitoringEDM expDataManager;
    private IExperimentDAO experimentDAO;
    private IMetricGeneratorDAO metricGenDAO;
    private IEntityDAO entityDAO;
    private IMeasurementSetDAO msetDAO;
    private IReportDAO expReportDAO;

    private boolean started = false;

    @Autowired
    ExperimentService experimentService;

    public DataService() {
    }

    /**
     * Initialises the service (empty).
     */
    @PostConstruct
    public void init() {
    }

    /**
     * Ensures the service is shut down properly.
     */
    @PreDestroy
    public void shutdown() {
        logger.debug("Shutting down data service");

        boolean stopResult = stop();

        logger.debug("Data service shut down: " + stopResult);
    }

    /**
     * Attempts to stop service.
     *
     * @return false on fail.
     */
    public boolean stop() {
        if (started) {
            logger.debug("Stopping data service");
            try {
                expReportDAO = null;
                msetDAO = null;
                entityDAO = null;
                metricGenDAO = null;
                experimentDAO = null;
                expDataManager = null;

                return true;
            } catch (Throwable e) {
                logger.error("Failed to stop data service", e);
                return false;
            }
        } else {
            logger.error("Failed to stop data service: not started");
            return false;
        }
    }

    /**
     * Starts the service (should only be called by
     * {@link ConfigurationService})
     *
     * @param databaseConfiguration
     * @return
     */
    public boolean start(DatabaseConfiguration databaseConfiguration) {
        started = false;

        if (databaseConfiguration != null) {

            // Make sure clear any previously created EDM references
            expDataManager = null;
            experimentDAO = null;
            metricGenDAO = null;
            entityDAO = null;
            msetDAO = null;
            expReportDAO = null;

            // Create properties for EDM Factory
            Properties props = new Properties();
            props.put("dbPassword", databaseConfiguration.getUserPassword());
            props.put("dbName", databaseConfiguration.getDatabaseName());
            props.put("dbType", databaseConfiguration.getDatabaseType());
            props.put("dbURL", databaseConfiguration.getUrl());
            props.put("dbUsername", databaseConfiguration.getUserName());

            // Try getting data accessors
            try {
                expDataManager = EDMInterfaceFactory.getMonitoringEDM(props);

                if (!expDataManager.isDatabaseSetUpAndAccessible()) {
                    throw new Exception("Could not access EDM: database is not accessible");
                }

                experimentDAO = expDataManager.getExperimentDAO();
                metricGenDAO = expDataManager.getMetricGeneratorDAO();
                entityDAO = expDataManager.getEntityDAO();
                msetDAO = expDataManager.getMeasurementSetDAO();
                expReportDAO = expDataManager.getReportDAO();

                started = true;
            } catch (Exception ex) {
                logger.error("Could not start data service: " + ex.getMessage());
            }
        }
        return started;
    }

    public boolean isStarted() {
        return started;
    }

    /**
     * Use this method to retrieve high-level meta-data relating to all known
     * experiments stored in the data service database. This method will return
     * instances of class Experiment but these will not contain metric models.
     *
     * @return - Returns a set of experiment instances (high-level data only)
     */
    public Set<Experiment> getAllKnownExperiments() {

        HashSet<Experiment> result = new HashSet<Experiment>();

        // Safety first
        if (started) {
            try {
                result.addAll(experimentDAO.getExperiments(false));
            } catch (Exception ex) {
                logger.warn("Could not retrieve experiments: " + ex.getMessage());
            }
        } else {
            logger.warn("Cannot get experiments: service not started");
        }

        return result;
    }

    /**
     * Use this method to retrieve high-level experiment meta-data and also all
     * known MetricGenerators currently associated with the experiment.
     *
     * @param expID - UUID of the experiment required
     * @return - Experiment instance with all currently known metric generators
     */
    public Experiment getExperimentWithMetricModels(UUID expID) {

        // Safety first
        if (!started) {
            logger.error("Failed to get experiment: service not started");
            return null;
        }
        if (expID == null) {
            logger.error("Failed to get experiment: ID is null");
            return null;
        }

        Experiment experiment = null;

        try {
            experiment = experimentDAO.getExperiment(expID, true);
        } catch (Exception ex) {
            logger.error("Could not retrieve experiment: " + ex.getMessage());
        }

        return experiment;
    }

    /**
     * Use this method to quickly extract all known (metric) entities from an
     * experiment.
     *
     * @param expID - ID of the experiment you need entities from.
     * @return - Returns a set of entities for the experiment (if any exist)
     * @throws Exception - throws if the experiment ID is invalid or there is no
     * experiment that ID
     */
    public Set<Entity> getEntitiesForExperiment(UUID expID) throws Exception {

        // Safety
        if (!started) {
            throw new Exception("Could not get Entities for experiment: service not started");
        }
        if (expID == null) {
            throw new Exception("Could not get Entities for experiment: ID is null");
        }

        HashSet<Entity> entities = new HashSet<Entity>();

        try {
            entities.addAll(entityDAO.getEntitiesForExperiment(expID, true));
        } catch (Exception ex) {
            logger.warn("Could not get entities for experiment (" + expID.toString() + "): " + ex.getMessage());
        }

        return entities;
    }

    public Attribute getAttribute(String uuid) {
        if (uuid == null) {
            logger.error("Failed to return attribute, requested id is NULL");
            return null;
        }

        if (!started) {
            logger.error("Failed to return attribute with id [" + uuid + "]: data service not yet started");
            return null;
        } else {
            if (experimentService.getActiveExperiment() == null) {
                logger.error("Failed to return attribute with id [" + uuid + "]: no active experiment");
                return null;
            } else {
                try {
                    return entityDAO.getAttribute(UUID.fromString(uuid));
                } catch (Exception e) {
                    logger.error("Failed to return attribute with id [" + uuid + "]", e);
                    return null;
                }
            }
        }
    }

    /**
     *
     * @param experimentUuid
     * @param uuid
     * @return attribute by UUID.
     */
    public Attribute getAttribute(String experimentUuid, String uuid) {
        if (uuid == null) {
            logger.error("Failed to return attribute, requested id is NULL");
            return null;
        }

        if (!started) {
            logger.error("Failed to return attribute with id [" + uuid + "]: data service not yet started");
            return null;
        } else {
            try {
                Attribute result = null;
                for (Entity e : entityDAO.getEntitiesForExperiment(UUID.fromString(experimentUuid), true)) {

                    Set<Attribute> attributes = e.getAttributes();
                    if (attributes != null) {

                        for (Attribute a : attributes) {
                            if (a.getUUID().equals(UUID.fromString(uuid))) {
                                result = a;
                                break;
                            }
                        }
                    }
                }

                return result;
            } catch (Exception e) {
                logger.error("Failed to return attribute with id [" + uuid + "]", e);
                return null;
            }
        }
    }

    /**
     *
     * @param uuid
     * @param withAttributes
     * @return attribute by UUID.
     */
    public Entity getEntity(String uuid, boolean withAttributes) {
        if (uuid == null) {
            logger.error("Failed to return entity, requested id is NULL");
            return null;
        }

        if (!started) {
            logger.error("Failed to return entity with id [" + uuid + "]: data service not yet started");
            return null;
        } else {
            if (experimentService.getActiveExperiment() == null) {
                logger.error("Failed to return entity with id [" + uuid + "]: no active experiment");
                return null;
            } else {
                try {
                    return entityDAO.getEntity(UUID.fromString(uuid), withAttributes);
                } catch (Exception e) {
                    logger.error("Failed to return entity with id [" + uuid + "]", e);
                    return null;
                }
            }
        }
    }

    /**
     * Use this method to retrieve ALL MeasurementSets (WITHOUT measurement
     * data) related to a specific attribute in an experiment. This method is
     * useful if you want to retrieve all measurement data (irrespective of
     * which MetricGenerator created it) for a specific attribute.
     *
     * @param expID - Non-null experiment ID for the experiment the attribute
     * belongs to
     * @param attr - Non-null attribute for which measurements are sought
     * @return - Returns (a possibly empty) set of measurement sets
     * @throws Exception - throws if the input parameters are null or invalid
     */
    public Set<MeasurementSet> getAllEmptyMeasurementSetsForAttribute(UUID expID, Attribute attr) throws Exception {

        // Safety first
        if (!started) {
            throw new Exception("Cannot get experiments: service not started");
        }
        if (expID == null) {
            throw new Exception("Could not get Measurement Sets for Attribute: experiment ID is null");
        }
        if (attr == null) {
            throw new Exception("Could not get Measurement Sets for Attribute: Attribute is null");
        }

        HashSet<MeasurementSet> mSets = new HashSet<MeasurementSet>();

        // Get metric generators for this experiment
        try {
            Set<MetricGenerator> metGens = metricGenDAO.getMetricGeneratorsForExperiment(expID, true);

            Map<UUID, MeasurementSet> allMemSets = MetricHelper.getMeasurementSetsForAttribute(attr, metGens);
            mSets.addAll(allMemSets.values());
        } catch (Exception ex) {
            String msg = "Could not retrieve all measurement sets for attribute [" + attr.getName() + "]: "
                    + ex.getMessage();

            logger.warn(msg);
            throw new Exception(msg, ex);
        }

        for (MeasurementSet mSet : mSets) {
            //            logger.debug("Adding measurement set [" + mSet.getID().toString() + "] to attribute " + attr.getUUID().toString());
            if (mSet.getMetric() == null) {
                logger.warn("Metric for measurement set [" + mSet.getID().toString() + "] is NULL");
            } else {
                if (mSet.getMetric().getMetricType() == null) {
                    logger.warn("Metric type for measurement set [" + mSet.getID().toString() + "] is NULL");
                } else {
                    if (mSet.getMetric().getUnit() == null) {
                        logger.warn("Metric unit for measurement set [" + mSet.getID().toString() + "] is NULL");
                    } else {
                        logger.debug(
                                "[" + mSet.getID().toString() + "] type: " + mSet.getMetric().getMetricType().name()
                                        + ", unit: " + mSet.getMetric().getUnit().getName());
                    }
                }

            }
        }

        return mSets;
    }

    /**
     * Use this method to retrieve MeasurementSets (WITHOUT measurement data)
     * for a specific attribute from a specific MetricGeneator in an experiment.
     *
     * @param mgen - MetricGenerator that may hold MeasurementSets for an
     * Attribute
     * @param attr - Target Attribute which may have measurements associated
     * with it
     * @return - returns a set of MeasurementSets for the specific
     * MetricGenerator (if they exist)
     * @throws Exception - throws if the parameters are invalid or the
     * MetricGenerator/Attribute could not be found
     */
    public Set<MeasurementSet> getEmptyMeasurementSetsForAttribute(MetricGenerator mgen, Attribute attr)
            throws Exception {

        // Safety first
        if (!started) {
            throw new Exception("Cannot get experiments: service not started");
        }
        if (mgen == null) {
            throw new Exception("Could not get MeasurementSets for Attribute: MetricGenerator is null");
        }
        if (attr == null) {
            throw new Exception("Could not get MeasurementSets for Attribute: Attribute is null");
        }

        UUID attrID = attr.getUUID();
        if (attrID == null) {
            throw new Exception("Could not get MeasurementSets for Attribute: Attribute ID is null");
        }

        // Create a result set, then try populating
        HashSet<MeasurementSet> resultSet = new HashSet<MeasurementSet>();

        try {
            Map<UUID, MeasurementSet> entityMSets = MetricHelper.getAllMeasurementSets(mgen);

            // If there are measurement sets associated with this entity, add those linked to the target entity
            for (MeasurementSet ms : entityMSets.values()) {

                if (ms != null) {
                    // Check set is mapped to attribute
                    UUID srcAttrID = ms.getAttributeID();

                    // Add it to the result set if good
                    if (srcAttrID != null && srcAttrID.equals(attrID)) {
                        resultSet.add(ms);
                    }
                } else {
                    logger.warn("Found NULL measurement set associated with attribute " + attr.getName());
                }
            }
        } catch (Exception ex) {
            String msg = "Had problems getting MeasurementSet for attribute " + attr.getName() + ": "
                    + ex.getMessage();
            logger.warn(msg);

            throw ex;
        }

        return resultSet;
    }

    /**
     * Use this method to retrieve MeasurementSets that are populated with
     * actual measurements (where they exist). Using this method requires that
     * you specify a start and end range within which measurements are sought.
     *
     * @param expID - Non-null experiment ID for the experiment the attribute
     * belongs to
     * @param attr - Non-null attribute for which measurements are sought
     * @param start - Non-null start date indicating measurements should have
     * been created on or after this point in time
     * @param end - Non-null end date indicating measurements should have been
     * created on or before this point in time
     * @return - Returns a (possibly empty) set of measurement sets
     * @throws Exception - throws if the input parameters are null or invalid
     */
    public Set<MeasurementSet> getMeasurementSetsForAttribute(UUID expID, Attribute attr, Date start, Date end)
            throws Exception {

        // Safety first
        if (!started) {
            throw new Exception("Cannot get experiments: service not started");
        }
        if (expID == null) {
            throw new Exception("Could not get Measurement Sets for Attribute: experiment ID is null");
        }
        if (attr == null) {
            throw new Exception("Could not get Measurement Sets for Attribute: Attribute is null");
        }
        if (start == null || end == null) {
            throw new Exception("Could not get Measurement Sets for Attribute: date(s) is null");
        }

        HashSet<MeasurementSet> resultSet = new HashSet<MeasurementSet>();

        try {
            // Get the MeasurementSet model first
            Set<MeasurementSet> msetInfo = getAllEmptyMeasurementSetsForAttribute(expID, attr);
            for (MeasurementSet ms : msetInfo) {
                // Then populate with data
                if (ms != null) {

                    Report report = expReportDAO.getReportForMeasurementsForTimePeriod(expID, start, end, true);

                    // Only add non-empty measurement sets
                    if (report.getNumberOfMeasurements() > 0) {
                        resultSet.add(report.getMeasurementSet());
                    }

                } else {
                    String msg = "Had problems retrieving a measurement set: MS ID is null";
                    logger.warn(msg);
                    throw new Exception(msg);
                }
            }
        } catch (Exception ex) {
            String msg = "Had problems retrieving measurement set data for attribute " + attr.getName() + ": "
                    + ex.getMessage();
            logger.warn(msg);

            throw new Exception(msg, ex);
        }

        return resultSet;
    }

    /**
     *
     * @param attributeId
     * @return
     */
    public EccMeasurementSet getAllMeasurementsForAttribute(String attributeId) {
        return getAllMeasurementsForAttribute(experimentService.getActiveExperiment().getUUID().toString(),
                attributeId);
    }

    public EccMeasurementSet getAllMeasurementsForAttribute(String experimentId, String attributeId) {

        EccMeasurementSet result = new EccMeasurementSet();
        ArrayList<EccMeasurement> data = new ArrayList<EccMeasurement>();
        result.setData(data);

        // TODO: make safe + convert to stream
        Attribute a = getAttribute(experimentId, attributeId);

        if (a == null) {
            return result;
        }

        try {
            Set<MeasurementSet> msetInfo = getAllEmptyMeasurementSetsForAttribute(UUID.fromString(experimentId), a);

            for (MeasurementSet ms : msetInfo) {
                Report rep = expReportDAO.getReportForAllMeasurements(ms.getID(), true);
                MeasurementSet repMS = rep.getMeasurementSet();

                for (Measurement m : repMS.getMeasurements()) {
                    data.add(new EccMeasurement(m.getTimeStamp(), m.getValue()));
                }

            }
        } catch (Exception e) {
            if (e instanceof NoDataException) {
                logger.debug(
                        "No data found for attribute [" + attributeId + "] in experiment [" + experimentId + "]");
            } else {
                logger.error("Failed to get data for attribute [" + attributeId + "] in experiment [" + experimentId
                        + "]", e);
            }
        }

        if (data.size() > 1) {
            Collections.sort(data, new EccMeasurementsComparator());
        }

        return result;
    }

    /**
     *
     * @param attributeId
     * @param limit
     * @return
     */
    public EccMeasurementSet getLatestMeasurementsForAttribute(String attributeId, int limit) {
        return getTailMeasurementsForAttribute(attributeId, (new Date().getTime()), limit);
    }

    /**
     *
     * @param attributeId the attribute.
     * @param since
     * @param limit
     * @return last 10 measurements for the attribute.
     */
    public EccMeasurementSet getTailMeasurementsForAttribute(String attributeId, Long since, int limit) {
        EccMeasurementSet result = new EccMeasurementSet();
        ArrayList<EccMeasurement> data = new ArrayList<>();
        result.setData(data);

        Experiment currentExperiment = experimentService.getActiveExperiment();

        if (currentExperiment != null) {
            try {
                Attribute attr = MetricHelper.getAttributeFromGenerators(UUID.fromString(attributeId),
                        metricGenDAO.getMetricGeneratorsForExperiment(currentExperiment.getUUID(), true));
                Set<MeasurementSet> measurementSets = getTailMeasurementSetsForAttribute(
                        currentExperiment.getUUID(), attr, new Date(since), limit);
                Iterator<MeasurementSet> it = measurementSets.iterator();
                MeasurementSet ms;
                while (it.hasNext()) {
                    ms = it.next();
                    logger.debug("Processing measurement set [" + ms.getID().toString() + "] to attribute "
                            + attr.getUUID().toString());
                    if (ms.getMetric() == null) {
                        logger.warn("Metric for measurement set [" + ms.getID().toString() + "] is NULL");
                    } else {
                        if (ms.getMetric().getMetricType() == null) {
                            logger.warn("Metric type for measurement set [" + ms.getID().toString() + "] is NULL");
                        } else {
                            if (ms.getMetric().getUnit() == null) {
                                logger.warn(
                                        "Metric unit for measurement set [" + ms.getID().toString() + "] is NULL");
                            } else {
                                logger.debug("Adding [" + ms.getID().toString() + "] type: "
                                        + ms.getMetric().getMetricType().name() + ", unit: "
                                        + ms.getMetric().getUnit().getName());
                                result.setType(ms.getMetric().getMetricType().name());
                                result.setUnit(ms.getMetric().getUnit().getName());
                                for (Measurement m : ms.getMeasurements()) {
                                    data.add(new EccMeasurement(m.getTimeStamp(), m.getValue()));
                                }
                            }
                        }

                    }

                }
            } catch (Exception e) {
                if (e instanceof NoDataException) {
                    logger.debug("No measurements for attribute [" + attributeId + "] before " + since);
                } else {
                    logger.error("Failed to retrieve data for attribute [" + attributeId + "]", e);
                }
            }

        } else {
            logger.warn("Data requested on current experiment which is NULL");
        }

        // Sort by time stamps, add timestamp
        if (result.getData().size() > 1) {
            Collections.sort(result.getData(), new EccMeasurementsComparator());

        }

        if (result.getData().size() > 0) {
            result.setTimestamp(ISODateTimeFormat.dateTime()
                    .print(result.getData().get(result.getData().size() - 1).getTimestamp().getTime()));
        }

        return result;
    }

    /**
     *
     * @param attributeId the attribute.
     * @param since
     * @param limit
     * @return latest 10 measurements for the attribute since a moment in time.
     */
    public EccMeasurementSet getLatestSinceMeasurementsForAttribute(String attributeId, Long since, int limit) {
        EccMeasurementSet result = new EccMeasurementSet();
        ArrayList<EccMeasurement> data = new ArrayList<EccMeasurement>();
        result.setData(data);

        Experiment currentExperiment = experimentService.getActiveExperiment();

        if (currentExperiment != null) {
            try {
                Attribute attr = MetricHelper.getAttributeFromGenerators(UUID.fromString(attributeId),
                        metricGenDAO.getMetricGeneratorsForExperiment(currentExperiment.getUUID(), true));
                Set<MeasurementSet> measurementSets = getSinceMeasurementSetsForAttribute(
                        currentExperiment.getUUID(), attr, new Date(since), limit);
                Iterator<MeasurementSet> it = measurementSets.iterator();
                MeasurementSet ms;
                while (it.hasNext()) {
                    ms = it.next();
                    logger.debug("Processing measurement set [" + ms.getID().toString() + "] to attribute "
                            + attr.getUUID().toString());
                    if (ms.getMetric() == null) {
                        logger.warn("Metric for measurement set [" + ms.getID().toString() + "] is NULL");
                    } else {
                        if (ms.getMetric().getMetricType() == null) {
                            logger.warn("Metric type for measurement set [" + ms.getID().toString() + "] is NULL");
                        } else {
                            if (ms.getMetric().getUnit() == null) {
                                logger.warn(
                                        "Metric unit for measurement set [" + ms.getID().toString() + "] is NULL");
                            } else {
                                logger.debug("Adding [" + ms.getID().toString() + "] type: "
                                        + ms.getMetric().getMetricType().name() + ", unit: "
                                        + ms.getMetric().getUnit().getName());
                                result.setType(ms.getMetric().getMetricType().name());
                                result.setUnit(ms.getMetric().getUnit().getName());
                                for (Measurement m : ms.getMeasurements()) {
                                    if (!m.getTimeStamp().equals(new Date(since))) {
                                        data.add(new EccMeasurement(m.getTimeStamp(), m.getValue()));
                                    }
                                }
                            }
                        }

                    }

                }
            } catch (Exception e) {
                logger.error("Failed to retrieve data for attribute [" + attributeId + "]", e);
            }

        } else {
            logger.warn("Data requested on current experiment which is NULL");
        }

        // TODO: make this a database operation!
        // Sort by time stamps
        int resultSize = result.getData().size();
        if (resultSize > 1) {
            // reverse sort
            Collections.sort(result.getData(), Collections.reverseOrder(new EccMeasurementsComparator()));

            // select latest 'limit' measurements
            ArrayList<EccMeasurement> tempData = new ArrayList<EccMeasurement>(
                    result.getData().subList(0, limit > resultSize ? resultSize : limit));

            // sort again
            Collections.sort(tempData, new EccMeasurementsComparator());

            // reset to new data
            result.setData(tempData);
        }

        if (resultSize > 0) {
            // set timestamp
            result.setTimestamp(ISODateTimeFormat.dateTime()
                    .print(result.getData().get(result.getData().size() - 1).getTimestamp().getTime()));
        }

        return result;
    }

    /**
     * Use this method to retrieve historical measurements (if they exist) from
     * a specific point in time. Use the count parameter to specific the maximum
     * number of measurements you want returned for any Measurement Set
     * discovered.
     *
     * @param expID - Non-null ID of the experiment
     * @param attr - Non-null Attribute of interest
     * @param beforeThisDate - Non-null time stamp from which to work backwards
     * from
     * @param count - Greater than zero maximum number of measurements per
     * measurement set
     * @return - Returns a collection of Measurement Sets
     * @throws Exception - Throws if parameters are invalid or there were
     * problems retrieving data from the database
     */
    public Set<MeasurementSet> getTailMeasurementSetsForAttribute(UUID expID, Attribute attr, Date beforeThisDate,
            int count) {

        // Safety first
        if (!started) {
            throw new IllegalStateException("Cannot get experiments: service not started");
        }
        if (expID == null) {
            throw new IllegalArgumentException(
                    "Could not get tail Measurement Sets for Attribute: experiment ID is null");
        }
        if (attr == null) {
            throw new IllegalArgumentException(
                    "Could not get tail Measurement Sets for Attribute: Attribute is null");
        }
        if (beforeThisDate == null) {
            throw new IllegalArgumentException("Could not get tail Measurement Sets for Attribute: Date is null");
        }
        if (count < 1) {
            throw new IllegalArgumentException(
                    "Could not get tail Measurement Sets for Attribute: date(s) is null");
        }

        logger.debug("Returning " + count + " data points BACK since '" + beforeThisDate.toString()
                + "' for attribute [" + attr.getUUID().toString() + "] of experiment [" + expID.toString() + "]");

        HashSet<MeasurementSet> resultSet = new HashSet<MeasurementSet>();

        // Get the MeasurementSet model first
        Set<MeasurementSet> msetInfo;
        try {
            msetInfo = getAllEmptyMeasurementSetsForAttribute(expID, attr);
        } catch (Exception e) {
            logger.error(
                    "Failed to return All Empty MeasurementSets For Attribute [" + attr.getUUID().toString() + "]",
                    e);
            return resultSet;
        }

        for (MeasurementSet ms : msetInfo) {
            // Then populate with data
            if (ms != null) {
                try {
                    Report report = expReportDAO.getReportForTailMeasurements(ms.getID(), beforeThisDate, count,
                            true);
                    // Only add non-empty measurement sets
                    MeasurementSet tempMs;
                    if (report.getNumberOfMeasurements() > 0) {
                        tempMs = report.getMeasurementSet();
                        if (ms.getMetric() == null) {
                            logger.warn("Metric for measurement set [" + ms.getID().toString() + "] is NULL");
                        } else {
                            if (ms.getMetric().getMetricType() == null) {
                                logger.warn(
                                        "Metric type for measurement set [" + ms.getID().toString() + "] is NULL");
                            } else {
                                if (ms.getMetric().getUnit() == null) {
                                    logger.warn("Metric unit for measurement set [" + ms.getID().toString()
                                            + "] is NULL");
                                } else {
                                    logger.debug("Adding INITIAL MS [" + ms.getID().toString() + "] type: "
                                            + ms.getMetric().getMetricType().name() + ", unit: "
                                            + ms.getMetric().getUnit().getName());

                                }
                            }

                        }
                        if (tempMs.getMetric() == null) {
                            logger.warn("Metric for measurement set [" + tempMs.getID().toString() + "] is NULL");
                            tempMs.setMetric(ms.getMetric());
                        } else {
                            if (tempMs.getMetric().getMetricType() == null) {
                                logger.warn("Metric type for measurement set [" + tempMs.getID().toString()
                                        + "] is NULL");
                            } else {
                                if (tempMs.getMetric().getUnit() == null) {
                                    logger.warn("Metric unit for measurement set [" + tempMs.getID().toString()
                                            + "] is NULL");
                                } else {
                                    logger.debug("Adding REPORTED MS [" + tempMs.getID().toString() + "] type: "
                                            + tempMs.getMetric().getMetricType().name() + ", unit: "
                                            + tempMs.getMetric().getUnit().getName());

                                }
                            }

                        }

                        resultSet.add(tempMs);
                    }
                } catch (Exception e) {
                    if (e instanceof NoDataException) {
                        logger.debug("No measurements for attribute [" + attr.getUUID().toString() + "] before "
                                + beforeThisDate);
                    } else {
                        logger.error("Failed to retrieve data for attribute [" + attr.getUUID().toString() + "]",
                                e);
                    }
                    break;
                }
            } else {
                logger.warn("Failed to retrieve measurement set: MS ID is NULL");
            }
        }
        return resultSet;
    }

    public Set<MeasurementSet> getSinceMeasurementSetsForAttribute(UUID expID, Attribute attr, Date sinceThisDate,
            int count) {

        // Safety first
        if (!started) {
            throw new IllegalStateException("Cannot get experiments: service not started");
        }
        if (expID == null) {
            throw new IllegalArgumentException(
                    "Could not get since Measurement Sets for Attribute: experiment ID is null");
        }
        if (attr == null) {
            throw new IllegalArgumentException(
                    "Could not get since Measurement Sets for Attribute: Attribute is null");
        }
        if (sinceThisDate == null) {
            throw new IllegalArgumentException("Could not get since Measurement Sets for Attribute: Date is null");
        }
        if (count < 1) {
            throw new IllegalArgumentException(
                    "Could not get since Measurement Sets for Attribute: date(s) is null");
        }

        logger.debug("Returning " + count + " data points FORWARD since '" + sinceThisDate.toString()
                + "' for attribute [" + attr.getUUID().toString() + "] of experiment [" + expID.toString() + "]");

        HashSet<MeasurementSet> resultSet = new HashSet<MeasurementSet>();

        // Get the MeasurementSet model first
        Set<MeasurementSet> msetInfo;
        try {
            msetInfo = getAllEmptyMeasurementSetsForAttribute(expID, attr);
        } catch (Exception e) {
            logger.error(
                    "Failed to return All Empty MeasurementSets For Attribute [" + attr.getUUID().toString() + "]",
                    e);
            return resultSet;
        }

        for (MeasurementSet ms : msetInfo) {
            // Then populate with data
            if (ms != null) {
                try {
                    Report report = expReportDAO.getReportForMeasurementsFromDate(ms.getID(), sinceThisDate, true);
                    // Only add non-empty measurement sets
                    MeasurementSet tempMs;
                    if (report.getNumberOfMeasurements() > 0) {
                        tempMs = report.getMeasurementSet();
                        if (ms.getMetric() == null) {
                            logger.warn("Metric for measurement set [" + ms.getID().toString() + "] is NULL");
                        } else {
                            if (ms.getMetric().getMetricType() == null) {
                                logger.warn(
                                        "Metric type for measurement set [" + ms.getID().toString() + "] is NULL");
                            } else {
                                if (ms.getMetric().getUnit() == null) {
                                    logger.warn("Metric unit for measurement set [" + ms.getID().toString()
                                            + "] is NULL");
                                } else {
                                    logger.debug("Adding INITIAL MS [" + ms.getID().toString() + "] type: "
                                            + ms.getMetric().getMetricType().name() + ", unit: "
                                            + ms.getMetric().getUnit().getName());

                                }
                            }

                        }
                        if (tempMs.getMetric() == null) {
                            logger.warn("Metric for measurement set [" + tempMs.getID().toString()
                                    + "] is NULL, fixing");
                            tempMs.setMetric(ms.getMetric());
                        } else {
                            if (tempMs.getMetric().getMetricType() == null) {
                                logger.warn("Metric type for measurement set [" + tempMs.getID().toString()
                                        + "] is NULL");
                            } else {
                                if (tempMs.getMetric().getUnit() == null) {
                                    logger.warn("Metric unit for measurement set [" + tempMs.getID().toString()
                                            + "] is NULL");
                                } else {
                                    logger.debug("Adding REPORTED MS [" + tempMs.getID().toString() + "] type: "
                                            + tempMs.getMetric().getMetricType().name() + ", unit: "
                                            + tempMs.getMetric().getUnit().getName());

                                }
                            }

                        }

                        resultSet.add(tempMs);
                    }
                } catch (Exception e) {
                    if (e instanceof NoDataException) {
                        logger.debug("No measurements for attribute [" + attr.getUUID().toString() + "] since "
                                + sinceThisDate);
                    } else {
                        logger.error("Failed to retrieve data for attribute [" + attr.getUUID().toString() + "]",
                                e);
                    }
                    break;
                }
            } else {
                logger.warn("Failed to retrieve measurement set: MS ID is NULL");
            }
        }
        return resultSet;
    }

    public ArrayList<Experiment> getAllExperiments(boolean sortedByDateCreated, boolean withMetricModels) {

        ArrayList<Experiment> resultSet = new ArrayList<Experiment>();

        // Safety first
        if (started) {
            try {
                Collection<Experiment> experiments = experimentDAO.getExperiments(withMetricModels);

                // Sort these by creation date, if required
                if (sortedByDateCreated) {

                    // Sort experiments
                    TreeMap<Date, Experiment> sortedExps = new TreeMap<Date, Experiment>();
                    for (Experiment exp : experiments) {
                        sortedExps.put(exp.getStartTime(), exp);
                    }

                    // Add experiments linearly, most recent first
                    Iterator<Date> dateIt = sortedExps.descendingKeySet().descendingIterator();
                    while (dateIt.hasNext()) {
                        resultSet.add(sortedExps.get(dateIt.next()));
                    }
                } else {
                    resultSet.addAll(experiments);
                }

            } catch (Exception ex) {
                logger.warn("Could not retrieve experiments: " + ex.getMessage());
            }
        } else {
            logger.warn("Cannot get experiments: service not started");
        }

        return resultSet;

    }

    public Experiment getExperiment(String experimentUuid, boolean withMetricModels) {

        // Safety first
        if (!started) {
            logger.error("Failed to get experiment: service not started");
            return null;
        }
        if (experimentUuid == null) {
            logger.error("Failed to get experiment: ID is null");
            return null;
        }

        Experiment experiment = null;

        try {
            experiment = experimentDAO.getExperiment(UUID.fromString(experimentUuid), withMetricModels);
        } catch (Exception ex) {
            logger.error("Could not retrieve experiment: " + ex.getMessage());
        }

        return experiment;

    }

    public Experiment getCurrentExperiment(boolean withMetricModels) {

        Experiment targetExperiment = null;

        if (started && experimentService != null) {
            if (experimentService.isExperimentInProgress()) {

                // Get high-level experiment meta-data
                targetExperiment = experimentService.getActiveExperiment();

                // If we want more details, query the database
                if (withMetricModels) {
                    try {
                        targetExperiment = experimentDAO.getExperiment(targetExperiment.getUUID(), true);
                    } catch (Exception ex) {
                        logger.error("Could not retrieve experiment from database: " + ex.getMessage());
                    }
                }
            } else {
                logger.error("Could not return current experiment: no experiment in progress");
            }
        } else {
            logger.error("Could not return current experiment: service(s) not started");
        }

        return targetExperiment;
    }

    public ArrayList<EccClient> getEccClientsForCurrentExperiment() {

        ArrayList<EccClient> currentClients = new ArrayList<EccClient>();

        if (started && experimentService != null) {
            if (experimentService.isExperimentInProgress()) {

                Set<EMClient> currClients = experimentService.getAllKnownClients();

                for (EMClient client : currClients) {
                    EccClient ec = new EccClient(client.getID().toString(), client.getName(), client.isConnected());
                    currentClients.add(ec);
                }
            } else {
                logger.error("Could not return clients for current experiment: no experiment in progress");
            }
        } else {
            logger.error("Could not return clients for current experiment: service(s) not started");
        }

        return currentClients;
    }

    public EccEntity getEccEntity(String uuid, boolean withAttributes) {

        // Safety
        if (uuid == null) {
            logger.error("Failed to return entity, requested id is NULL");
            return null;
        }

        if (!started) {
            logger.error("Failed to return entity with id [" + uuid + "]: data service not yet started");
            return null;
        } else {
            try {
                Entity entity = entityDAO.getEntity(UUID.fromString(uuid), withAttributes);

                // Convert to domain class
                return toEccEntity(entity, withAttributes);

            } catch (Exception e) {
                logger.error("Failed to return entity with id [" + uuid + "]", e);
                return null;
            }
        }
    }

    public ArrayList<EccEntity> getEntitiesForExperiment(String experimentUuid, boolean withAttributes) {

        ArrayList<EccEntity> eccEntities = new ArrayList<EccEntity>();

        if (started) {
            try {
                Set<Entity> entities = entityDAO.getEntitiesForExperiment(UUID.fromString(experimentUuid),
                        withAttributes);
                for (Entity entity : entities) {
                    eccEntities.add(toEccEntity(entity, withAttributes));
                }

            } catch (Exception ex) {
                logger.warn("Could not get entities for experiment (" + experimentUuid + "): " + ex.getMessage());
            }
        } else {
            logger.error("Could not get entities for experiment: Data Service not started");
        }

        if (eccEntities.size() > 1) {
            Collections.sort(eccEntities, new EccEntitiesComparator());
        }

        return eccEntities;
    }

    public ArrayList<EccEntity> getEntitiesForClient(String clientUuid, boolean withAttributes) {

        ArrayList<EccEntity> clientEntities = new ArrayList<EccEntity>();

        if (started && experimentService != null) {
            if (experimentService.isExperimentInProgress()) {

                UUID targetID = UUID.fromString(clientUuid);

                if (targetID != null) {

                    Set<EMClient> currClients = experimentService.getAllKnownClients();

                    for (EMClient client : currClients) {

                        // Use extended client type to get all metric metric generators
                        EMClientEx clientEx = (EMClientEx) client;

                        if (clientEx.getID().equals(targetID)) {
                            for (Entity entity : clientEx.getCopyOfUniqueHistoricEntities()) {

                                EccEntity ent = toEccEntity(entity, withAttributes);

                                if (ent != null) {
                                    clientEntities.add(ent);
                                }
                            }
                        }
                    }
                } else {
                    logger.error("Could not get client entities for current experiment: client ID is invalid");
                }
            } else {
                logger.error("Could not get client entities for current experiment: no experiment in progress");
            }
        } else {
            logger.error("Could not get client entities for current experiment: service(s) not started");
        }

        if (clientEntities.size() > 1) {
            Collections.sort(clientEntities, new EccEntitiesComparator());
        }

        return clientEntities;
    }

    public EccAttribute getEccAttribute(String uuid) {

        EccAttribute eccAttr = null;

        if (uuid == null) {
            logger.error("Failed to return attribute, requested id is NULL");
            return null;
        }

        if (!started) {
            logger.error("Failed to return attribute with id [" + uuid + "]: data service not yet started");
            return null;
        }

        try {
            Attribute attr = entityDAO.getAttribute(UUID.fromString(uuid));

            if (attr != null) {
                eccAttr = toEccAttribute(attr);
            } else {
                logger.error("Failed to return attribute with id[" + uuid + "] - it does not exist");
            }

        } catch (Exception ex) {
            logger.error("Failed to return attribute with id [" + uuid + "]", ex.getMessage());
        }

        return eccAttr;
    }

    public ArrayList<EccAttribute> getAttributesForExperiment(String experimentUuid) {

        // Safety
        if (!started) {
            logger.error("Could not retrieve attributes for experiment: service not started");
            return null;
        }

        if (experimentUuid == null) {
            logger.error("Could not retrieve attributes for experiment: UUID value is null");
            return null;
        }

        ArrayList<EccAttribute> resultSet = new ArrayList<EccAttribute>();

        // Get all entities for this experiment
        try {
            Collection<Entity> entities = entityDAO.getEntitiesForExperiment(UUID.fromString(experimentUuid), true);

            // And add their attributes (none should be shared by Entities)
            EccAttribute eccAttr;
            for (Entity entity : entities) {
                for (Attribute attr : entity.getAttributes()) {

                    eccAttr = toEccAttribute(attr);

                    if (eccAttr != null) {
                        resultSet.add(eccAttr);
                    }
                }
            }
        } catch (Exception ex) {
            String msg = "Could not retrieve attributes for experiment " + experimentUuid + " " + ex.getMessage();
            logger.error(msg);
        }

        return resultSet;
    }

    // Returns 'limit' measurements starting from the 'dateInMsec' into the past
    public Set<EccMeasurementSet> getMeasurementsForAttributeAfter(String experimentUuid, String attributeUuid,
            long dateInMsec, int limit) {

        // Safety
        if (!attrSearchParamsValid(experimentUuid, attributeUuid, dateInMsec, limit)) {
            logger.error("Could not get measurements for attribute: input parameter(s) invalid");
            return null;
        }

        UUID expID = UUID.fromString(experimentUuid);
        UUID attrID = UUID.fromString(attributeUuid);
        HashSet<EccMeasurementSet> resultSet = null;

        try {
            Set<MeasurementSet> mSets = msetDAO.getMeasurementSetsForAttribute(attrID, expID, true);

            resultSet = new HashSet<EccMeasurementSet>();

            for (MeasurementSet ms : mSets) {

                // Get measurements within time frame
                Date start = new Date(dateInMsec);
                Date end = new Date();
                Report report = expReportDAO.getReportForMeasurementsForTimePeriod(ms.getID(), start, end, true);

                Set<Measurement> mSet = report.getMeasurementSet().getMeasurements();

                if (!mSet.isEmpty()) {

                    // Sort measurements
                    List<Measurement> measurements = MetricHelper.sortMeasurementsByDateLinear(mSet);

                    // Truncate measuements and create domain object if we have data
                    if (measurements.size() > 0) {
                        measurements = MetricHelper.truncateMeasurements(measurements, limit, true);

                        // Create domain class
                        EccMeasurementSet ems = createMSDomainObject(ms, measurements);
                        resultSet.add(ems);
                    }
                }
            }
        } catch (Exception ex) {
            logger.error("Could not retrieve measurements for Attribute after date: " + ex.getMessage());
        }

        return resultSet;
    }

    // Returns 'limit' measurements starting from now until 'dateInMsec' in the past excluding dateInMsec
    public Set<EccMeasurementSet> getMeasurementsForAttributeBefore(String experimentUuid, String attributeUuid,
            long dateInMsec, int limit) {

        // Safety
        if (experimentUuid == null || attributeUuid == null || dateInMsec < 0 || limit < 1) {
            logger.error("Could not get measurements for attribute before date: input parameter(s) invalid");
            return null;
        }

        UUID expID = UUID.fromString(experimentUuid);
        UUID attrID = UUID.fromString(attributeUuid);
        HashSet<EccMeasurementSet> resultSet = null;

        try {
            Set<MeasurementSet> mSets = msetDAO.getMeasurementSetsForAttribute(attrID, expID, true);

            resultSet = new HashSet<EccMeasurementSet>();

            for (MeasurementSet ms : mSets) {

                // Get measurements...
                Report report = expReportDAO.getReportForTailMeasurements(ms.getID(), new Date(dateInMsec), limit,
                        true);

                Set<Measurement> mSet = report.getMeasurementSet().getMeasurements();

                if (!mSet.isEmpty()) {

                    // Sort measurements
                    List<Measurement> measurements = MetricHelper.sortMeasurementsByDateLinear(mSet);

                    // Don't need to truncate; report already truncated
                    // Create domain class
                    resultSet.add(createMSDomainObject(ms, measurements));
                }
            }
        } catch (Exception ex) {
            logger.error("Could not retrieve measurements for Attribute after date: " + ex.getMessage());
        }

        return resultSet;
    }

    // Returns 'limit' measurements starting from the 'dateInMsec' into the past in <value, number of times the value has occurred>
    public EccCounterMeasurementSet getCounterMeasurementsForAttributeAfter(String experimentUuid,
            String attributeUuid, long dateInMsec, int limit) {

        // Safety
        if (experimentUuid == null || attributeUuid == null || dateInMsec < 0 || limit < 1) {
            logger.error("Could not get measurements for attribute before date: input parameter(s) invalid");
            return null;
        }

        UUID expID = UUID.fromString(experimentUuid);
        UUID attrID = UUID.fromString(attributeUuid);
        EccCounterMeasurementSet result = new EccCounterMeasurementSet();
        ArrayList<EccCounterMeasurement> data = new ArrayList<EccCounterMeasurement>();
        result.setData(data);

        // TODO: get from measurement sets below (not sure if Metric is NULL below)
        result.setType("NOMINAL");
        result.setUnit("");

        try {
            Set<MeasurementSet> mSets = msetDAO.getMeasurementSetsForAttribute(attrID, expID, true);

            Set<Measurement> allMeasurements = new HashSet<Measurement>();
            for (MeasurementSet ms : mSets) {

                // Get measurements from 0 to dateInMsec
                Report report = expReportDAO.getReportForMeasurementsForTimePeriod(ms.getID(), new Date(0),
                        new Date(dateInMsec), true);

                allMeasurements.addAll(report.getMeasurementSet().getMeasurements());
            }

            if (!allMeasurements.isEmpty()) {

                // find most recent
                Date mostRecent = allMeasurements.iterator().next().getTimeStamp(), temp;
                for (Measurement m : allMeasurements) {
                    temp = m.getTimeStamp();
                    if (temp.after(mostRecent)) {
                        mostRecent = temp;
                    }
                }
                result.setTimestamp(ISODateTimeFormat.dateTime().print(mostRecent.getTime()));

                Map<String, Integer> freqMap = MetricCalculator.countValueFrequencies(allMeasurements);

                for (String key : freqMap.keySet()) {
                    data.add(new EccCounterMeasurement(key, freqMap.get(key)));
                }

            }
        } catch (Exception ex) {
            logger.error("Could not retrieve measurements for Attribute after date: " + ex.getMessage());
        }

        return result;
    }

    // Returns 'limit' measurements starting from now until 'dateInMsec' in the past in <value, number of times the value has occurred> excluding dateInMsec
    public EccCounterMeasurementSet getCounterMeasurementsForAttributeBeforeAndExcluding(String experimentUuid,
            String attributeUuid, long dateInMsec, int limit) {

        // Safety
        if (experimentUuid == null || attributeUuid == null || dateInMsec < 0 || limit < 1) {
            logger.error("Could not get counter measurements for attribute after date: input parameter(s) invalid");
            return null;
        }

        UUID expID = UUID.fromString(experimentUuid);
        UUID attrID = UUID.fromString(attributeUuid);
        EccCounterMeasurementSet result = new EccCounterMeasurementSet();
        Date start = new Date(dateInMsec);
        Date end = new Date();

        try {
            Set<MeasurementSet> mSets = msetDAO.getMeasurementSetsForAttribute(attrID, expID, true);

            Set<Measurement> allMeasurements = new HashSet<Measurement>();
            ArrayList<EccCounterMeasurement> data = new ArrayList<EccCounterMeasurement>();
            result.setData(data);

            // TODO: get from measurement sets below (not sure if Metric is NULL below)
            result.setType("NOMINAL");
            result.setUnit("");

            for (MeasurementSet ms : mSets) {
                // Get measurements within time frame
                Report report = expReportDAO.getReportForMeasurementsForTimePeriod(ms.getID(), start, end, true);

                // TODO: optimise
                for (Measurement m : report.getMeasurementSet().getMeasurements()) {
                    if (!m.getTimeStamp().equals(start)) {
                        allMeasurements.add(m);
                    }
                }
            }

            if (!allMeasurements.isEmpty()) {

                // find most recent
                Date mostRecent = allMeasurements.iterator().next().getTimeStamp(), temp;
                for (Measurement m : allMeasurements) {
                    temp = m.getTimeStamp();
                    if (temp.after(mostRecent)) {
                        mostRecent = temp;
                    }
                }
                result.setTimestamp(ISODateTimeFormat.dateTime().print(mostRecent.getTime()));

                Map<String, Integer> freqMap = MetricCalculator.countValueFrequencies(allMeasurements);

                for (String key : freqMap.keySet()) {
                    data.add(new EccCounterMeasurement(key, freqMap.get(key)));
                }

            }

        } catch (Exception ex) {
            logger.error("Could not retrieve measurements for Attribute after date: " + ex.getMessage());
        }

        return result;
    }

    // Private methods ---------------------------------------------------------
    /**
     * This method provides a best guess at the attribute's measurement metrics
     * - these are actually separately stored in measurement sets. Most ECC
     * clients independently declare their entities so in the majority of cases
     * this will return the metric type that is expected.
     *
     * @param attr - Attribute of interest
     * @return - Measurement Set representing observations for the attribute
     */
    private MeasurementSet getBestGuessMeasurementSet(Attribute attr) {

        MeasurementSet bestMS = null;
        Set<MeasurementSet> mSets = null;

        if (attr != null && msetDAO != null) {

            try {
                mSets = msetDAO.getMeasurementSetsForAttribute(attr.getUUID(), true);
            } catch (Exception ex) {
                logger.warn("Failed to retrieve measurement sets for attribute " + attr.getName(), ex);
            }
        }

        if (mSets != null) {
            if (mSets.isEmpty()) {
                logger.warn("Could not find any measurement sets for attribute " + attr.getName());
            }

            // Take the first measurement set
            bestMS = mSets.iterator().next();
        }

        return bestMS;
    }

    private EccAttribute toEccAttribute(Attribute attr) {

        EccAttribute eccAttr = null;

        if (attr != null) {

            MeasurementSet ms = getBestGuessMeasurementSet(attr);

            if (ms != null) {

                Metric met = ms.getMetric();
                if (met != null) {
                    eccAttr = new EccAttribute(attr.getName(), attr.getDescription(), attr.getUUID(),
                            attr.getEntityUUID(), met.getMetricType().name(), met.getUnit().getName());
                }
            }
        }

        return eccAttr;
    }

    private EccEntity toEccEntity(Entity entity, boolean withAttrs) {

        ArrayList<EccAttribute> domAttrs = new ArrayList<EccAttribute>();

        EccEntity eccEnt = new EccEntity(entity.getName(), entity.getDescription(), entity.getUUID(), domAttrs);

        // Add attributes, if required
        if (withAttrs) {

            Set<Attribute> attributes = entity.getAttributes();

            if (attributes != null) {

                for (Attribute attr : attributes) {
                    EccAttribute ea = toEccAttribute(attr);

                    if (ea != null) {
                        domAttrs.add(toEccAttribute(attr));
                    }
                }
            }
        }

        if (domAttrs.size() > 1) {
            Collections.sort(domAttrs, new EccAttributesComparator());
        }

        return eccEnt;
    }

    private boolean attrSearchParamsValid(String experimentUuid, String attributeUuid, long dateInMsec, int limit) {

        if (experimentUuid == null || attributeUuid == null || dateInMsec < 1 || limit < 1) {
            return false;
        }

        UUID expID = UUID.fromString(experimentUuid);
        UUID attrID = UUID.fromString(attributeUuid);

        if (expID == null || attrID == null) {
            return false;
        }

        return true;
    }

    private EccMeasurementSet createMSDomainObject(MeasurementSet srcMS, List<Measurement> measures) {

        EccMeasurementSet result = null;

        // Re-create data
        ArrayList<EccMeasurement> targetMeasures = new ArrayList<EccMeasurement>();
        for (Measurement m : measures) {
            targetMeasures.add(new EccMeasurement(m.getTimeStamp(), m.getValue()));
        }

        // Attach metric meta-dat
        Metric met = srcMS.getMetric();

        result = new EccMeasurementSet(met.getUnit().getName(), met.getMetricType().name(), targetMeasures);

        return result;
    }

    private EccCounterMeasurementSet createCounterMSDomainObject(MeasurementSet srcMs,
            Map<String, Integer> freqMap) {

        Metric met = srcMs.getMetric();

        // Create counter measurement set
        EccCounterMeasurementSet ecms = new EccCounterMeasurementSet();
        ecms.setUnit(met.getUnit().getName());
        ecms.setType(met.getMetricType().name());

        // Populate the set
        ArrayList<EccCounterMeasurement> freqCounts = new ArrayList<EccCounterMeasurement>();
        ecms.setData(freqCounts);

        for (String key : freqMap.keySet()) {
            freqCounts.add(new EccCounterMeasurement(key, freqMap.get(key)));
        }

        return ecms;
    }
}