gernotbelger@9404: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde gernotbelger@9404: * Software engineering by Intevation GmbH gernotbelger@9404: * gernotbelger@9404: * This file is Free Software under the GNU AGPL (>=v3) gernotbelger@9404: * and comes with ABSOLUTELY NO WARRANTY! Check out the gernotbelger@9404: * documentation coming with Dive4Elements River for details. gernotbelger@9404: */ gernotbelger@9404: gernotbelger@9404: package org.dive4elements.river.artifacts.services; gernotbelger@9404: gernotbelger@9404: import java.util.ArrayList; gernotbelger@9404: import java.util.Calendar; gernotbelger@9404: import java.util.Date; gernotbelger@9404: import java.util.List; gernotbelger@9404: gernotbelger@9404: import javax.xml.xpath.XPathConstants; gernotbelger@9404: gernotbelger@9404: import org.dive4elements.artifacts.CallMeta; gernotbelger@9404: import org.dive4elements.artifacts.GlobalContext; gernotbelger@9404: import org.dive4elements.artifacts.common.ArtifactNamespaceContext; gernotbelger@9404: import org.dive4elements.artifacts.common.utils.XMLUtils; gernotbelger@9404: import org.dive4elements.artifacts.common.utils.XMLUtils.ElementCreator; gernotbelger@9404: import org.dive4elements.river.artifacts.resources.Resources; gernotbelger@9404: import org.dive4elements.river.artifacts.services.AbstractMainValuesService.MainValuesServiceException; gernotbelger@9404: import org.dive4elements.river.model.Gauge; gernotbelger@9404: import org.dive4elements.river.model.River; gernotbelger@9404: import org.dive4elements.river.model.sinfo.DailyDischargeValue; gernotbelger@9404: import org.w3c.dom.Document; gernotbelger@9404: import org.w3c.dom.Element; gernotbelger@9404: import org.w3c.dom.NodeList; gernotbelger@9404: gernotbelger@9404: /** gernotbelger@9404: * This service returns the main values of a river's gauge based on the start gernotbelger@9404: * and end point of the river. gernotbelger@9404: * gernotbelger@9404: * @author Ingo Weinzierl gernotbelger@9404: */ gernotbelger@9404: public class DynamicMainValuesTimeRangeDeterminationService extends D4EService { gernotbelger@9404: gernotbelger@9404: private static final long serialVersionUID = 1L; gernotbelger@9404: gernotbelger@9404: private static final String ROOT_NODE = "dynamic-mainvalues-input"; gernotbelger@9404: gernotbelger@9404: private static final Long DATE_DELTA_ERROR_MSG = (long) (60 * 60 * 24 * 1000); gernotbelger@9404: gernotbelger@9404: public static final class ServiceException extends Exception { gernotbelger@9404: gernotbelger@9404: private static final long serialVersionUID = 1L; gernotbelger@9404: gernotbelger@9404: public ServiceException(final String message) { gernotbelger@9404: super(message); gernotbelger@9404: } gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: /** gernotbelger@9404: * Computes a gauge's main values for a period of time based on its daily discharges stored in the database gernotbelger@9404: * gernotbelger@9404: * @throws Exception gernotbelger@9404: */ gernotbelger@9404: gernotbelger@9404: private static class GaugeInfoResult { gernotbelger@9404: private final String globalErrorMsg; gernotbelger@9404: private final List gaugeInfos; gernotbelger@9404: gernotbelger@9404: private GaugeInfoResult(final List gaugeInfos, final String globalErrorMsg) { gernotbelger@9404: this.gaugeInfos = gaugeInfos; gernotbelger@9404: this.globalErrorMsg = globalErrorMsg; gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: private static class GaugeInfo { gernotbelger@9404: private final String errorMsg; gernotbelger@9404: private final Gauge gauge; gernotbelger@9404: private final Date startdate; gernotbelger@9404: private final Date enddate; gernotbelger@9404: gernotbelger@9404: public GaugeInfo(final String errorMsg, final Gauge gauge, final Date startdate, final Date enddate) { gernotbelger@9404: this.errorMsg = errorMsg; gernotbelger@9404: this.gauge = gauge; gernotbelger@9404: this.startdate = startdate; gernotbelger@9404: this.enddate = enddate; gernotbelger@9404: } gernotbelger@9404: } gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: private GaugeInfoResult getCommonTimeRangeForGauges(final List gauges, final Date startTime, final Date endTime, final CallMeta meta) gernotbelger@9404: throws ServiceException { gernotbelger@9404: gernotbelger@9404: // Query the gauge's daily Q values gernotbelger@9404: final List gaugeResults = new ArrayList<>(); gernotbelger@9404: Date min = startTime; gernotbelger@9404: Date max = endTime; gernotbelger@9404: gernotbelger@9404: for (final Gauge gauge : gauges) { gernotbelger@9404: gernotbelger@9404: final List qdvsGlobal = DailyDischargeValue.getGlobalMinMax(gauge); gernotbelger@9404: if (qdvsGlobal == null) { gernotbelger@9404: gernotbelger@9404: gaugeResults.add(new GaugeInfoResult.GaugeInfo(getMsg(meta, "bundu.wst_no_data_at_all"), gauge, null, null)); gernotbelger@9404: // TODO : wenn der Workflow abgebrochen werden soll, GlobalErrorMsg setzen, dass mind. ein Pegel überhaupt keine Daten gernotbelger@9404: // hat (der Mechnismus auf Client-Seite ist schon implementiert) gernotbelger@9404: gernotbelger@9404: continue; gernotbelger@9404: } gernotbelger@9404: assert qdvsGlobal.size() == 2; gernotbelger@9404: final Date minGlobalForGauge = qdvsGlobal.get(0).getDay(); gernotbelger@9404: final Date maxGlobalForGauge = qdvsGlobal.get(1).getDay(); gernotbelger@9404: gernotbelger@9404: if (minGlobalForGauge.getTime() > startTime.getTime()) gernotbelger@9404: min = minGlobalForGauge; gernotbelger@9404: gernotbelger@9404: if (maxGlobalForGauge.getTime() < endTime.getTime()) gernotbelger@9404: max = maxGlobalForGauge; gernotbelger@9404: gernotbelger@9404: String errormsg = null; gernotbelger@9404: if ((maxGlobalForGauge.getTime() < endTime.getTime()) || (minGlobalForGauge.getTime() > startTime.getTime())) gernotbelger@9404: errormsg = makeDoesNotCoverErrorMsg(minGlobalForGauge, maxGlobalForGauge, meta); gernotbelger@9404: gernotbelger@9404: gaugeResults.add(new GaugeInfoResult.GaugeInfo(errormsg, gauge, min, max)); gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: // common Range and correct errorMsg gernotbelger@9404: final List gaugeResultsSecondTurn = new ArrayList<>(); gernotbelger@9404: for (final GaugeInfoResult.GaugeInfo gi : gaugeResults) { gernotbelger@9404: gaugeResultsSecondTurn gernotbelger@9404: .add(new GaugeInfoResult.GaugeInfo(gi.errorMsg, gi.gauge, gi.startdate != null ? min : null, gi.enddate != null ? max : null)); gernotbelger@9404: } gernotbelger@9404: final String globalErrorMsg = (min.getTime() > max.getTime()) ? getMsg(meta, "bundu.wst.gauge_timeranges_disjoint") : ""; gernotbelger@9404: final GaugeInfoResult result = new GaugeInfoResult(gaugeResultsSecondTurn, globalErrorMsg); gernotbelger@9404: gernotbelger@9404: return result; gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: private String makeDoesNotCoverErrorMsg(final Date start, final Date end, final CallMeta meta) { gernotbelger@9404: final Calendar cal = Calendar.getInstance(); gernotbelger@9404: cal.setTime(start); gernotbelger@9404: final String startyear = String.valueOf(cal.get(Calendar.YEAR)); gernotbelger@9404: cal.setTime(end); gernotbelger@9404: final String endyear = String.valueOf(cal.get(Calendar.YEAR)); gernotbelger@9404: return Resources.getMsg(meta, "bundu.wst.range_does_not_cover", new Object[] { startyear, endyear }); gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: @Override gernotbelger@9404: public Document doProcess(final Document data, final GlobalContext context, final CallMeta callMeta) { gernotbelger@9404: try { gernotbelger@9404: final River river = AbstractMainValuesService.getRequestedRiver(data, "/art:" + this.ROOT_NODE + "/art:river/text()"); gernotbelger@9404: final List gauges = getRequestedGauges(data, river, callMeta); gernotbelger@9404: final Date start = getRequestedStartYear(data, "/art:" + this.ROOT_NODE + "/art:startYear/text()"); gernotbelger@9404: final Date end = getRequestedEndYear(data, "/art:" + this.ROOT_NODE + "/art:endYear/text()"); gernotbelger@9404: gernotbelger@9404: final GaugeInfoResult result = getCommonTimeRangeForGauges(gauges, start, end, callMeta); gernotbelger@9404: gernotbelger@9404: return buildDocument(result, context, callMeta); gernotbelger@9404: } gernotbelger@9404: catch (final ServiceException | MainValuesServiceException e) { gernotbelger@9404: e.printStackTrace(); gernotbelger@9404: return AbstractMainValuesService.error(e.getMessage()); gernotbelger@9404: } gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: public static final Date getRequestedEndYear(final Document data, final String XPATH_END_YEAR) throws MainValuesServiceException { gernotbelger@9404: gernotbelger@9404: final String endStr = XMLUtils.xpathString(data, XPATH_END_YEAR, ArtifactNamespaceContext.INSTANCE); gernotbelger@9404: gernotbelger@9404: if (endStr == null) gernotbelger@9404: throw new MainValuesServiceException("no end year"); // should not happen gernotbelger@9404: gernotbelger@9404: try { gernotbelger@9404: final int year = Integer.parseInt(endStr); gernotbelger@9404: gernotbelger@9404: // FIXME: timezone? probably must match timezone of database gernotbelger@9404: final Calendar cal = Calendar.getInstance(); gernotbelger@9404: cal.clear(); gernotbelger@9404: cal.set(year, 11, 31); gernotbelger@9404: return cal.getTime(); gernotbelger@9404: } gernotbelger@9404: catch (final NumberFormatException e) { gernotbelger@9404: e.printStackTrace(); gernotbelger@9404: throw new MainValuesServiceException("invalid end year"); // should not happen gernotbelger@9404: } gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: public static final Date getRequestedStartYear(final Document data, final String XPATH_START_YEAR) throws MainValuesServiceException { gernotbelger@9404: gernotbelger@9404: final String startStr = XMLUtils.xpathString(data, XPATH_START_YEAR, ArtifactNamespaceContext.INSTANCE); gernotbelger@9404: gernotbelger@9404: if (startStr == null) gernotbelger@9404: throw new MainValuesServiceException("no start year");// should not happen gernotbelger@9404: gernotbelger@9404: try { gernotbelger@9404: final int year = Integer.parseInt(startStr); gernotbelger@9404: gernotbelger@9404: // FIXME: timezone? probably must match timezone of database gernotbelger@9404: final Calendar cal = Calendar.getInstance(); gernotbelger@9404: cal.clear(); gernotbelger@9404: cal.set(year, 0, 1); gernotbelger@9404: return cal.getTime(); gernotbelger@9404: } gernotbelger@9404: catch (final NumberFormatException e) { gernotbelger@9404: e.printStackTrace(); gernotbelger@9404: throw new MainValuesServiceException("invalid start year"); // should not happen gernotbelger@9404: } gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: private Document buildDocument(final GaugeInfoResult result, final GlobalContext context, final CallMeta meta) { gernotbelger@9404: gernotbelger@9404: final Document doc = XMLUtils.newDocument(); gernotbelger@9404: gernotbelger@9404: final ElementCreator cr = new ElementCreator(doc, ArtifactNamespaceContext.NAMESPACE_URI, ArtifactNamespaceContext.NAMESPACE_PREFIX); gernotbelger@9404: gernotbelger@9404: final Element rootEl = cr.create(ROOT_NODE); gernotbelger@9404: gernotbelger@9404: doc.appendChild(rootEl); gernotbelger@9404: gernotbelger@9404: final Element globalErrElement = cr.create("global-error-msg"); gernotbelger@9404: globalErrElement.setTextContent(result.globalErrorMsg); gernotbelger@9404: rootEl.appendChild(globalErrElement); gernotbelger@9404: gernotbelger@9404: final List values = result.gaugeInfos; gernotbelger@9404: gernotbelger@9404: for (final GaugeInfoResult.GaugeInfo gauge : values) { gernotbelger@9404: final Element gaugeElement = cr.create("gauge"); gernotbelger@9404: cr.addAttr(gaugeElement, "name", gauge.gauge.getName()); gernotbelger@9404: if (gauge.startdate != null) gernotbelger@9404: cr.addAttr(gaugeElement, "date-from", String.valueOf(gauge.startdate.getTime())); gernotbelger@9404: gernotbelger@9404: if (gauge.enddate != null) gernotbelger@9404: cr.addAttr(gaugeElement, "date-to", String.valueOf(gauge.enddate.getTime())); gernotbelger@9404: gernotbelger@9404: if (gauge.errorMsg != null) gernotbelger@9404: cr.addAttr(gaugeElement, "error-message", gauge.errorMsg); gernotbelger@9404: gernotbelger@9404: rootEl.appendChild(gaugeElement); gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: return doc; gernotbelger@9404: gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: final Element buildElement(final ElementCreator cr, final String type, final Date date) { gernotbelger@9404: final Element el = cr.create(type); gernotbelger@9404: cr.addAttr(el, "value", String.valueOf(date.getTime())); gernotbelger@9404: return el; gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: private static final List getRequestedGauges(final Document data, final River river, final CallMeta meta) throws ServiceException { gernotbelger@9404: gernotbelger@9404: final NodeList gaugeNodes = data.getElementsByTagNameNS(ArtifactNamespaceContext.NAMESPACE_URI, "gauge"); gernotbelger@9404: gernotbelger@9404: final List gauges = new ArrayList<>(); gernotbelger@9404: gernotbelger@9404: for (int i = 0; i < gaugeNodes.getLength(); i++) { gernotbelger@9404: final Element gaugeElt = (Element) gaugeNodes.item(i); gernotbelger@9404: gernotbelger@9404: final String gaugeName = (String) XMLUtils.xpath(gaugeElt, "text()", XPathConstants.STRING); gernotbelger@9404: final Gauge gauge = Gauge.getGaugeByNameAndRiver(gaugeName, river); gernotbelger@9404: if (gauge != null) gernotbelger@9404: gauges.add(gauge); gernotbelger@9404: else { gernotbelger@9404: throw new ServiceException("bundu_wst_error_reading_gauges"); gernotbelger@9404: } gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: return gauges; gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: private static String getMsg(final CallMeta meta, final String key) { gernotbelger@9404: return Resources.getMsg(meta, key); gernotbelger@9404: } gernotbelger@9404: }