Package com.serotonin.m2m2.reports.web

Source Code of com.serotonin.m2m2.reports.web.ReportChartCreator

/*
    Copyright (C) 2014 Infinite Automation Systems Inc. All rights reserved.
    @author Matthew Lohbihler
*/
package com.serotonin.m2m2.reports.web;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Stroke;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jfree.data.time.TimeSeries;

import com.serotonin.InvalidArgumentException;
import com.serotonin.ShouldNeverHappenException;
import com.serotonin.m2m2.Common;
import com.serotonin.m2m2.DataTypes;
import com.serotonin.m2m2.email.MessageFormatDirective;
import com.serotonin.m2m2.email.SubjectDirective;
import com.serotonin.m2m2.email.UsedImagesDirective;
import com.serotonin.m2m2.i18n.Translations;
import com.serotonin.m2m2.reports.ReportDao;
import com.serotonin.m2m2.reports.vo.ReportInstance;
import com.serotonin.m2m2.reports.vo.ReportVO;
import com.serotonin.m2m2.rt.dataImage.PointValueTime;
import com.serotonin.m2m2.rt.event.EventInstance;
import com.serotonin.m2m2.util.chart.DiscreteTimeSeries;
import com.serotonin.m2m2.util.chart.ImageChartUtils;
import com.serotonin.m2m2.util.chart.NumericTimeSeries;
import com.serotonin.m2m2.util.chart.PointTimeSeriesCollection;
import com.serotonin.m2m2.view.quantize.AbstractDataQuantizer;
import com.serotonin.m2m2.view.quantize.BinaryDataQuantizer;
import com.serotonin.m2m2.view.quantize.DiscreteTimeSeriesQuantizerCallback;
import com.serotonin.m2m2.view.quantize.MultistateDataQuantizer;
import com.serotonin.m2m2.view.quantize.NumericDataQuantizer;
import com.serotonin.m2m2.view.quantize.TimeSeriesQuantizerCallback;
import com.serotonin.m2m2.view.stats.AnalogStatistics;
import com.serotonin.m2m2.view.stats.StartsAndRuntime;
import com.serotonin.m2m2.view.stats.StartsAndRuntimeList;
import com.serotonin.m2m2.view.stats.StatisticsGenerator;
import com.serotonin.m2m2.view.stats.ValueChangeCounter;
import com.serotonin.m2m2.view.text.TextRenderer;
import com.serotonin.m2m2.vo.UserComment;
import com.serotonin.m2m2.vo.export.EventCsvStreamer;
import com.serotonin.m2m2.vo.export.ExportCsvStreamer;
import com.serotonin.m2m2.vo.export.ExportDataStreamHandler;
import com.serotonin.m2m2.vo.export.ExportDataValue;
import com.serotonin.m2m2.vo.export.ExportPointInfo;
import com.serotonin.m2m2.web.taglib.Functions;
import com.serotonin.util.ColorUtils;

import freemarker.template.Template;

/**
* @author Matthew Lohbihler
*/
public class ReportChartCreator {
    static final Log LOG = LogFactory.getLog(ReportChartCreator.class);

    private static final String IMAGE_SERVLET = "reportImageChart/";

    /**
     * This image width is specifically chosen such that the report will print on a single page width in landscape.
     */
    private static final int IMAGE_WIDTH = 930;
    private static final int IMAGE_HEIGHT = 400;
    public static final String IMAGE_CONTENT_ID = "reportChart.png";

    public static final int POINT_IMAGE_WIDTH = 440;
    public static final int POINT_IMAGE_HEIGHT = 250; // 340

    String inlinePrefix;
    private String html;
    private String subject;
    private List<String> inlineImageList;
    private byte[] imageData;
    private String chartName;
    private File exportFile;
    private File eventFile;
    private File commentFile;
    private List<PointStatistics> pointStatistics;
  private HashMap<String, HashMap<String, PointStatistics>> devices;
  private HashMap<String, PointStatistics> pointMap;

