Mercurial > dive4elements > river
diff artifacts/src/main/java/org/dive4elements/river/artifacts/services/DynamicMainValuesTimeRangeDeterminationService.java @ 9404:bc9a45d2b1fa
common time range for gauges incl. error messages
author | gernotbelger |
---|---|
date | Wed, 15 Aug 2018 13:59:09 +0200 |
parents | |
children | 34cd4faf43f4 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/services/DynamicMainValuesTimeRangeDeterminationService.java Wed Aug 15 13:59:09 2018 +0200 @@ -0,0 +1,269 @@ +/* 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 main values of a river's gauge based on the start + * and end point of the river. + * + * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> + */ +public class DynamicMainValuesTimeRangeDeterminationService extends D4EService { + + private static final long serialVersionUID = 1L; + + private static final String ROOT_NODE = "dynamic-mainvalues-input"; + + private static final Long DATE_DELTA_ERROR_MSG = (long) (60 * 60 * 24 * 1000); + + public static final class ServiceException extends Exception { + + private static final long serialVersionUID = 1L; + + public ServiceException(final String message) { + super(message); + } + } + + /** + * Computes a gauge's main values for a period of time based on its daily discharges stored in the database + * + * @throws Exception + */ + + private static class GaugeInfoResult { + private final String globalErrorMsg; + private final List<GaugeInfo> gaugeInfos; + + private GaugeInfoResult(final List<GaugeInfo> gaugeInfos, final String globalErrorMsg) { + this.gaugeInfos = gaugeInfos; + this.globalErrorMsg = globalErrorMsg; + } + + private static class GaugeInfo { + private final String errorMsg; + private final Gauge gauge; + private final Date startdate; + private 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; + } + } + } + + 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 + final List<GaugeInfoResult.GaugeInfo> gaugeResults = new ArrayList<>(); + Date min = startTime; + Date max = endTime; + + for (final Gauge gauge : gauges) { + + final List<DailyDischargeValue> qdvsGlobal = DailyDischargeValue.getGlobalMinMax(gauge); + if (qdvsGlobal == null) { + + gaugeResults.add(new GaugeInfoResult.GaugeInfo(getMsg(meta, "bundu.wst_no_data_at_all"), gauge, null, null)); + // TODO : wenn der Workflow abgebrochen werden soll, GlobalErrorMsg setzen, dass mind. ein Pegel überhaupt keine Daten + // hat (der Mechnismus auf Client-Seite ist schon implementiert) + + continue; + } + assert qdvsGlobal.size() == 2; + final Date minGlobalForGauge = qdvsGlobal.get(0).getDay(); + final Date maxGlobalForGauge = qdvsGlobal.get(1).getDay(); + + if (minGlobalForGauge.getTime() > startTime.getTime()) + min = minGlobalForGauge; + + if (maxGlobalForGauge.getTime() < endTime.getTime()) + max = maxGlobalForGauge; + + String errormsg = null; + if ((maxGlobalForGauge.getTime() < endTime.getTime()) || (minGlobalForGauge.getTime() > startTime.getTime())) + errormsg = makeDoesNotCoverErrorMsg(minGlobalForGauge, maxGlobalForGauge, meta); + + gaugeResults.add(new GaugeInfoResult.GaugeInfo(errormsg, gauge, min, max)); + } + + // 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)); + } + final String globalErrorMsg = (min.getTime() > max.getTime()) ? 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(); + cal.setTime(start); + final String startyear = String.valueOf(cal.get(Calendar.YEAR)); + cal.setTime(end); + final String endyear = String.valueOf(cal.get(Calendar.YEAR)); + return Resources.getMsg(meta, "bundu.wst.range_does_not_cover", new Object[] { startyear, endyear }); + } + + @Override + public Document doProcess(final Document data, final GlobalContext context, final CallMeta callMeta) { + try { + final River river = AbstractMainValuesService.getRequestedRiver(data, "/art:" + this.ROOT_NODE + "/art:river/text()"); + final List<Gauge> gauges = getRequestedGauges(data, river, callMeta); + final Date start = getRequestedStartYear(data, "/art:" + this.ROOT_NODE + "/art:startYear/text()"); + final Date end = getRequestedEndYear(data, "/art:" + this.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); + } +} \ No newline at end of file