comparison 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
comparison
equal deleted inserted replaced
9403:e2da9c8a7c57 9404:bc9a45d2b1fa
1 /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
2 * Software engineering by Intevation GmbH
3 *
4 * This file is Free Software under the GNU AGPL (>=v3)
5 * and comes with ABSOLUTELY NO WARRANTY! Check out the
6 * documentation coming with Dive4Elements River for details.
7 */
8
9 package org.dive4elements.river.artifacts.services;
10
11 import java.util.ArrayList;
12 import java.util.Calendar;
13 import java.util.Date;
14 import java.util.List;
15
16 import javax.xml.xpath.XPathConstants;
17
18 import org.dive4elements.artifacts.CallMeta;
19 import org.dive4elements.artifacts.GlobalContext;
20 import org.dive4elements.artifacts.common.ArtifactNamespaceContext;
21 import org.dive4elements.artifacts.common.utils.XMLUtils;
22 import org.dive4elements.artifacts.common.utils.XMLUtils.ElementCreator;
23 import org.dive4elements.river.artifacts.resources.Resources;
24 import org.dive4elements.river.artifacts.services.AbstractMainValuesService.MainValuesServiceException;
25 import org.dive4elements.river.model.Gauge;
26 import org.dive4elements.river.model.River;
27 import org.dive4elements.river.model.sinfo.DailyDischargeValue;
28 import org.w3c.dom.Document;
29 import org.w3c.dom.Element;
30 import org.w3c.dom.NodeList;
31
32 /**
33 * This service returns the main values of a river's gauge based on the start
34 * and end point of the river.
35 *
36 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
37 */
38 public class DynamicMainValuesTimeRangeDeterminationService extends D4EService {
39
40 private static final long serialVersionUID = 1L;
41
42 private static final String ROOT_NODE = "dynamic-mainvalues-input";
43
44 private static final Long DATE_DELTA_ERROR_MSG = (long) (60 * 60 * 24 * 1000);
45
46 public static final class ServiceException extends Exception {
47
48 private static final long serialVersionUID = 1L;
49
50 public ServiceException(final String message) {
51 super(message);
52 }
53 }
54
55 /**
56 * Computes a gauge's main values for a period of time based on its daily discharges stored in the database
57 *
58 * @throws Exception
59 */
60
61 private static class GaugeInfoResult {
62 private final String globalErrorMsg;
63 private final List<GaugeInfo> gaugeInfos;
64
65 private GaugeInfoResult(final List<GaugeInfo> gaugeInfos, final String globalErrorMsg) {
66 this.gaugeInfos = gaugeInfos;
67 this.globalErrorMsg = globalErrorMsg;
68 }
69
70 private static class GaugeInfo {
71 private final String errorMsg;
72 private final Gauge gauge;
73 private final Date startdate;
74 private final Date enddate;
75
76 public GaugeInfo(final String errorMsg, final Gauge gauge, final Date startdate, final Date enddate) {
77 this.errorMsg = errorMsg;
78 this.gauge = gauge;
79 this.startdate = startdate;
80 this.enddate = enddate;
81 }
82 }
83 }
84
85 private GaugeInfoResult getCommonTimeRangeForGauges(final List<Gauge> gauges, final Date startTime, final Date endTime, final CallMeta meta)
86 throws ServiceException {
87
88 // Query the gauge's daily Q values
89 final List<GaugeInfoResult.GaugeInfo> gaugeResults = new ArrayList<>();
90 Date min = startTime;
91 Date max = endTime;
92
93 for (final Gauge gauge : gauges) {
94
95 final List<DailyDischargeValue> qdvsGlobal = DailyDischargeValue.getGlobalMinMax(gauge);
96 if (qdvsGlobal == null) {
97
98 gaugeResults.add(new GaugeInfoResult.GaugeInfo(getMsg(meta, "bundu.wst_no_data_at_all"), gauge, null, null));
99 // TODO : wenn der Workflow abgebrochen werden soll, GlobalErrorMsg setzen, dass mind. ein Pegel überhaupt keine Daten
100 // hat (der Mechnismus auf Client-Seite ist schon implementiert)
101
102 continue;
103 }
104 assert qdvsGlobal.size() == 2;
105 final Date minGlobalForGauge = qdvsGlobal.get(0).getDay();
106 final Date maxGlobalForGauge = qdvsGlobal.get(1).getDay();
107
108 if (minGlobalForGauge.getTime() > startTime.getTime())
109 min = minGlobalForGauge;
110
111 if (maxGlobalForGauge.getTime() < endTime.getTime())
112 max = maxGlobalForGauge;
113
114 String errormsg = null;
115 if ((maxGlobalForGauge.getTime() < endTime.getTime()) || (minGlobalForGauge.getTime() > startTime.getTime()))
116 errormsg = makeDoesNotCoverErrorMsg(minGlobalForGauge, maxGlobalForGauge, meta);
117
118 gaugeResults.add(new GaugeInfoResult.GaugeInfo(errormsg, gauge, min, max));
119 }
120
121 // common Range and correct errorMsg
122 final List<GaugeInfoResult.GaugeInfo> gaugeResultsSecondTurn = new ArrayList<>();
123 for (final GaugeInfoResult.GaugeInfo gi : gaugeResults) {
124 gaugeResultsSecondTurn
125 .add(new GaugeInfoResult.GaugeInfo(gi.errorMsg, gi.gauge, gi.startdate != null ? min : null, gi.enddate != null ? max : null));
126 }
127 final String globalErrorMsg = (min.getTime() > max.getTime()) ? getMsg(meta, "bundu.wst.gauge_timeranges_disjoint") : "";
128 final GaugeInfoResult result = new GaugeInfoResult(gaugeResultsSecondTurn, globalErrorMsg);
129
130 return result;
131 }
132
133 private String makeDoesNotCoverErrorMsg(final Date start, final Date end, final CallMeta meta) {
134 final Calendar cal = Calendar.getInstance();
135 cal.setTime(start);
136 final String startyear = String.valueOf(cal.get(Calendar.YEAR));
137 cal.setTime(end);
138 final String endyear = String.valueOf(cal.get(Calendar.YEAR));
139 return Resources.getMsg(meta, "bundu.wst.range_does_not_cover", new Object[] { startyear, endyear });
140 }
141
142 @Override
143 public Document doProcess(final Document data, final GlobalContext context, final CallMeta callMeta) {
144 try {
145 final River river = AbstractMainValuesService.getRequestedRiver(data, "/art:" + this.ROOT_NODE + "/art:river/text()");
146 final List<Gauge> gauges = getRequestedGauges(data, river, callMeta);
147 final Date start = getRequestedStartYear(data, "/art:" + this.ROOT_NODE + "/art:startYear/text()");
148 final Date end = getRequestedEndYear(data, "/art:" + this.ROOT_NODE + "/art:endYear/text()");
149
150 final GaugeInfoResult result = getCommonTimeRangeForGauges(gauges, start, end, callMeta);
151
152 return buildDocument(result, context, callMeta);
153 }
154 catch (final ServiceException | MainValuesServiceException e) {
155 e.printStackTrace();
156 return AbstractMainValuesService.error(e.getMessage());
157 }
158 }
159
160 public static final Date getRequestedEndYear(final Document data, final String XPATH_END_YEAR) throws MainValuesServiceException {
161
162 final String endStr = XMLUtils.xpathString(data, XPATH_END_YEAR, ArtifactNamespaceContext.INSTANCE);
163
164 if (endStr == null)
165 throw new MainValuesServiceException("no end year"); // should not happen
166
167 try {
168 final int year = Integer.parseInt(endStr);
169
170 // FIXME: timezone? probably must match timezone of database
171 final Calendar cal = Calendar.getInstance();
172 cal.clear();
173 cal.set(year, 11, 31);
174 return cal.getTime();
175 }
176 catch (final NumberFormatException e) {
177 e.printStackTrace();
178 throw new MainValuesServiceException("invalid end year"); // should not happen
179 }
180 }
181
182 public static final Date getRequestedStartYear(final Document data, final String XPATH_START_YEAR) throws MainValuesServiceException {
183
184 final String startStr = XMLUtils.xpathString(data, XPATH_START_YEAR, ArtifactNamespaceContext.INSTANCE);
185
186 if (startStr == null)
187 throw new MainValuesServiceException("no start year");// should not happen
188
189 try {
190 final int year = Integer.parseInt(startStr);
191
192 // FIXME: timezone? probably must match timezone of database
193 final Calendar cal = Calendar.getInstance();
194 cal.clear();
195 cal.set(year, 0, 1);
196 return cal.getTime();
197 }
198 catch (final NumberFormatException e) {
199 e.printStackTrace();
200 throw new MainValuesServiceException("invalid start year"); // should not happen
201 }
202 }
203
204 private Document buildDocument(final GaugeInfoResult result, final GlobalContext context, final CallMeta meta) {
205
206 final Document doc = XMLUtils.newDocument();
207
208 final ElementCreator cr = new ElementCreator(doc, ArtifactNamespaceContext.NAMESPACE_URI, ArtifactNamespaceContext.NAMESPACE_PREFIX);
209
210 final Element rootEl = cr.create(ROOT_NODE);
211
212 doc.appendChild(rootEl);
213
214 final Element globalErrElement = cr.create("global-error-msg");
215 globalErrElement.setTextContent(result.globalErrorMsg);
216 rootEl.appendChild(globalErrElement);
217
218 final List<GaugeInfoResult.GaugeInfo> values = result.gaugeInfos;
219
220 for (final GaugeInfoResult.GaugeInfo gauge : values) {
221 final Element gaugeElement = cr.create("gauge");
222 cr.addAttr(gaugeElement, "name", gauge.gauge.getName());
223 if (gauge.startdate != null)
224 cr.addAttr(gaugeElement, "date-from", String.valueOf(gauge.startdate.getTime()));
225
226 if (gauge.enddate != null)
227 cr.addAttr(gaugeElement, "date-to", String.valueOf(gauge.enddate.getTime()));
228
229 if (gauge.errorMsg != null)
230 cr.addAttr(gaugeElement, "error-message", gauge.errorMsg);
231
232 rootEl.appendChild(gaugeElement);
233 }
234
235 return doc;
236
237 }
238
239 final Element buildElement(final ElementCreator cr, final String type, final Date date) {
240 final Element el = cr.create(type);
241 cr.addAttr(el, "value", String.valueOf(date.getTime()));
242 return el;
243 }
244
245 private static final List<Gauge> getRequestedGauges(final Document data, final River river, final CallMeta meta) throws ServiceException {
246
247 final NodeList gaugeNodes = data.getElementsByTagNameNS(ArtifactNamespaceContext.NAMESPACE_URI, "gauge");
248
249 final List<Gauge> gauges = new ArrayList<>();
250
251 for (int i = 0; i < gaugeNodes.getLength(); i++) {
252 final Element gaugeElt = (Element) gaugeNodes.item(i);
253
254 final String gaugeName = (String) XMLUtils.xpath(gaugeElt, "text()", XPathConstants.STRING);
255 final Gauge gauge = Gauge.getGaugeByNameAndRiver(gaugeName, river);
256 if (gauge != null)
257 gauges.add(gauge);
258 else {
259 throw new ServiceException("bundu_wst_error_reading_gauges");
260 }
261 }
262
263 return gauges;
264 }
265
266 private static String getMsg(final CallMeta meta, final String key) {
267 return Resources.getMsg(meta, key);
268 }
269 }

http://dive4elements.wald.intevation.org