    final Translations translations;
    final TimeZone timeZone;

    public ReportChartCreator(Translations translations, TimeZone timeZone) {
        this.translations = translations;
        this.timeZone = timeZone;
    }

    /**
     * Uses the given parameters to create the data for the fields of this class. Once the content has been created the
     * getters for the fields can be used to retrieve.
     *
     * @param reportInstance
     * @param reportDao
     * @param inlinePrefix
     *            if this is non-null, it implies that the content should be inline.
     * @param createExportFile
     */
    public void createContent(ReportInstance reportInstance, ReportDao reportDao, String inlinePrefix,
            boolean createExportFile) {
        this.inlinePrefix = inlinePrefix;

        reportInstance.setTranslations(translations);

        // Use a stream handler to get the report data from the database.
        StreamHandler handler = new StreamHandler(reportInstance.getXidMap(), reportInstance.getReportStartTime(),
                reportInstance.getReportEndTime(), IMAGE_WIDTH, createExportFile, translations);
        // Process the report content with the handler.
        if(Common.databaseProxy.getNoSQLProxy() == null)
          reportDao.reportInstanceDataSQL(reportInstance.getId(), handler);
        else
          reportDao.reportInstanceDataNoSQL(reportInstance.getId(), handler);

        pointStatistics = handler.getPointStatistics();
    devices = handler.getDevices();
    pointMap = handler.getStatisticsMap();
        UsedImagesDirective inlineImages = new UsedImagesDirective();
        SubjectDirective subjectDirective = new SubjectDirective(translations);

        // Prepare the model for the content rendering.
        Map<String, Object> model = new HashMap<String, Object>();
        model.put("fmt", new MessageFormatDirective(translations));
        model.put("subject", subjectDirective);
        model.put("img", inlineImages);
        model.put("instance", reportInstance);
        model.put("timezone", timeZone.getID());
        model.put("points", pointStatistics);
        model.put("inline", inlinePrefix == null ? "" : "cid:");
    model.put("devices", devices);
    model.put("mapped", pointMap);

        model.put("ALPHANUMERIC", DataTypes.ALPHANUMERIC);
        model.put("BINARY", DataTypes.BINARY);
        model.put("MULTISTATE", DataTypes.MULTISTATE);
        model.put("NUMERIC", DataTypes.NUMERIC);
        model.put("IMAGE", DataTypes.IMAGE);

        // Create the individual point charts
        for (PointStatistics pointStat : pointStatistics) {
            PointTimeSeriesCollection ptsc = new PointTimeSeriesCollection(timeZone);

            if (pointStat.getNumericTimeSeries() != null)
                ptsc.addNumericTimeSeries(pointStat.getNumericTimeSeries().plainCopy());
            else if (pointStat.getDiscreteTimeSeries() != null)
                ptsc.addDiscreteTimeSeries(pointStat.getDiscreteTimeSeries().plainCopy());

            if (ptsc.hasData()) {
                if (inlinePrefix != null)
                    model.put("chartName", inlinePrefix + pointStat.getChartName());
                pointStat.setImageData(ImageChartUtils.getChartData(ptsc, POINT_IMAGE_WIDTH, POINT_IMAGE_HEIGHT,
                        reportInstance.getReportStartTime(), reportInstance.getReportEndTime()));
            }
        }

        PointTimeSeriesCollection ptsc = handler.getPointTimeSeriesCollection();
        if (ptsc.hasData()) {
            if (inlinePrefix != null)
                model.put("chartName", inlinePrefix + IMAGE_CONTENT_ID);
            else {
                chartName = "r" + reportInstance.getId() + ".png";
                // The path comes from the servlet path definition in web.xml.
                model.put("chartName", IMAGE_SERVLET + chartName);
            }

            imageData = ImageChartUtils.getChartData(ptsc, true, IMAGE_WIDTH, IMAGE_HEIGHT,
                    reportInstance.getReportStartTime(), reportInstance.getReportEndTime());
        }

        List<EventInstance> events = null;
        if (reportInstance.getIncludeEvents() != ReportVO.EVENTS_NONE) {
            events = reportDao.getReportInstanceEvents(reportInstance.getId());
            model.put("includeEvents", true);
            model.put("events", events);
        }
        else
            model.put("includeEvents", false);

        List<ReportUserComment> comments = null;
        if (reportInstance.isIncludeUserComments()) {
            comments = reportDao.getReportInstanceUserComments(reportInstance.getId());

            // Only provide the list of point comments to the report. The event comments have already be correlated
            // into the events list.
            List<ReportUserComment> pointComments = new ArrayList<ReportUserComment>();
            for (ReportUserComment c : comments) {
                if (c.getCommentType() == UserComment.TYPE_POINT)
                    pointComments.add(c);
            }

            model.put("includeUserComments", true);
            model.put("userComments", pointComments);
        }
        else
            model.put("includeUserComments", false);

        // Create the template.
        Template template;
        try {
            template = Common.freemarkerConfiguration.getTemplate(reportInstance.getTemplateFile());
        }
        catch (IOException e) {
            // Couldn't load the template?
            throw new ShouldNeverHappenException(e);
        }

        // Create the content from the template.
        StringWriter writer = new StringWriter();
        try {
            template.process(model, writer);
        }
        catch (Exception e) {
            // Couldn't process the template?
            throw new ShouldNeverHappenException(e);
        }

        // Save the content
        html = writer.toString();
        subject = subjectDirective.getSubject();
        inlineImageList = inlineImages.getImageList();

        // Save the export file (if any)
        exportFile = handler.exportFile;

        if (createExportFile && events != null) {
            try {
                eventFile = File.createTempFile("tempEventCSV", ".csv");
                new EventCsvStreamer(new PrintWriter(new FileWriter(eventFile)), events, translations);
            }
            catch (IOException e) {
                LOG.error("Failed to create temp event file", e);
            }
        }

        if (createExportFile && comments != null) {
            try {
                commentFile = File.createTempFile("tempCommentCSV", ".csv");
                new UserCommentCsvStreamer(new PrintWriter(new FileWriter(commentFile)), comments, translations);
            }
            catch (IOException e) {
                LOG.error("Failed to create temp comment file", e);
            }
        }
    }

