view artifacts/src/main/java/org/dive4elements/river/artifacts/services/DynamicMainValuesTimeRangeDeterminationService.java @ 9415:9744ce3c3853

Rework of fixanalysis computation and dWt and WQ facets. Got rid of strange remapping and bitshifting code by explicitely saving the column information and using it in the facets. The facets also put the valid station range into their xml-metadata
author gernotbelger
date Thu, 16 Aug 2018 16:27:53 +0200
parents b534a4f4e4f6
children a31cb674ddd1
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU AGPL (>=v3)
 * and comes with ABSOLUTELY NO WARRANTY! Check out the
 * documentation coming with Dive4Elements River for details.
 */

package org.dive4elements.river.artifacts.services;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.xml.xpath.XPathConstants;

import org.dive4elements.artifacts.CallMeta;
import org.dive4elements.artifacts.GlobalContext;
import org.dive4elements.artifacts.common.ArtifactNamespaceContext;
import org.dive4elements.artifacts.common.utils.XMLUtils;
import org.dive4elements.artifacts.common.utils.XMLUtils.ElementCreator;
import org.dive4elements.river.artifacts.resources.Resources;
import org.dive4elements.river.artifacts.services.AbstractMainValuesService.MainValuesServiceException;
import org.dive4elements.river.model.Gauge;
import org.dive4elements.river.model.River;
import org.dive4elements.river.model.sinfo.DailyDischargeValue;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * This service returns the list of gauges with daily discharge time periods and error messages
 */
public class DynamicMainValuesTimeRangeDeterminationService extends D4EService {

    private static final long serialVersionUID = 1L;

    private static final String ROOT_NODE = "dynamic-mainvalues-input";

    public static final class ServiceException extends Exception {

        private static final long serialVersionUID = 1L;

        public ServiceException(final String message) {
            super(message);
        }
    }

    private static class GaugeInfoResult {
        protected final String globalErrorMsg;
        protected final List<GaugeInfo> gaugeInfos;

        protected GaugeInfoResult(final List<GaugeInfo> gaugeInfos, final String globalErrorMsg) {
            this.gaugeInfos = gaugeInfos;
            this.globalErrorMsg = globalErrorMsg;
        }

        private static class GaugeInfo {
            protected final String errorMsg;
            protected final Gauge gauge;
            protected final Date startdate;
            protected final Date enddate;

            public GaugeInfo(final String errorMsg, final Gauge gauge, final Date startdate, final Date enddate) {
                this.errorMsg = errorMsg;
                this.gauge = gauge;
                this.startdate = startdate;
                this.enddate = enddate;
            }
        }
    }

    /**
     * Queries the available daily discharge time periods of a list of gauges from the database and checks the overlapping
     *
     * @throws ServiceException
     */
    private GaugeInfoResult getCommonTimeRangeForGauges(final List<Gauge> gauges, final Date startTime, final Date endTime, final CallMeta meta)
            throws ServiceException {

        // Query the gauge's daily Q values
        String globalErrorMsg = "";
        final List<GaugeInfoResult.GaugeInfo> gaugeResults = new ArrayList<>();
        Date min = startTime;
        Date max = endTime;

        for (final Gauge gauge : gauges) {

            final Date[] gaugeDates = DailyDischargeValue.getTimePeriod(gauge, startTime, endTime);
            if (gaugeDates[0] == null) {
                final String msg = Resources.getMsg(meta, "bundu.wst_no_data_at_all", "bundu.wst_no_data_at_all", gauge.getName());
                final GaugeInfoResult.GaugeInfo gi = new GaugeInfoResult.GaugeInfo(msg, gauge, null, null);
                gaugeResults.add(gi);
                if (globalErrorMsg.isEmpty())
                    globalErrorMsg = msg;
                continue;
            }

            if (gaugeDates[0].getTime() > min.getTime())
                min = gaugeDates[0];

            if (gaugeDates[1].getTime() < max.getTime())
                max = gaugeDates[1];

            String errormsg = null;
            if ((gaugeDates[1].getTime() < endTime.getTime()) || (gaugeDates[0].getTime() > startTime.getTime()))
                errormsg = makeDoesNotCoverErrorMsg(gaugeDates[0], gaugeDates[1], meta);

            gaugeResults.add(new GaugeInfoResult.GaugeInfo(errormsg, gauge, gaugeDates[0], gaugeDates[1]));
        }

        // common Range and correct errorMsg
        final List<GaugeInfoResult.GaugeInfo> gaugeResultsSecondTurn = new ArrayList<>();
        for (final GaugeInfoResult.GaugeInfo gi : gaugeResults) {
            gaugeResultsSecondTurn
                    .add(new GaugeInfoResult.GaugeInfo(gi.errorMsg, gi.gauge, gi.startdate != null ? min : null, gi.enddate != null ? max : null));
        }
        if (globalErrorMsg.isEmpty() && (min.getTime() > max.getTime()))
            globalErrorMsg = getMsg(meta, "bundu.wst.gauge_timeranges_disjoint");
        final GaugeInfoResult result = new GaugeInfoResult(gaugeResultsSecondTurn, globalErrorMsg);

        return result;
    }

