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; mschaefer@9419: import org.dive4elements.artifacts.common.utils.DateUtils; 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: /** mschaefer@9409: * This service returns the list of gauges with daily discharge time periods and error messages 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: 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@9588: public static class GaugeInfoResult { mschaefer@9409: protected final String globalErrorMsg; mschaefer@9409: protected final List gaugeInfos; gernotbelger@9588: private final Date globalStartDate; gernotbelger@9588: private final Date globalEndDate; gernotbelger@9404: gernotbelger@9588: protected GaugeInfoResult(final List gaugeInfos, final String globalErrorMsg, final Date min, final Date max) { gernotbelger@9404: this.gaugeInfos = gaugeInfos; gernotbelger@9404: this.globalErrorMsg = globalErrorMsg; gernotbelger@9588: this.globalStartDate = min; gernotbelger@9588: this.globalEndDate = max; gernotbelger@9588: } gernotbelger@9588: gernotbelger@9588: public int getGlobalEndYear() { gernotbelger@9588: return getYearFromDate(this.globalEndDate); gernotbelger@9588: } gernotbelger@9588: gernotbelger@9588: public int getGlobalStartYear() { gernotbelger@9588: return getYearFromDate(this.globalStartDate); gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: private static class GaugeInfo { mschaefer@9409: protected final String errorMsg; mschaefer@9409: protected final Gauge gauge; mschaefer@9494: /** mschaefer@9494: * New year of the first year for which the gauge has complete discharge data, including november+december of the year mschaefer@9494: * before mschaefer@9494: */ mschaefer@9409: protected final Date startdate; mschaefer@9409: protected 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: mschaefer@9409: /** mschaefer@9409: * Queries the available daily discharge time periods of a list of gauges from the database and checks the overlapping mschaefer@9409: * mschaefer@9409: * @throws ServiceException mschaefer@9409: */ gernotbelger@9588: public static GaugeInfoResult getCommonTimeRangeForGauges(final List gauges, final int startYear, final int endYear, final CallMeta meta) { gernotbelger@9404: gernotbelger@9588: final Date startTime = getStartDateFromYear(startYear); gernotbelger@9588: final Date endTime = getEndDateFromYear(endYear); gernotbelger@9404: // Query the gauge's daily Q values mschaefer@9409: String globalErrorMsg = ""; gernotbelger@9404: final List gaugeResults = new ArrayList<>(); mschaefer@9494: final Date qStartTime = DateUtils.getAbflussYear(startTime)[0]; mschaefer@9494: Date min = qStartTime; gernotbelger@9404: Date max = endTime; gernotbelger@9404: gernotbelger@9404: for (final Gauge gauge : gauges) { gernotbelger@9404: mschaefer@9494: final Date[] gaugeDates = DailyDischargeValue.getTimePeriod(gauge, qStartTime, endTime); mschaefer@9409: if (gaugeDates[0] == null) { mschaefer@9409: final String msg = Resources.getMsg(meta, "bundu.wst_no_data_at_all", "bundu.wst_no_data_at_all", gauge.getName()); mschaefer@9409: final GaugeInfoResult.GaugeInfo gi = new GaugeInfoResult.GaugeInfo(msg, gauge, null, null); mschaefer@9409: gaugeResults.add(gi); mschaefer@9409: if (globalErrorMsg.isEmpty()) mschaefer@9409: globalErrorMsg = msg; gernotbelger@9404: continue; gernotbelger@9404: } gernotbelger@9404: mschaefer@9494: final Date gaugeCalcStartDate = DateUtils.getNextAbflussYear(gaugeDates[0])[0]; mschaefer@9410: if (gaugeDates[0].getTime() > min.getTime()) mschaefer@9494: min = gaugeCalcStartDate; gernotbelger@9404: mschaefer@9410: if (gaugeDates[1].getTime() < max.getTime()) mschaefer@9409: max = gaugeDates[1]; gernotbelger@9404: gernotbelger@9404: String errormsg = null; mschaefer@9494: if ((gaugeDates[1].getTime() < endTime.getTime()) || (gaugeDates[0].getTime() > qStartTime.getTime())) mschaefer@9494: errormsg = makeDoesNotCoverErrorMsg(DateUtils.getAbflussYearFromDate(gaugeCalcStartDate), DateUtils.getYearFromDate(gaugeDates[1]), meta); gernotbelger@9404: mschaefer@9494: gaugeResults.add(new GaugeInfoResult.GaugeInfo(errormsg, gauge, DateUtils.getNextNewYear(gaugeCalcStartDate), gaugeDates[1])); gernotbelger@9404: } gernotbelger@9404: gernotbelger@9404: // common Range and correct errorMsg mschaefer@9494: min = DateUtils.getNextNewYear(min); gernotbelger@9404: final List gaugeResultsSecondTurn = new ArrayList<>(); gernotbelger@9404: for (final GaugeInfoResult.GaugeInfo gi : gaugeResults) { gernotbelger@9458: gaugeResultsSecondTurn gernotbelger@9588: .add(new GaugeInfoResult.GaugeInfo(gi.errorMsg, gi.gauge, gi.startdate != null ? min : null, gi.enddate != null ? max : null)); gernotbelger@9404: } mschaefer@9409: if (globalErrorMsg.isEmpty() && (min.getTime() > max.getTime())) mschaefer@9409: globalErrorMsg = getMsg(meta, "bundu.wst.gauge_timeranges_disjoint"); gernotbelger@9458: gernotbelger@9458: // add "cannot calculate UD" to globalErrorMsg gernotbelger@9458: // Eine Berechnung der UD ist nicht möglich. gernotbelger@9458: if (!globalErrorMsg.isEmpty()) gernotbelger@9458: globalErrorMsg = new StringBuilder().append(globalErrorMsg).append("\n").append(getMsg(meta, "bundu.wst.gauge_no_ud_calc_available")).toString(); gernotbelger@9458: gernotbelger@9588: final GaugeInfoResult result = new GaugeInfoResult(gaugeResultsSecondTurn, globalErrorMsg, min, max); gernotbelger@9404: gernotbelger@9404: return result; gernotbelger@9404: } gernotbelger@9404: gernotbelger@9588: private static String makeDoesNotCoverErrorMsg(final int startYear, final int endYear, final CallMeta meta) { mschaefer@9419: final String msgkey = "bundu.wst.range_does_not_cover"; mschaefer@9494: return Resources.getMsg(meta, msgkey, msgkey, 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 { mschaefer@9409: final River river = AbstractMainValuesService.getRequestedRiver(data, "/art:" + ROOT_NODE + "/art:river/text()"); gernotbelger@9404: final List gauges = getRequestedGauges(data, river, callMeta); gernotbelger@9588: final int start = getRequestedStartYear(data, "/art:" + ROOT_NODE + "/art:startYear/text()"); gernotbelger@9588: final int end = getRequestedEndYear(data, "/art:" + 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@9588: public static final int 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@9588: return Integer.parseInt(endStr); 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@9588: public static final int 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@9588: return Integer.parseInt(startStr); 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@9588: private static Integer getYearFromDate(final Date date) { gernotbelger@9588: // FIXME: timezone? probably must match timezone of database gernotbelger@9588: final Calendar cal = Calendar.getInstance(); gernotbelger@9588: cal.clear(); gernotbelger@9588: cal.setTime(date); gernotbelger@9588: return cal.get(Calendar.YEAR); gernotbelger@9588: } gernotbelger@9588: gernotbelger@9588: private static Date getEndDateFromYear(final int year) { gernotbelger@9588: // FIXME: timezone? probably must match timezone of database gernotbelger@9588: final Calendar cal = Calendar.getInstance(); gernotbelger@9588: cal.clear(); gernotbelger@9588: cal.set(year, 11, 31); gernotbelger@9588: return cal.getTime(); gernotbelger@9588: } gernotbelger@9588: gernotbelger@9588: private static Date getStartDateFromYear(final int year) { gernotbelger@9588: // FIXME: timezone? probably must match timezone of database gernotbelger@9588: final Calendar cal = Calendar.getInstance(); gernotbelger@9588: cal.clear(); gernotbelger@9588: cal.set(year, 0, 1); gernotbelger@9588: return cal.getTime(); gernotbelger@9588: } gernotbelger@9588: 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: }