    public String getHtml() {
        return html;
    }

    public String getSubject() {
        return subject;
    }

    public List<String> getInlineImageList() {
        return inlineImageList;
    }

    public String getChartName() {
        return chartName;
    }

    public byte[] getImageData() {
        return imageData;
    }

    public File getExportFile() {
        return exportFile;
    }

    public File getEventFile() {
        return eventFile;
    }

    public File getCommentFile() {
        return commentFile;
    }

    public List<PointStatistics> getPointStatistics() {
        return pointStatistics;
    }

    public class PointStatistics {
        private final int reportPointId;
        private String name;
    private String deviceName;
        private int dataType;
        private String dataTypeDescription;
        private String startValue;
        private TextRenderer textRenderer;
        private StatisticsGenerator stats;
        //private TimeSeries numericTimeSeries;
        //private Color numericTimeSeriesColor;
        private NumericTimeSeries numericTimeSeries;
        private DiscreteTimeSeries discreteTimeSeries;
        private byte[] imageData;

        public PointStatistics(int reportPointId) {
            this.reportPointId = reportPointId;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
   
    public String getDeviceName() {
      return deviceName;
    }
   
    public void setDeviceName(String deviceName) {
      this.deviceName = deviceName;
    }

        public int getDataType() {
            return dataType;
        }

        public void setDataType(int dataType) {
            this.dataType = dataType;
        }

        public String getDataTypeDescription() {
            return dataTypeDescription;
        }

        public void setDataTypeDescription(String dataTypeDescription) {
            this.dataTypeDescription = dataTypeDescription;
        }

        public String getStartValue() {
            return startValue;
        }

        public void setStartValue(String startValue) {
            this.startValue = startValue;
        }

        public StatisticsGenerator getStats() {
            return stats;
        }

        public void setStats(StatisticsGenerator stats) {
            this.stats = stats;
        }

        public TextRenderer getTextRenderer() {
            return textRenderer;
        }

        public void setTextRenderer(TextRenderer textRenderer) {
            this.textRenderer = textRenderer;
        }

        public NumericTimeSeries getNumericTimeSeries() {
            return numericTimeSeries;
        }

        public void setNumericTimeSeries(NumericTimeSeries numericTimeSeries) {
            this.numericTimeSeries = numericTimeSeries;
        }

        public DiscreteTimeSeries getDiscreteTimeSeries() {
            return discreteTimeSeries;
        }

        public void setDiscreteTimeSeries(DiscreteTimeSeries discreteTimeSeries) {
            this.discreteTimeSeries = discreteTimeSeries;
        }

        public byte[] getImageData() {
            return imageData;
        }

        public void setImageData(byte[] imageData) {
            this.imageData = imageData;
        }

        public String getAnalogMinimum() {
            Double d = ((AnalogStatistics) stats).getMinimumValue();
            if (d == null)
                return null;
            return textRenderer.getText(d, TextRenderer.HINT_SPECIFIC);
        }

        public String getAnalogMinTime() {
            Long l = ((AnalogStatistics) stats).getMinimumTime();
            if (l == null)
                return null;
            return Functions.getFullMinuteTime(l);
        }

        public String getAnalogMaximum() {
            Double d = ((AnalogStatistics) stats).getMaximumValue();
            if (d == null)
                return null;
            return textRenderer.getText(d, TextRenderer.HINT_SPECIFIC);
        }

        public String getAnalogMaxTime() {
            Long l = ((AnalogStatistics) stats).getMaximumTime();
            if (l == null)
                return null;
            return Functions.getFullMinuteTime(l);
        }

        public String getAnalogAverage() {
            Double d = ((AnalogStatistics) stats).getAverage();
            if (d == null)
                return null;
            return textRenderer.getText(d, TextRenderer.HINT_SPECIFIC);
        }

        public String getAnalogSum() {
            return textRenderer.getText(((AnalogStatistics) stats).getSum(), TextRenderer.HINT_SPECIFIC);
        }

        public String getAnalogCount() {
            return Integer.toString(((AnalogStatistics) stats).getCount());
        }

        public List<StartsAndRuntimeWrapper> getStartsAndRuntimes() {
            List<StartsAndRuntime> original = ((StartsAndRuntimeList) stats).getData();
            List<StartsAndRuntimeWrapper> result = new ArrayList<StartsAndRuntimeWrapper>(original.size());
            for (StartsAndRuntime sar : original)
                result.add(new StartsAndRuntimeWrapper(sar, textRenderer));
            return result;
        }

        public String getValueChangeCount() {
            return Integer.toString(((ValueChangeCounter) stats).getChanges());
        }

        public boolean isChartData() {
            return numericTimeSeries != null || discreteTimeSeries != null;
        }

        public String getChartPath() {
            if (inlinePrefix != null)
                return inlinePrefix + getChartName();
            return IMAGE_SERVLET + getChartName();
        }

        public String getChartName() {
            return "reportPointChart" + reportPointId + ".png";
        }
    }