    private String makeDoesNotCoverErrorMsg(final Date start, final Date end, final CallMeta meta) {
        final Calendar cal = Calendar.getInstance();
        return Resources.getMsg(meta, "bundu.wst.range_does_not_cover", new Object[] { getYear(start, cal), getYear(end, cal) });
    }

    private String getYear(final Date d, final Calendar cal) {
        return String.valueOf(cal.get(Calendar.YEAR));
    }

    @Override
    public Document doProcess(final Document data, final GlobalContext context, final CallMeta callMeta) {
        try {
            final River river = AbstractMainValuesService.getRequestedRiver(data, "/art:" + ROOT_NODE + "/art:river/text()");
            final List<Gauge> gauges = getRequestedGauges(data, river, callMeta);
            final Date start = getRequestedStartYear(data, "/art:" + ROOT_NODE + "/art:startYear/text()");
            final Date end = getRequestedEndYear(data, "/art:" + ROOT_NODE + "/art:endYear/text()");

            final GaugeInfoResult result = getCommonTimeRangeForGauges(gauges, start, end, callMeta);

            return buildDocument(result, context, callMeta);
        }
        catch (final ServiceException | MainValuesServiceException e) {
            e.printStackTrace();
            return AbstractMainValuesService.error(e.getMessage());
        }
    }

    public static final Date getRequestedEndYear(final Document data, final String XPATH_END_YEAR) throws MainValuesServiceException {

        final String endStr = XMLUtils.xpathString(data, XPATH_END_YEAR, ArtifactNamespaceContext.INSTANCE);

        if (endStr == null)
            throw new MainValuesServiceException("no end year"); // should not happen

        try {
            final int year = Integer.parseInt(endStr);

            // FIXME: timezone? probably must match timezone of database
            final Calendar cal = Calendar.getInstance();
            cal.clear();
            cal.set(year, 11, 31);
            return cal.getTime();
        }
        catch (final NumberFormatException e) {
            e.printStackTrace();
            throw new MainValuesServiceException("invalid end year"); // should not happen
        }
    }

    public static final Date getRequestedStartYear(final Document data, final String XPATH_START_YEAR) throws MainValuesServiceException {

        final String startStr = XMLUtils.xpathString(data, XPATH_START_YEAR, ArtifactNamespaceContext.INSTANCE);

        if (startStr == null)
            throw new MainValuesServiceException("no start year");// should not happen

        try {
            final int year = Integer.parseInt(startStr);

            // FIXME: timezone? probably must match timezone of database
            final Calendar cal = Calendar.getInstance();
            cal.clear();
            cal.set(year, 0, 1);
            return cal.getTime();
        }
        catch (final NumberFormatException e) {
            e.printStackTrace();
            throw new MainValuesServiceException("invalid start year"); // should not happen
        }
    }

    private Document buildDocument(final GaugeInfoResult result, final GlobalContext context, final CallMeta meta) {

        final Document doc = XMLUtils.newDocument();

        final ElementCreator cr = new ElementCreator(doc, ArtifactNamespaceContext.NAMESPACE_URI, ArtifactNamespaceContext.NAMESPACE_PREFIX);

        final Element rootEl = cr.create(ROOT_NODE);

        doc.appendChild(rootEl);

        final Element globalErrElement = cr.create("global-error-msg");
        globalErrElement.setTextContent(result.globalErrorMsg);
        rootEl.appendChild(globalErrElement);

        final List<GaugeInfoResult.GaugeInfo> values = result.gaugeInfos;

        for (final GaugeInfoResult.GaugeInfo gauge : values) {
            final Element gaugeElement = cr.create("gauge");
            cr.addAttr(gaugeElement, "name", gauge.gauge.getName());
            if (gauge.startdate != null)
                cr.addAttr(gaugeElement, "date-from", String.valueOf(gauge.startdate.getTime()));

            if (gauge.enddate != null)
                cr.addAttr(gaugeElement, "date-to", String.valueOf(gauge.enddate.getTime()));

            if (gauge.errorMsg != null)
                cr.addAttr(gaugeElement, "error-message", gauge.errorMsg);

            rootEl.appendChild(gaugeElement);
        }

        return doc;

    }

    final Element buildElement(final ElementCreator cr, final String type, final Date date) {
        final Element el = cr.create(type);
        cr.addAttr(el, "value", String.valueOf(date.getTime()));
        return el;
    }

    private static final List<Gauge> getRequestedGauges(final Document data, final River river, final CallMeta meta) throws ServiceException {

        final NodeList gaugeNodes = data.getElementsByTagNameNS(ArtifactNamespaceContext.NAMESPACE_URI, "gauge");

        final List<Gauge> gauges = new ArrayList<>();

        for (int i = 0; i < gaugeNodes.getLength(); i++) {
            final Element gaugeElt = (Element) gaugeNodes.item(i);

            final String gaugeName = (String) XMLUtils.xpath(gaugeElt, "text()", XPathConstants.STRING);
            final Gauge gauge = Gauge.getGaugeByNameAndRiver(gaugeName, river);
            if (gauge != null)
                gauges.add(gauge);
            else {
                throw new ServiceException("bundu_wst_error_reading_gauges");
            }
        }

        return gauges;
    }

    private static String getMsg(final CallMeta meta, final String key) {
        return Resources.getMsg(meta, key);
    }
}

http://dive4elements.wald.intevation.org