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: /** 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: 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: mschaefer@9409: private static class GaugeInfoResult { mschaefer@9409: protected final String globalErrorMsg; mschaefer@9409: protected final List gaugeInfos; gernotbelger@9404: mschaefer@9409: protected 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 { mschaefer@9409: protected final String errorMsg; mschaefer@9409: protected final Gauge gauge; 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@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 mschaefer@9409: String globalErrorMsg = ""; 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: mschaefer@9409: final Date[] gaugeDates = DailyDischargeValue.getTimePeriod(gauge, startTime, 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@9409: if (gaugeDates[0].getTime() > startTime.getTime()) mschaefer@9409: min = gaugeDates[0]; gernotbelger@9404: mschaefer@9409: if (gaugeDates[1].getTime() < endTime.getTime()) mschaefer@9409: max = gaugeDates[1]; gernotbelger@9404: gernotbelger@9404: String errormsg = null; mschaefer@9409: if ((gaugeDates[1].getTime() < endTime.getTime()) || (gaugeDates[0].getTime() > startTime.getTime())) mschaefer@9409: errormsg = makeDoesNotCoverErrorMsg(gaugeDates[0], gaugeDates[1], 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) { mschaefer@9409: gaugeResultsSecondTurn.add(new GaugeInfoResult.GaugeInfo(gi.errorMsg, gi.gauge, gi.startdate != null ? min : null, mschaefer@9409: 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@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 { mschaefer@9409: final River river = AbstractMainValuesService.getRequestedRiver(data, "/art:" + ROOT_NODE + "/art:river/text()"); gernotbelger@9404: final List gauges = getRequestedGauges(data, river, callMeta); mschaefer@9409: final Date start = getRequestedStartYear(data, "/art:" + ROOT_NODE + "/art:startYear/text()"); mschaefer@9409: final Date 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@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: }