    public static class StartsAndRuntimeWrapper {
        private static DecimalFormat percFormat = new DecimalFormat("0.#%");
        private final StartsAndRuntime sar;
        private final TextRenderer textRenderer;

        public StartsAndRuntimeWrapper(StartsAndRuntime sar, TextRenderer textRenderer) {
            this.sar = sar;
            this.textRenderer = textRenderer;
        }

        public String getValue() {
            return textRenderer.getText(sar.getDataValue(), TextRenderer.HINT_SPECIFIC);
        }

        public String getStarts() {
            return Integer.toString(sar.getStarts());
        }

        public String getRuntime() {
            return percFormat.format(sar.getProportion());
        }
    }

    class StreamHandler implements ExportDataStreamHandler {
        private final long start;
        private final long end;
        private final int imageWidth;

        File exportFile;
        private ExportCsvStreamer exportCsvStreamer;

        private final List<PointStatistics> pointStatistics;
        private final PointTimeSeriesCollection pointTimeSeriesCollection;

        private PointStatistics point;
        private NumericTimeSeries numericTimeSeries;
        private DiscreteTimeSeries discreteTimeSeries;
        private AbstractDataQuantizer quantizer;
       
        private Map<String, String> xidMapping;
        private HashMap<String, PointStatistics> statisticsMap;
    private HashMap<String, HashMap<String, PointStatistics>> devices;

        public StreamHandler(Map<String, String> xidMapping, long start, long end, int imageWidth, boolean createExportFile, Translations translations) {
            pointStatistics = new ArrayList<PointStatistics>();
            pointTimeSeriesCollection = new PointTimeSeriesCollection(timeZone);
     
            this.xidMapping = xidMapping;
      devices = new HashMap<String, HashMap<String, PointStatistics>>();
      statisticsMap = new HashMap<String, PointStatistics>();

            this.start = start;
            this.end = end;
            this.imageWidth = imageWidth * 10;
            try {
                if (createExportFile) {
                    exportFile = File.createTempFile("tempCSV", ".csv");
                    exportCsvStreamer = new ExportCsvStreamer(new PrintWriter(new FileWriter(exportFile)), translations);
                }
            }
            catch (IOException e) {
                LOG.error("Failed to create temp file", e);
            }
        }

        public List<PointStatistics> getPointStatistics() {
            return pointStatistics;
        }

        public PointTimeSeriesCollection getPointTimeSeriesCollection() {
            return pointTimeSeriesCollection;
        }
   
    public HashMap<String, HashMap<String, PointStatistics>> getDevices() {
      return devices;
    }
   
    public HashMap<String, PointStatistics> getStatisticsMap() {
      return statisticsMap;
    }

        @Override
        public void startPoint(ExportPointInfo pointInfo) {
            donePoint();
     
      addDeviceIfAbsent(pointInfo.getDeviceName());

            point = new PointStatistics(pointInfo.getReportPointId());
            point.setName(pointInfo.getPointName());
      point.setDeviceName(pointInfo.getDeviceName());
            point.setDataType(pointInfo.getDataType());
            point.setDataTypeDescription(DataTypes.getDataTypeMessage(pointInfo.getDataType()).translate(translations));
            point.setTextRenderer(pointInfo.getTextRenderer());
            if (pointInfo.getStartValue() != null)
                point.setStartValue(pointInfo.getTextRenderer().getText(pointInfo.getStartValue(),
                        TextRenderer.HINT_SPECIFIC));
            pointStatistics.add(point);
            devices.get(point.getDeviceName()).put(point.getName(), point);
            statisticsMap.put(xidMapping.get(pointInfo.getXid()), point);

            Color colour = null;
            try {
                if (pointInfo.getColour() != null)
                    colour = ColorUtils.toColor("#" + pointInfo.getColour());
            }
            catch (InvalidArgumentException e) {
                // Should never happen, but leave the color null in case it does.
            }

            Stroke stroke = new BasicStroke(pointInfo.getWeight());

            if (pointInfo.getDataType() == DataTypes.NUMERIC) {
                point.setStats(new AnalogStatistics(start, end, pointInfo.getStartValue() == null ? null : pointInfo
                        .getStartValue().getDoubleValue()));

                discreteTimeSeries = null;
                TimeSeries timeSeries = new TimeSeries(pointInfo.getExtendedName(), null, null);
                timeSeries.setRangeDescription(point.getTextRenderer().getMetaText());
                numericTimeSeries = new NumericTimeSeries(pointInfo.getPlotType(), timeSeries, colour, stroke);
                point.setNumericTimeSeries(numericTimeSeries);
                if (pointInfo.isConsolidatedChart())
                    pointTimeSeriesCollection.addNumericTimeSeries(numericTimeSeries);

                quantizer = new NumericDataQuantizer(start, end, imageWidth,
                        new TimeSeriesQuantizerCallback(timeSeries));
            }
            else if (pointInfo.getDataType() == DataTypes.MULTISTATE) {
                point.setStats(new StartsAndRuntimeList(start, end, pointInfo.getStartValue()));

                discreteTimeSeries = new DiscreteTimeSeries(pointInfo.getExtendedName(), pointInfo.getTextRenderer(),
                        colour, stroke);
                point.setDiscreteTimeSeries(discreteTimeSeries);
                if (pointInfo.isConsolidatedChart())
                    pointTimeSeriesCollection.addDiscreteTimeSeries(discreteTimeSeries);
                numericTimeSeries = null;

                quantizer = new MultistateDataQuantizer(start, end, imageWidth,
                        new DiscreteTimeSeriesQuantizerCallback(discreteTimeSeries));
            }
            else if (pointInfo.getDataType() == DataTypes.BINARY) {
                point.setStats(new StartsAndRuntimeList(start, end, pointInfo.getStartValue()));

                discreteTimeSeries = new DiscreteTimeSeries(pointInfo.getExtendedName(), pointInfo.getTextRenderer(),
                        colour, stroke);
                point.setDiscreteTimeSeries(discreteTimeSeries);
                if (pointInfo.isConsolidatedChart())
                    pointTimeSeriesCollection.addDiscreteTimeSeries(discreteTimeSeries);
                numericTimeSeries = null;

                quantizer = new BinaryDataQuantizer(start, end, imageWidth, new DiscreteTimeSeriesQuantizerCallback(
                        discreteTimeSeries));
            }
            else if (pointInfo.getDataType() == DataTypes.ALPHANUMERIC) {
                point.setStats(new ValueChangeCounter(start, end, pointInfo.getStartValue()));
                quantizer = null;

                discreteTimeSeries = null;
                numericTimeSeries = null;
            }
            else if (pointInfo.getDataType() == DataTypes.IMAGE) {
                point.setStats(new ValueChangeCounter(start, end, pointInfo.getStartValue()));
                quantizer = null;

                discreteTimeSeries = null;
                numericTimeSeries = null;
            }
            else
                throw new ShouldNeverHappenException("Unknown point data type: " + pointInfo.getDataType()
                        + " for point " + pointInfo.getReportPointId() + ", name=" + pointInfo.getExtendedName());

            if (exportCsvStreamer != null)
                exportCsvStreamer.startPoint(pointInfo);
        }

        @Override
        public void pointData(ExportDataValue rdv) {
            if (quantizer != null)
                quantizer.data(rdv);
            point.getStats().addValueTime(rdv);
            if (exportCsvStreamer != null)
                exportCsvStreamer.pointData(rdv);
        }

        private void donePoint() {
            if (quantizer != null)
                quantizer.done();
            if (point != null)
                // Add in an end value to calculate stats until the end of the report.
                point.getStats().done(new PointValueTime(0D, end));
        }

        @Override
        public void done() {
            donePoint();
            if (exportCsvStreamer != null)
                exportCsvStreamer.done();
        }
   
    private void addDeviceIfAbsent(String deviceName) {
      if(devices.containsKey(deviceName))
        return;
      devices.put(deviceName, new HashMap<String, PointStatistics>());
    }
    }
}
TOP

Related Classes of com.serotonin.m2m2.reports.web.ReportChartCreator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.