diff --git a/src/main/java/no/nibio/vips/logic/controller/servlet/OrganizationController.java b/src/main/java/no/nibio/vips/logic/controller/servlet/OrganizationController.java index b10c713a6ce7bf20ec28db65a7ac945cc21fe658..ed2e9fd72e7dd666418d13a765304fdcdd98597b 100644 --- a/src/main/java/no/nibio/vips/logic/controller/servlet/OrganizationController.java +++ b/src/main/java/no/nibio/vips/logic/controller/servlet/OrganizationController.java @@ -29,6 +29,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import no.nibio.vips.gis.GISUtil; import no.nibio.vips.gis.LonLatStringFormatException; +import no.nibio.vips.logic.controller.session.PointOfInterestBean; import no.nibio.vips.logic.controller.session.UserBean; import no.nibio.vips.logic.entity.Organization; import no.nibio.vips.logic.entity.VipsLogicUser; @@ -46,6 +47,9 @@ public class OrganizationController extends HttpServlet { @EJB UserBean userBean; + + @EJB + PointOfInterestBean poiBean; /** * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods. @@ -101,6 +105,7 @@ public class OrganizationController extends HttpServlet { request.setAttribute("countries", userBean.getCountries()); request.setAttribute("timeZones", TimeZone.getAvailableIDs()); request.setAttribute("organization", organization); + request.setAttribute("gridWeatherDataSources", poiBean.getGridWeatherStationDataSources()); request.getRequestDispatcher("/organizationForm.ftl").forward(request, response); } else if(action.equals("organizationFormSubmit")) @@ -142,6 +147,11 @@ public class OrganizationController extends HttpServlet { : new GISUtil().getJtsPointFromString(formValidation.getFormField("defaultMapCenter").getWebValue()) ); + organization.setDefaultGridWeatherStationDataSource( + formValidation.getFormField("defaultGridWeatherStationDataSourceId").getValueAsInteger() < 0 ? + null + : poiBean.getWeatherStationDataSource(formValidation.getFormField("defaultGridWeatherStationDataSourceId").getValueAsInteger()) + ); organization = userBean.storeOrganization(organization); response.sendRedirect( diff --git a/src/main/java/no/nibio/vips/logic/controller/servlet/WeatherStationDataSourceController.java b/src/main/java/no/nibio/vips/logic/controller/servlet/WeatherStationDataSourceController.java new file mode 100644 index 0000000000000000000000000000000000000000..766d208f92d7924d4d5be151e124a313b34a2b05 --- /dev/null +++ b/src/main/java/no/nibio/vips/logic/controller/servlet/WeatherStationDataSourceController.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2024 NIBIO <http://www.nibio.no/>. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ +package no.nibio.vips.logic.controller.servlet; + +import java.io.IOException; + +import java.util.List; + +import javax.ejb.EJB; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import no.nibio.vips.gis.LonLatStringFormatException; +import no.nibio.vips.logic.controller.session.PointOfInterestBean; +import no.nibio.vips.logic.entity.VipsLogicUser; +import no.nibio.vips.logic.entity.WeatherStationDataSource; +import no.nibio.vips.logic.util.Globals; +import no.nibio.vips.logic.util.SystemTime; +import no.nibio.vips.util.ServletUtil; +import no.nibio.web.forms.FormValidation; +import no.nibio.web.forms.FormValidationException; +import no.nibio.web.forms.FormValidator;; + +/** + * Handles actions regarding listing and modifying weather station data sources + * + * @copyright 2024 <a href="http://www.nibio.no/">NIBIO</a> + * @author Tor-Einar Skog <tor-einar.skog@nibio.no> + */ +public class WeatherStationDataSourceController extends HttpServlet{ + + private static Logger LOGGER = LoggerFactory.getLogger(WeatherStationDataSourceController.class); + + @EJB + PointOfInterestBean poiBean; + + /** + * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods. + * @param request servlet request + * @param response servlet response + * @throws ServletException if a servlet-specific error occurs + * @throws IOException if an I/O error occurs + */ + protected void processRequest(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + response.setContentType("text/html;charset=UTF-8"); + + VipsLogicUser user = (VipsLogicUser) request.getSession().getAttribute("user"); + // Basic authorization + if(!user.isSuperUser() && ! user.isOrganizationAdmin()){ + response.sendError(403,"Access not authorized"); + return; + } + String action = request.getParameter("action"); + + if(action == null) + { + List<WeatherStationDataSource> wsDataSources = poiBean.getWeatherStationDataSources(); + request.setAttribute("messageKey", request.getParameter("messageKey") != null ? request.getParameter("messageKey") : null); + request.setAttribute("weatherStationDataSources", wsDataSources); + request.getRequestDispatcher("/weatherStationDataSourceList.ftl").forward(request, response); + } + else if(action.equals("editWeatherStationDataSource") || action.equals("newWeatherStationDataSource")) + { + WeatherStationDataSource weatherStationDataSource = null; + try + { + weatherStationDataSource = poiBean.getWeatherStationDataSource(Integer.valueOf(request.getParameter("weatherStationDataSourceId"))); + } + catch(NullPointerException | NumberFormatException ex){} + + if(weatherStationDataSource == null) + { + LOGGER.debug("Could not find weather data source with ID = " + request.getParameter("weatherStationDataSourceId")); + weatherStationDataSource = new WeatherStationDataSource(); + } + + request.setAttribute("messageKey", request.getParameter("messageKey") != null ? request.getParameter("messageKey") : null); + request.setAttribute("weatherStationDataSource", weatherStationDataSource); + request.getRequestDispatcher("/weatherStationDataSourceForm.ftl").forward(request, response); + } + else if(action.equals("weatherStationDataSourceFormSubmit")) + { + try + { + // Check that the provided id is either -1 or it already exists + Integer weatherStationDataSourceId = null; + WeatherStationDataSource weatherStationDataSource; + try + { + weatherStationDataSourceId = Integer.valueOf(request.getParameter("weatherStationDataSourceId")); + } + catch(NullPointerException | NumberFormatException ex) + { + response.sendError(400,"Wrong format of id for weather station data source"); + } + if(weatherStationDataSourceId < 0) + { + weatherStationDataSource = new WeatherStationDataSource(); + } + else + { + weatherStationDataSource = poiBean.getWeatherStationDataSource(weatherStationDataSourceId); + if(weatherStationDataSource == null) + { + response.sendError(404,"Could not find weather station data source with id=" + weatherStationDataSourceId); + } + } + FormValidation formValidation = FormValidator.validateForm("weatherStationDataSourceForm", request, getServletContext()); + if(formValidation.isValid()) + { + weatherStationDataSource.setName(formValidation.getFormField("name").getWebValue()); + weatherStationDataSource.setDefaultDescription(formValidation.getFormField("defaultDescription").getWebValue()); + weatherStationDataSource.setUri(formValidation.getFormField("uri").getWebValue()); + weatherStationDataSource.setDatafetchUriExpression(formValidation.getFormField("datafetchUriExpression").getWebValue()); + weatherStationDataSource.setInfoUriExpression(formValidation.getFormField("infoUriExpression").getWebValue()); + weatherStationDataSource.setIsGrid(formValidation.getFormField("isGrid").getWebValue() != null); + weatherStationDataSource = poiBean.storeWeatherStationDataSource(weatherStationDataSource); + + response.sendRedirect( + Globals.PROTOCOL + "://" + ServletUtil.getServerName(request) + + "/weatherstationdatasource?action=editWeatherStationDataSource&weatherStationDataSourceId=" + weatherStationDataSource.getWeatherStationDataSourceId() + + "&messageKey=weatherStationDataSourceStored" + ); + } + else + { + request.setAttribute("formValidation", formValidation); + request.setAttribute("weatherStationDataSource", weatherStationDataSource); + LOGGER.debug("Form NOT valid. Dispatching"); + request.getRequestDispatcher("/weatherStationDataSourceForm.ftl").forward(request, response); + } + } + catch(FormValidationException ex) + { + ex.printStackTrace(); + response.sendError(500, ex.getClass().toString() + ": " + ex.getMessage()); + } + } + else if(action.equals("deleteWeatherStationDataSource")) + { + + try + { + Integer weatherStationDataSourceId = Integer.valueOf(request.getParameter("weatherStationDataSourceId")); + WeatherStationDataSource weatherStationDataSource = poiBean.getWeatherStationDataSource(weatherStationDataSourceId); + if(weatherStationDataSource == null) + { + response.sendError(404,"Could not find weather station data source with id=" + weatherStationDataSourceId); + } + // Check: Can it be deleted? Not if organizations or weatherStations refer to it + if(poiBean.isweatherStationDataSourceDeleteable(weatherStationDataSource)) + { + poiBean.deleteWeatherStationDataSource(weatherStationDataSource); + // Redirect to list from which the deleted item has been removed + response.sendRedirect( + Globals.PROTOCOL + "://" + ServletUtil.getServerName(request) + + "/weatherstationdatasource?messageKey=weatherStationDataSourceDeleted" + ); + } + else + { + response.sendError(403,weatherStationDataSource.getName() + " is in use by one or more organizations, so it cannot be deleted. Hit the browser's back button return to your form."); + } + } + catch(NullPointerException | NumberFormatException ex) + { + response.sendError(400,"Wrong format of id for weather station data source"); + } + + + } + } + + // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code."> + /** + * Handles the HTTP <code>GET</code> method. + * @param request servlet request + * @param response servlet response + * @throws ServletException if a servlet-specific error occurs + * @throws IOException if an I/O error occurs + */ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + processRequest(request, response); + } + + /** + * Handles the HTTP <code>POST</code> method. + * @param request servlet request + * @param response servlet response + * @throws ServletException if a servlet-specific error occurs + * @throws IOException if an I/O error occurs + */ + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + processRequest(request, response); + } + + /** + * Returns a short description of the servlet. + * @return a String containing servlet description + */ + @Override + public String getServletInfo() { + return "Short description"; + }// </editor-fold> +} diff --git a/src/main/java/no/nibio/vips/logic/controller/session/PointOfInterestBean.java b/src/main/java/no/nibio/vips/logic/controller/session/PointOfInterestBean.java index 7c82e925fe6a7c595ecd996b5fbbcab09b7f0182..22229cf81bc468edeed0512b3f2a87923bec3c42 100755 --- a/src/main/java/no/nibio/vips/logic/controller/session/PointOfInterestBean.java +++ b/src/main/java/no/nibio/vips/logic/controller/session/PointOfInterestBean.java @@ -263,10 +263,54 @@ public class PointOfInterestBean { return kml; } + public WeatherStationDataSource getWeatherStationDataSource(Integer weatherStationDataSourceId) + { + return em.find(WeatherStationDataSource.class, weatherStationDataSourceId); + } + public List<WeatherStationDataSource> getWeatherStationDataSources() { return em.createNamedQuery("WeatherStationDataSource.findAll").getResultList(); } + public List<WeatherStationDataSource> getGridWeatherStationDataSources() { + return em.createNamedQuery("WeatherStationDataSource.findGridSources").getResultList(); + } + + /** + * Checks if the weather station data source can be deleted from the system. Criteria: + * <ul> + * <li>Not referenced from public.point_of_interest_weather_station</li> + * <li>Not referenced from public.organization</li> + * </ul> + * @param weatherStationDataSource + * @return + */ + public Boolean isweatherStationDataSourceDeleteable(WeatherStationDataSource weatherStationDataSource) + { + Query poiRefQuery = em.createQuery("SELECT COUNT(*) FROM PointOfInterestWeatherStation poiws where poiws.weatherStationDataSourceId = :weatherStationDataSourceId"); + Long weatherStationReferences = (Long) poiRefQuery.setParameter("weatherStationDataSourceId", weatherStationDataSource).getSingleResult(); + if(weatherStationReferences > 0) + { + return false; + } + + Query orgRefQuery = em.createQuery("SELECT COUNT(*) FROM Organization o where o.defaultGridWeatherStationDataSource = :weatherStationDataSourceId"); + Long organizationReferences = (Long) orgRefQuery.setParameter("weatherStationDataSourceId", weatherStationDataSource).getSingleResult(); + return organizationReferences == 0; + } + + public void deleteWeatherStationDataSource(WeatherStationDataSource weatherStationDataSource) + { + WeatherStationDataSource sourceToDelete = em.find(WeatherStationDataSource.class, weatherStationDataSource.getWeatherStationDataSourceId()); + em.remove(sourceToDelete); + } + + public WeatherStationDataSource storeWeatherStationDataSource(WeatherStationDataSource weatherStationDataSource) + { + weatherStationDataSource = em.merge(weatherStationDataSource); + return weatherStationDataSource; + } + public PointOfInterestWeatherStation storeWeatherStation(PointOfInterestWeatherStation weatherStation) { weatherStation = em.merge(weatherStation); return weatherStation; diff --git a/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java b/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java index 735eebd16da0098531a68965e87403fe1994bbb1..7be6c11a3b2a35155dc61cf1421ca573a8c7dfba 100755 --- a/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java +++ b/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java @@ -257,6 +257,29 @@ public class ForecastConfiguration implements Serializable, Comparable { * @return the weatherStationPointOfInterestId */ public PointOfInterest getWeatherStationPointOfInterestId() { + if( this.getUseGridWeatherData() && this.getVipsLogicUserId().getOrganizationId().getDefaultGridWeatherStationDataSource() != null) + { + // Create a "weather station" with coordinates from the location + // And the default grid weather data source for the current organization (get location owner's organization) + PointOfInterestWeatherStation gridStation = new PointOfInterestWeatherStation(); + gridStation.setLatitude(this.getLocationPointOfInterestId().getLatitude()); + gridStation.setLongitude(this.getLocationPointOfInterestId().getLongitude()); + gridStation.setName("GRID-punkt for " + this.getLocationPointOfInterestId().getName()); // TODO Translate!!! + gridStation.setTimeZone(this.getLocationPointOfInterestId().getTimeZone()); + gridStation.setWeatherStationDataSourceId(this.getVipsLogicUserId().getOrganizationId().getDefaultGridWeatherStationDataSource()); + gridStation.setUser(this.getVipsLogicUserId()); + gridStation.setWeatherForecastProviderId(null); + gridStation.setWeatherStationRemoteId(gridStation.getLongitude() + "_" + gridStation.getLatitude()); + gridStation.setGisGeom(this.getLocationPointOfInterestId().getGisGeom()); + gridStation.setAltitude(this.getLocationPointOfInterestId().getAltitude()); + gridStation.setCountryCode(this.getLocationPointOfInterestId().getCountryCode()); + gridStation.setIsForecastLocation(true); + gridStation.setPointOfInterestTypeId(PointOfInterestType.POINT_OF_INTEREST_TYPE_WEATHER_STATION); + gridStation.setUser(this.getVipsLogicUserId()); + gridStation.setProperties(this.getLocationPointOfInterestId().getProperties()); + return gridStation; + } + return weatherStationPointOfInterestId; } diff --git a/src/main/java/no/nibio/vips/logic/entity/Organization.java b/src/main/java/no/nibio/vips/logic/entity/Organization.java index 4e5d1bd8be98829b6d203075f4f563e9e030607b..4e5d7e67b0154adde1a7b7ca33c8fba2bd6b438f 100755 --- a/src/main/java/no/nibio/vips/logic/entity/Organization.java +++ b/src/main/java/no/nibio/vips/logic/entity/Organization.java @@ -111,6 +111,21 @@ public class Organization implements Serializable { @OneToOne @JsonIgnore private VipsLogicUser archiveUser; + + @JoinColumn(name = "default_grid_weather_station_data_source_id", referencedColumnName = "weather_station_data_source_id") + @ManyToOne + private WeatherStationDataSource defaultGridWeatherStationDataSource; + + public WeatherStationDataSource getDefaultGridWeatherStationDataSource() { + return defaultGridWeatherStationDataSource; + } + + public void setDefaultGridWeatherStationDataSource(WeatherStationDataSource defaultGridWeatherStationDataSource) { + this.defaultGridWeatherStationDataSource = defaultGridWeatherStationDataSource; + } + + + public Organization() { } diff --git a/src/main/java/no/nibio/vips/logic/entity/WeatherStationDataSource.java b/src/main/java/no/nibio/vips/logic/entity/WeatherStationDataSource.java index 9fe247f205036fd792eebe65ab0e6342a2625510..b31bd3aad0c4a63cf73652253fc942660688f974 100755 --- a/src/main/java/no/nibio/vips/logic/entity/WeatherStationDataSource.java +++ b/src/main/java/no/nibio/vips/logic/entity/WeatherStationDataSource.java @@ -40,6 +40,7 @@ import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement @NamedQueries({ @NamedQuery(name = "WeatherStationDataSource.findAll", query = "SELECT w FROM WeatherStationDataSource w"), + @NamedQuery(name = "WeatherStationDataSource.findGridSources", query = "SELECT w FROM WeatherStationDataSource w WHERE w.isGrid IS TRUE"), @NamedQuery(name = "WeatherStationDataSource.findByWeatherStationDataSourceId", query = "SELECT w FROM WeatherStationDataSource w WHERE w.weatherStationDataSourceId = :weatherStationDataSourceId"), @NamedQuery(name = "WeatherStationDataSource.findByName", query = "SELECT w FROM WeatherStationDataSource w WHERE w.name = :name"), @NamedQuery(name = "WeatherStationDataSource.findByDefaultDescription", query = "SELECT w FROM WeatherStationDataSource w WHERE w.defaultDescription = :defaultDescription"), @@ -68,6 +69,18 @@ public class WeatherStationDataSource implements Serializable { @Size(max = 1024) @Column(name = "info_uri_expression") private String infoUriExpression; + @Column(name = "is_grid") + private Boolean isGrid; + + + + public Boolean getIsGrid() { + return isGrid; + } + + public void setIsGrid(Boolean isGrid) { + this.isGrid = isGrid; + } public WeatherStationDataSource() { } diff --git a/src/main/java/no/nibio/vips/logic/scheduling/VipsLogicTaskFactory.java b/src/main/java/no/nibio/vips/logic/scheduling/VipsLogicTaskFactory.java index 66ae454af9550f24ce0552d5de6bb17d9cd2e87f..bf4bb80ee5e27f560c1ae23298fdc6b4385bc7c5 100755 --- a/src/main/java/no/nibio/vips/logic/scheduling/VipsLogicTaskFactory.java +++ b/src/main/java/no/nibio/vips/logic/scheduling/VipsLogicTaskFactory.java @@ -49,7 +49,7 @@ public class VipsLogicTaskFactory { public static final int RUN_FORECAST_CONFIGURATIONS_BY_ID_TASK = 9; - private final static int[] ALL_TASK_IDS = {1,2,3,4,5,6,7,8,9}; + private final static int[] ALL_TASK_IDS = {1,2,3,4,5,6,7,9}; private final static int[] ORGANIZATION_ADMIN_TASK_IDS = {7}; diff --git a/src/main/java/no/nibio/vips/logic/scheduling/model/preprocessor/AppleScabModelPreprocessor.java b/src/main/java/no/nibio/vips/logic/scheduling/model/preprocessor/AppleScabModelPreprocessor.java index 374785eb0666c660d5b7d2800a67beb629064f17..230ffcb73ab8db055e90f5b57b72005caf1551c1 100755 --- a/src/main/java/no/nibio/vips/logic/scheduling/model/preprocessor/AppleScabModelPreprocessor.java +++ b/src/main/java/no/nibio/vips/logic/scheduling/model/preprocessor/AppleScabModelPreprocessor.java @@ -27,6 +27,10 @@ import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.TimeZone; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import no.nibio.vips.entity.ModelConfiguration; import no.nibio.vips.entity.WeatherObservation; import no.nibio.vips.logic.entity.ForecastConfiguration; @@ -48,17 +52,13 @@ import no.nibio.vips.util.weather.WeatherDataSourceUtil; * @author Tor-Einar Skog <tor-einar.skog@nibio.no> */ public class AppleScabModelPreprocessor extends ModelRunPreprocessor{ - - private final boolean DEBUG = false; + private static Logger LOGGER = LoggerFactory.getLogger(AppleScabModelPreprocessor.class); public final static String APPLESCABM_START_DATE_ASCOSPORE_MATURITY = "APPLESCABM_START_DATE_ASCOSPORE_MATURITY"; @Override public ModelConfiguration getModelConfiguration(ForecastConfiguration configuration) throws PreprocessorException { - if(DEBUG) - { - System.out.println("getModelConfiguration"); - } + LOGGER.debug("AppleScabModelPreprocessor.getModelConfiguration() called"); //configuration.getDateStart(); PointOfInterestWeatherStation weatherStation = (PointOfInterestWeatherStation) configuration.getWeatherStationPointOfInterestId(); // What timezone is the calculation for @@ -92,10 +92,8 @@ public class AppleScabModelPreprocessor extends ModelRunPreprocessor{ // Use Jackson to parse JSON from server // Weather data collections - if(DEBUG) - { - System.out.println("Getting weather data at " + new Date().toString()); - } + LOGGER.debug("Getting weather data at " + new Date().toString()); + WeatherDataSourceUtil wdsUtil = new WeatherDataSourceUtil(); List<WeatherObservation> observations; try { @@ -135,22 +133,17 @@ public class AppleScabModelPreprocessor extends ModelRunPreprocessor{ // We do nothing } - - if(DEBUG) - { - System.out.println("Finished getting weather data at " + new Date().toString()); - } + LOGGER.debug("Finished getting weather data at " + new Date().toString()); + try { observations = validateAndSanitizeObservations(observations, startDateAscosporeMaturity); } catch (ConfigValidationException | WeatherObservationListException ex) { //ex.printStackTrace(); throw new PreprocessorException(ex.getMessage()); } - if(DEBUG) - { - System.out.println("Observations=" + observations.toString()); - } + //LOGGER.debug("Observations=" + observations.toString()); + // Create the complete model configuration object ModelConfiguration retVal = new ModelConfiguration(); @@ -176,11 +169,7 @@ public class AppleScabModelPreprocessor extends ModelRunPreprocessor{ * @throws WeatherObservationListException */ private List<WeatherObservation> validateAndSanitizeObservations(List<WeatherObservation> observations, Date firstTimeStamp) throws ConfigValidationException, WeatherObservationListException { - if(DEBUG) - { - System.out.println("validateAndSanitizeObservations"); - } - + WeatherUtil wUtil = new WeatherUtil(); // First we remove all duplicates @@ -241,14 +230,7 @@ public class AppleScabModelPreprocessor extends ModelRunPreprocessor{ // Problems with weather observations - // Holes in series - if(DEBUG) - { - System.out.println("checkForAndFixHourlyTimeSeriesHoles"); - //System.out.println(wUtil.dumpWeatherObservationList(RR)); - } - - + // Unequal length of lists if ( @@ -257,6 +239,7 @@ public class AppleScabModelPreprocessor extends ModelRunPreprocessor{ || RR.size() != TM.size() ) { + LOGGER.debug("Unequal lists lengt: RR=" + RR.size() + ", TM=" + TM.size() + ", BT=" + BT.size()); UM = wUtil.fixHourlyValuesForParameters( UM, new HashSet(Arrays.asList("UM")), diff --git a/src/main/java/no/nibio/vips/logic/scheduling/tasks/RunAllForecastConfigurationsTask.java b/src/main/java/no/nibio/vips/logic/scheduling/tasks/RunAllForecastConfigurationsTask.java index 7a5cf687a4e6fa2e3e9c1de4827fe9b4c94b3953..b09788237b49bb869d77879b7201ccc9d72a1ca8 100755 --- a/src/main/java/no/nibio/vips/logic/scheduling/tasks/RunAllForecastConfigurationsTask.java +++ b/src/main/java/no/nibio/vips/logic/scheduling/tasks/RunAllForecastConfigurationsTask.java @@ -24,6 +24,10 @@ import java.util.Collections; import java.util.List; import java.util.Map; import javax.ejb.EJB; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import no.nibio.vips.i18n.I18nImpl; import no.nibio.vips.logic.controller.session.ForecastBean; import no.nibio.vips.logic.controller.session.PointOfInterestBean; @@ -46,6 +50,8 @@ import no.nibio.web.forms.FormField; * @author Tor-Einar Skog <tor-einar.skog@nibio.no> */ public class RunAllForecastConfigurationsTask extends VipsLogicTask{ + + private static Logger LOGGER = LoggerFactory.getLogger(RunAllForecastConfigurationsTask.class); private I18nImpl i18n; //private boolean DEBUG=true; @@ -91,24 +97,30 @@ public class RunAllForecastConfigurationsTask extends VipsLogicTask{ noForecastConfigurationsFound = false; for(ForecastConfiguration forecastConfiguration:currentForecastConfigurations) { - if( - weatherStationPointOfInterestId == null - || weatherStationPointOfInterestId <= 0 - || (forecastConfiguration.getWeatherStationPointOfInterestId() != null && forecastConfiguration.getWeatherStationPointOfInterestId().getPointOfInterestId().equals(weatherStationPointOfInterestId)) + if(forecastConfiguration.getUseGridWeatherData() && forecastConfiguration.getWeatherStationPointOfInterestId() == null) + { + errorMessage.append( + SchedulingUtil.createSchedulingMessageHTML( + "Error with forecast #" + forecastConfiguration.getForecastConfigurationId() + " (" + forecastConfiguration.getLocationPointOfInterestId().getName() + " - " + modelInformationMap.get(forecastConfiguration.getModelId()).getDefaultName() + ")", + "The forecast is configured to use gridded weather data, but the organization " + forecastConfiguration.getVipsLogicUserId().getOrganizationId().getOrganizationName() + " has not set its gridded weather data source. Please contact the system administrator.", + SchedulingUtil.MESSAGE_STATUS_WARNING ) + ); + totalNumberofForecastConfigurations++; + } + else if( + weatherStationPointOfInterestId == null + || weatherStationPointOfInterestId <= 0 + || (forecastConfiguration.getWeatherStationPointOfInterestId() != null && forecastConfiguration.getWeatherStationPointOfInterestId().getPointOfInterestId().equals(weatherStationPointOfInterestId)) + ) + { try { totalNumberofForecastConfigurations++; - //System.out.println("Running forecast #" + forecastConfiguration.getForecastConfigurationId()); + LOGGER.debug("Running forecast #" + forecastConfiguration.getForecastConfigurationId()); SessionControllerGetter.getForecastBean().runForecast(forecastConfiguration); - /* - if(DEBUG && totalNumberofForecastConfigurations == 2) - { - throw new RunModelException("This is a test!!!"); - }*/ numberOfCompletedForecastConfigurations++; - //System.out.println("All went well"); } catch (PreprocessorException | RunModelException ex) { @@ -119,38 +131,32 @@ public class RunAllForecastConfigurationsTask extends VipsLogicTask{ ex.getMessage(), SchedulingUtil.MESSAGE_STATUS_DANGER) ); - //System.out.println("########################### Error caught: " + errorMessage); - //System.out.println("numberOfCompletedForecastConfigurations=" + numberOfCompletedForecastConfigurations); - //System.out.println("totalNumberofForecastConfigurations=" + totalNumberofForecastConfigurations); - //continue; } } if(totalNumberofForecastConfigurations > 0) { noForecastConfigurationsFound = false; - double completeness = (double) numberOfCompletedForecastConfigurations/totalNumberofForecastConfigurations; - tec.setCompleteness(completeness); + tec.setCompleteness(Double.valueOf(numberOfCompletedForecastConfigurations)/Double.valueOf(totalNumberofForecastConfigurations)); } else { noForecastConfigurationsFound = true; - //System.out.println("noForecastConfigurationsFound == true!!"); + } - //System.out.println("Current completeness=" + tec.getTaskExecutor().getCompleteness()); } } } + if(noForecastConfigurationsFound) { tec.setCompleteness(1.0); tec.setStatusMessage("No current forecast configurations were found"); } - //System.out.println("Total completeness=" + tec.getTaskExecutor().getCompleteness()); - if(tec.getTaskExecutor().getCompleteness() != 1.0) + if(tec.getTaskExecutor().getCompleteness() != 1.0 || ! errorMessage.isEmpty()) { //System.out.println("Error detected, RuntimeException thrown just after this"); tec.setStatusMessage(errorMessage.toString()); diff --git a/src/main/resources/db/migration/V19__Add_Organization_Property_Default_Grid_DataSource.sql b/src/main/resources/db/migration/V19__Add_Organization_Property_Default_Grid_DataSource.sql new file mode 100644 index 0000000000000000000000000000000000000000..b254b3e45832acef2b0e85af03c170362322435b --- /dev/null +++ b/src/main/resources/db/migration/V19__Add_Organization_Property_Default_Grid_DataSource.sql @@ -0,0 +1,3 @@ +-- Adding this property when adding support for gridded weather datasources in VIPS +ALTER TABLE organization +ADD COLUMN default_grid_weather_station_data_source_id INTEGER REFERENCES weather_station_data_source(weather_station_data_source_id) DEFAULT NULL; \ No newline at end of file diff --git a/src/main/resources/db/migration/V20__Add_Datasource_Property_IsGrid.sql b/src/main/resources/db/migration/V20__Add_Datasource_Property_IsGrid.sql new file mode 100644 index 0000000000000000000000000000000000000000..468128cbcf53a0e66d5b8373095debb8491592b7 --- /dev/null +++ b/src/main/resources/db/migration/V20__Add_Datasource_Property_IsGrid.sql @@ -0,0 +1,3 @@ +-- Adding this property when adding support for gridded weather datasources in VIPS +ALTER TABLE weather_station_data_source +ADD COLUMN is_grid BOOLEAN DEFAULT FALSE; \ No newline at end of file diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties index e7caec3ae87ef744e17f826821f0ca3da4b91607..d1b535a4cc9b8bfe6cb4e23143f9a04c0edefe1f 100755 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties @@ -1056,3 +1056,12 @@ thresholdDSVMax=DSV threshold for high infection risk thresholdDSVTempMin=Minimum temperature for DSV calculation useGridWeatherData=Use grid weather data doNotUse=Do not use +defaultGridWeatherStationDataSource=Gridded weather data source +weatherStationDataSources=Weather station data sources +newWeatherStationDataSource=New weather (station) data source +editWeatherStationDataSource=Edit weather (station) data source +datafetchUriExpression=URI template for requesting data +infoUriExpression=Template for request for station information +isGridWeatherDataSource=This is a grid based weather data source +weatherStationDataSourceStored=Weather (station) data source was successfully stored +weatherStationDataSourceDeleted=The weather (station) data source was successfully deleted diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties index b632979d4bef2c6f99f1b0a2908e54e7ceee5671..69fb5f1d3cda2371e2ac60f20c77b441c431eb66 100755 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties @@ -1048,3 +1048,12 @@ thresholdDSVMax=DSV threshold for high infection risk thresholdDSVTempMin=Minimum temperature for DSV calculation useGridWeatherData=Use grid weather data doNotUse=Do not use +defaultGridWeatherStationDataSource=Gridded weather data source +weatherStationDataSources=Weather station data sources +newWeatherStationDataSource=New weather (station) data source +editWeatherStationDataSource=Edit weather (station) data source +datafetchUriExpression=URI template for requesting data +infoUriExpression=Template for request for station information +isGridWeatherDataSource=This is a grid based weather data source +weatherStationDataSourceStored=Weather (station) data source was successfully stored +weatherStationDataSourceDeleted=The weather (station) data source was successfully deleted diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties index 286433a39e854e88ffbc69e3174ea1a676fa5e55..fcde6d89ad112ea828d6f8663fcb3e1def2c91a2 100755 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties @@ -1046,3 +1046,12 @@ thresholdDSVMax=DSV threshold for high infection risk thresholdDSVTempMin=Minimum temperature for DSV calculation useGridWeatherData=Use grid weather data doNotUse=Do not use +defaultGridWeatherStationDataSource=Gridded weather data source +weatherStationDataSources=Weather station data sources +newWeatherStationDataSource=New weather (station) data source +editWeatherStationDataSource=Edit weather (station) data source +datafetchUriExpression=URI template for requesting data +infoUriExpression=Template for request for station information +isGridWeatherDataSource=This is a grid based weather data source +weatherStationDataSourceStored=Weather (station) data source was successfully stored +weatherStationDataSourceDeleted=The weather (station) data source was successfully deleted diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties index 1d3db33c3426184e4df91b2ccd1dca2d3e632b81..bc7b59b08ae5e993f8616d0b36d54d476f6b67e5 100755 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties @@ -1056,3 +1056,12 @@ thresholdDSVMax=DSV-terskel for h\u00f8y infeksjonsrisiko thresholdDSVTempMin=Minimumstemperatur for beregning av DSV useGridWeatherData=Bruk v\u00e6rdata fra rutenett doNotUse=Ikke bruk +defaultGridWeatherStationDataSource=GRID-basert v\u00e6rdatakilde +weatherStationDataSources=V\u00e6r(stasjons)datakilder +newWeatherStationDataSource=Ny v\u00e6r(stasjons)datakilde +editWeatherStationDataSource=Rediger v\u00e6r(stasjons)datakilde +datafetchUriExpression=URI-mal for v\u00e6rdataforesp\u00f8rsel +infoUriExpression=Mal for henvendelse om stasjonsinformasjon +isGridWeatherDataSource=Er en grid-basert v\u00e6rdatakilde +weatherStationDataSourceStored=V\u00e6r(stasjons)datakilden ble lagret +weatherStationDataSourceDeleted=V\u00e6r(stasjons)datakilden ble slettet diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties index 044f7b23e28adf500929451368da17dfde0b3908..7eb7473abecd9c43cc4a7c7829f13ae9cb3c4052 100755 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties @@ -1048,3 +1048,12 @@ thresholdDSVMax=DSV threshold for high infection risk thresholdDSVTempMin=Minimum temperature for DSV calculation useGridWeatherData=Use grid weather data doNotUse=Do not use +defaultGridWeatherStationDataSource=Gridded weather data source +weatherStationDataSources=Weather station data sources +newWeatherStationDataSource=New weather (station) data source +editWeatherStationDataSource=Edit weather (station) data source +datafetchUriExpression=URI template for requesting data +infoUriExpression=Template for request for station information +isGridWeatherDataSource=This is a grid based weather data source +weatherStationDataSourceStored=Weather (station) data source was successfully stored +weatherStationDataSourceDeleted=The weather (station) data source was successfully deleted diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties index 66a5fd1c01511447b57bb0470e725877b76641f8..8e0f57063b6cf713ffe34b4cfc16e42d4e119cfc 100755 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties @@ -1042,3 +1042,12 @@ thresholdDSVMax=DSV threshold for high infection risk thresholdDSVTempMin=Minimum temperature for DSV calculation useGridWeatherData=Use grid weather data doNotUse=Do not use +defaultGridWeatherStationDataSource=Gridded weather data source +weatherStationDataSources=Weather station data sources +newWeatherStationDataSource=New weather (station) data source +editWeatherStationDataSource=Edit weather (station) data source +datafetchUriExpression=URI template for requesting data +infoUriExpression=Template for request for station information +isGridWeatherDataSource=This is a grid based weather data source +weatherStationDataSourceStored=Weather (station) data source was successfully stored +weatherStationDataSourceDeleted=The weather (station) data source was successfully deleted diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 62d85f54fb23655a8bafb80e6f02925b270da5a8..2f2ca6f05c3225d9c37a4a9b10fd4695bd726ee3 100755 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -79,6 +79,10 @@ <servlet-name>CropCategoryController</servlet-name> <servlet-class>no.nibio.vips.logic.controller.servlet.CropCategoryController</servlet-class> </servlet> + <servlet> + <servlet-name>WeatherStationDataSourceController</servlet-name> + <servlet-class>no.nibio.vips.logic.controller.servlet.WeatherStationDataSourceController</servlet-class> + </servlet> <servlet> <servlet-name>JSEnvironment</servlet-name> <servlet-class>no.nibio.vips.logic.web.js.JSEnvironment</servlet-class> @@ -156,6 +160,10 @@ <servlet-name>CropCategoryController</servlet-name> <url-pattern>/organism/cropcategory</url-pattern> </servlet-mapping> + <servlet-mapping> + <servlet-name>WeatherStationDataSourceController</servlet-name> + <url-pattern>/weatherstationdatasource</url-pattern> + </servlet-mapping> <servlet-mapping> <servlet-name>JSEnvironment</servlet-name> <url-pattern>/js/environment.js</url-pattern> diff --git a/src/main/webapp/formdefinitions/organizationForm.json b/src/main/webapp/formdefinitions/organizationForm.json index acc63d9e460de6b61f08b83b7870926d11bfce3f..4ff9c999cf3cdd406e5b6db626f46b8564ebdf61 100644 --- a/src/main/webapp/formdefinitions/organizationForm.json +++ b/src/main/webapp/formdefinitions/organizationForm.json @@ -95,6 +95,12 @@ "name" : "defaultMapCenter", "dataType" : "STRING", "required" : false + }, + { + "name": "defaultGridWeatherStationDataSourceId", + "dataType": "INTEGER", + "fieldType": "SELECT_SINGLE", + "required": false } ] diff --git a/src/main/webapp/formdefinitions/weatherStationDataSourceForm.json b/src/main/webapp/formdefinitions/weatherStationDataSourceForm.json new file mode 100644 index 0000000000000000000000000000000000000000..35cf480345f1f784dba1cd19c2b555b553245fbe --- /dev/null +++ b/src/main/webapp/formdefinitions/weatherStationDataSourceForm.json @@ -0,0 +1,59 @@ +{ + "_licenseNote": [ + "Copyright (c) 2024 NIBIO <http://www.nibio.no/>. ", + "", + "This file is part of VIPSLogic. ", + + "This program is free software: you can redistribute it and/or modify", + "it under the terms of the GNU Affero General Public License as published by", + "the Free Software Foundation, either version 3 of the License, or", + "(at your option) any later version.", + + "This program is distributed in the hope that it will be useful,", + "but WITHOUT ANY WARRANTY; without even the implied warranty of", + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the", + "GNU Affero General Public License for more details.", + + "You should have received a copy of the GNU Affero General Public License", + "along with this program. If not, see <https://www.gnu.org/licenses/>." + ], + "_comment" : "Structure of the organizationForm and how to validate it", + "fields": [ + { + "name" : "weatherStationDataSourceId", + "dataType" : "INTEGER", + "required" : true + }, + { + "name" : "name", + "dataType" : "STRING", + "required" : true + }, + { + "name" : "defaultDescription", + "dataType" : "STRING", + "required" : false + }, + { + "name" : "uri", + "dataType" : "STRING", + "required" : false + }, + { + "name" : "datafetchUriExpression", + "dataType" : "STRING", + "required" : true + }, + { + "name" : "infoUriExpression", + "dataType" : "STRING", + "required" : false + }, + { + "name" : "isGrid", + "dataType" : "STRING", + "required" : false + } + + ] +} diff --git a/src/main/webapp/templates/forecastConfigurationForm.ftl b/src/main/webapp/templates/forecastConfigurationForm.ftl index d1037a361da27fa37e7d337816b9d9f56b4ce0bd..c574fa7f0d5b5c9069bdfdc4945925cb955d5bb3 100755 --- a/src/main/webapp/templates/forecastConfigurationForm.ftl +++ b/src/main/webapp/templates/forecastConfigurationForm.ftl @@ -15,6 +15,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. --><#include "master.ftl"> +<#assign isGridForecastSupported = (user.organizationId.defaultGridWeatherStationDataSource?has_content)> <#macro page_head> <title>${i18nBundle.viewForecastConfiguration}</title> </#macro> @@ -316,7 +317,9 @@ }; // Setting weather station select list state correct on page load - handleUseGridWeatherDataClicked(document.getElementById("useGridWeatherData")<#if forecastConfiguration.weatherStationPointOfInterestId?has_content>,${forecastConfiguration.weatherStationPointOfInterestId.pointOfInterestId}</#if>); + <#if isGridForecastSupported> + handleUseGridWeatherDataClicked(document.getElementById("useGridWeatherData")<#if forecastConfiguration.weatherStationPointOfInterestId?has_content && forecastConfiguration.weatherStationPointOfInterestId.pointOfInterestId?has_content>,${forecastConfiguration.weatherStationPointOfInterestId.pointOfInterestId}</#if>); + </#if> </script> </#macro> @@ -421,23 +424,30 @@ <div class="form-group"> <label for="weatherStationPointOfInterestId">${i18nBundle.weatherStationPointOfInterestId}</label> <select class="form-control" id="weatherStationPointOfInterestId" name="weatherStationPointOfInterestId" onblur="if(!document.getElementById('useGridWeatherData').checked) {validateField(this);};"> - <option value="-2">${i18nBundle.doNotUse} ${i18nBundle.weatherStationPointOfInterestId?lower_case}</option> + <#if isGridForecastSupported> + <option value="-2">${i18nBundle.doNotUse} ${i18nBundle.weatherStationPointOfInterestId?lower_case}</option> + </#if> <option value="-1"<#if !forecastConfiguration.weatherStationPointOfInterestId?has_content && !forecastConfiguration.useGridWeatherData> selected="selected"</#if>>${i18nBundle.pleaseSelect} ${i18nBundle.weatherStationPointOfInterestId?lower_case}</option> <#list weatherStationPointOfInterests?sort_by("name") as poi> - <option value="${poi.pointOfInterestId}"<#if forecastConfiguration.weatherStationPointOfInterestId?has_content && poi.pointOfInterestId == forecastConfiguration.weatherStationPointOfInterestId.pointOfInterestId> selected="selected"</#if>>${poi.name}</option> + <option value="${poi.pointOfInterestId}"<#if + forecastConfiguration.weatherStationPointOfInterestId?has_content + && forecastConfiguration.weatherStationPointOfInterestId.pointOfInterestId?has_content + && poi.pointOfInterestId == forecastConfiguration.weatherStationPointOfInterestId.pointOfInterestId> selected="selected"</#if>>${poi.name}</option> </#list> </select> <span class="help-block" id="${formId}_weatherStationPointOfInterestId_validation"></span> </div> - <input type="hidden" id="weatherStationPointOfInterestIdHidden" name="weatherStationPointOfInterestIdDisabled" value="-2" disabled="disabled"/> - <div class="form-group"> - <div class="checkbox"> - <label> - <input type="checkbox" id="useGridWeatherData" name="useGridWeatherData"<#if forecastConfiguration.useGridWeatherData?has_content && forecastConfiguration.useGridWeatherData == true> checked="checked"</#if> onclick="handleUseGridWeatherDataClicked(this);"/> - </label> - ${i18nBundle.useGridWeatherData} - <span class="help-block" id="${formId}_useGridWeatherData_validation"></span> - </div> + <#if isGridForecastSupported> + <input type="hidden" id="weatherStationPointOfInterestIdHidden" name="weatherStationPointOfInterestIdDisabled" value="-2" disabled="disabled"/> + <div class="form-group"> + <div class="checkbox"> + <label> + <input type="checkbox" id="useGridWeatherData" name="useGridWeatherData"<#if forecastConfiguration.useGridWeatherData?has_content && forecastConfiguration.useGridWeatherData == true> checked="checked"</#if> onclick="handleUseGridWeatherDataClicked(this);"/> + </label> + ${i18nBundle.useGridWeatherData} + <span class="help-block" id="${formId}_useGridWeatherData_validation"></span> + </div> + </#if> <#else> <input type="hidden" name="multipleNew" value="true"/> <div class="form-group"> diff --git a/src/main/webapp/templates/master.ftl b/src/main/webapp/templates/master.ftl index 0027c08c231fb3c53e0a7141a1e240a62efbf4d2..712e6aafac85f7a6ec85871ea3f78bac317a28ec 100755 --- a/src/main/webapp/templates/master.ftl +++ b/src/main/webapp/templates/master.ftl @@ -59,6 +59,9 @@ <#else> <li><a href="/user?action=viewUser&userId=${user.userId}">${i18nBundle.myAccount}</a></li> </#if> + <#if user.isOrganizationAdmin() || user.isSuperUser()> + <li><a href="/weatherstationdatasource">${i18nBundle.weatherStationDataSources}</a></li> + </#if> <li><a href="/poi">${i18nBundle.pois}</a></li> <#if user.isOrganizationAdmin() || user.isSuperUser() || user.isMessageAuthor()> <li><a href="/message">${i18nBundle.messages}</a></li> diff --git a/src/main/webapp/templates/organizationForm.ftl b/src/main/webapp/templates/organizationForm.ftl index 50ac1afc38f3741305c4cec59726a0e8d09ee2c6..cb907e210e7a0fffae2d438bd39fc540d2b57633 100644 --- a/src/main/webapp/templates/organizationForm.ftl +++ b/src/main/webapp/templates/organizationForm.ftl @@ -119,7 +119,7 @@ <input type="text" class="form-control" name="vipswebUrl" placeholder="${i18nBundle.vipswebUrl}" value="${(organization.vipswebUrl)!""}" onblur="validateField(this); "/> <span class="help-block" id="${formId}_vipswebUrl_validation"></span> </div> - <div class="form-group"> + <div class="form-group"> <label for="archiveUserId">${i18nBundle.archiveUserId}</label> <select class="form-control" name="archiveUserId" onblur="validateField(this);"> <option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.archiveUserId?lower_case} @@ -131,21 +131,33 @@ </select> <span class="help-block" id="${formId}_archiveUserId_validation"></span> </div> - <div class="form-group"> + <div class="form-group"> <label for="defaultVipsCoreUserId">${i18nBundle.defaultVipsCoreUserId}</label> <input type="number" class="form-control" name="defaultVipsCoreUserId" placeholder="${i18nBundle.defaultVipsCoreUserId}" value="${(organization.defaultVipsCoreUserId)!""}" onblur="validateField(this); "/> <span class="help-block" id="${formId}_defaultVipsCoreUserId_validation"></span> </div> - <div class="form-group"> + <div class="form-group"> <label for="defaultMapZoom">${i18nBundle.defaultMapZoom}</label> <input type="number" class="form-control" name="defaultMapZoom" placeholder="${i18nBundle.defaultMapZoom}" value="${(organization.defaultMapZoom)!""}" onblur="validateField(this); "/> <span class="help-block" id="${formId}_defaultMapZoom_validation"></span> </div> - <div class="form-group"> + <div class="form-group"> <label for="defaultMapCenter">${i18nBundle.defaultMapCenter} (WGS84 longitude,latitude)</label> <input type="text" class="form-control" name="defaultMapCenter" placeholder="${i18nBundle.defaultMapCenter}" value="${(defaultMapCenterLon?c)!""},${(defaultMapCenterLat?c)!""}" onblur="validateField(this); "/> <span class="help-block" id="${formId}_defaultMapCenter_validation"></span> </div> + <div class="form-group"> + <label for="defaultGridWeatherStationDataSourceId">${i18nBundle.defaultGridWeatherStationDataSource}</label> + <select class="form-control" name="defaultGridWeatherStationDataSourceId" onblur="validateField(this);"> + <option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.defaultGridWeatherStationDataSource?lower_case} + <#list gridWeatherDataSources as dSource> + <option value="${dSource.weatherStationDataSourceId}" + <#if organization.defaultGridWeatherStationDataSource?has_content && organization.defaultGridWeatherStationDataSource.weatherStationDataSourceId == dSource.weatherStationDataSourceId>selected="selected"</#if> + >${dSource.name}</option> + </#list> + </select> + <span class="help-block" id="${formId}_defaultGridWeatherStationDataSourceId_validation"></span> + </div> <button type="submit" class="btn btn-default">${i18nBundle.submit}</button> <#if organization.organizationId?has_content> <button type="button" class="btn btn-danger" onclick="if(confirm('${i18nBundle.confirmDelete}')){alert('Sorry, but this functionality has not yet been implemented');}">${i18nBundle.delete}</button> diff --git a/src/main/webapp/templates/weatherStationDataSourceForm.ftl b/src/main/webapp/templates/weatherStationDataSourceForm.ftl new file mode 100644 index 0000000000000000000000000000000000000000..d386d502e3a02eaea22d83ad0139cc8254c53674 --- /dev/null +++ b/src/main/webapp/templates/weatherStationDataSourceForm.ftl @@ -0,0 +1,97 @@ +<#-- + Copyright (c) 2024 NIBIO <http://www.nibio.no/>. + + This file is part of VIPSLogic. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. +--><#include "master.ftl"> +<#assign formId = "weatherStationDataSourceForm"> +<#macro page_head> + <title><#if weatherStationDataSource.weatherStationDataSourceId?has_content>${i18nBundle.editWeatherStationDataSource} ${weatherStationDataSource.name}<#else>${i18nBundle.newWeatherStationDataSource}</#if></title> +</#macro> +<#macro custom_css> + <link rel="stylesheet" type="text/css" href="/css/3rdparty/ol.css"/ > +</#macro> +<#macro custom_js> + <script type="text/javascript" src="/js/constants.js"></script> + <script type="text/javascript" src="/js/resourcebundle.js"></script> + <script type="text/javascript" src="/js/validateForm.js"></script> + <script type="text/javascript"> + $(document).ready(function() { + // Load main form definition (for validation) + loadFormDefinition("${formId}"); + + }); + function handleWeatherStationDataSourceDelete(weatherStationDataSourceId){ + window.location.href="/weatherstationdatasource?action=deleteWeatherStationDataSource&weatherStationDataSourceId=" + weatherStationDataSourceId; + } + + </script> +</#macro> +<#macro page_contents> +<div class="singleBlockContainer"> + <p><a href="/weatherstationdatasource" class="btn btn-default back" role="button">${i18nBundle.back}</a></p> + <h1><#if weatherStationDataSource.weatherStationDataSourceId?has_content>${i18nBundle.editWeatherStationDataSource} ${weatherStationDataSource.name}<#else>${i18nBundle.newWeatherStationDataSource}</#if></h1> + <div id="errorMsgEl" class="alert alert-danger" <#if !formValidation?has_content> style="display:none;"</#if>> + <#if formValidation?has_content>${formValidation.validationMessages?replace("\n", "<br>")}</#if> + </div> + <#if messageKey?has_content> + <div class="alert alert-success">${i18nBundle(messageKey)}</div> + </#if> + <div class="row"> + <div class="col-md-12"> + <form id="${formId}" role="form" action="/weatherstationdatasource?action=weatherStationDataSourceFormSubmit" method="POST" onsubmit="return validateForm(this);"> + <input type="hidden" name="weatherStationDataSourceId" value="${weatherStationDataSource.weatherStationDataSourceId!"-1"}"/> + <div class="form-group"> + <label for="name">${i18nBundle.name}</label> + <input type="text" class="form-control" name="name" placeholder="${i18nBundle.name}" value="${(weatherStationDataSource.name)!""}" onblur="validateField(this); "/> + <span class="help-block" id="${formId}_name_validation"></span> + </div> + <div class="form-group"> + <label for="defaultDescription">${i18nBundle.description}</label> + <input type="text" class="form-control" name="defaultDescription" placeholder="${i18nBundle.description}" value="${(weatherStationDataSource.defaultDescription)!""}" onblur="validateField(this); "/> + <span class="help-block" id="${formId}_defaultDescription_validation"></span> + </div> + <div class="form-group"> + <label for="uri">URI</label> + <input type="url" class="form-control" name="uri" placeholder="URI" value="${(weatherStationDataSource.uri)!""}" onblur="validateField(this); "/> + <span class="help-block" id="${formId}_uri_validation"></span> + </div> + <div class="form-group"> + <label for="datafetchUriExpression">${i18nBundle.datafetchUriExpression}</label> + <input type="url" class="form-control" name="datafetchUriExpression" placeholder="${i18nBundle.datafetchUriExpression}" value="${(weatherStationDataSource.datafetchUriExpression)!""}" onblur="validateField(this); "/> + <span class="help-block" id="${formId}_datafetchUriExpression_validation"></span> + </div> + <div class="form-group"> + <label for="infoUriExpression">${i18nBundle.infoUriExpression}</label> + <input type="url" class="form-control" name="infoUriExpression" placeholder="${i18nBundle.infoUriExpression}" value="${(weatherStationDataSource.infoUriExpression)!""}" onblur="validateField(this); "/> + <span class="help-block" id="${formId}_infoUriExpression_validation"></span> + </div> + <div class="form-group"> + <div class="checkbox"> + <label> + <input type="checkbox" id="isGrid" name="isGrid" value="true"<#if weatherStationDataSource.isGrid?has_content && weatherStationDataSource.isGrid == true> checked="checked"</#if>/> + </label> + ${i18nBundle.isGridWeatherDataSource} + <span class="help-block" id="${formId}_isGrid_validation"></span> + </div> + <button type="submit" class="btn btn-default">${i18nBundle.submit}</button> + <#if weatherStationDataSource.weatherStationDataSourceId?has_content> + <button type="button" class="btn btn-danger" onclick="if(confirm('${i18nBundle.confirmDelete}')){handleWeatherStationDataSourceDelete(${weatherStationDataSource.weatherStationDataSourceId});}">${i18nBundle.delete}</button> + </#if> + </form> + </div> + </div> +</div> +</#macro> +<@page_html/> \ No newline at end of file diff --git a/src/main/webapp/templates/weatherStationDataSourceList.ftl b/src/main/webapp/templates/weatherStationDataSourceList.ftl new file mode 100644 index 0000000000000000000000000000000000000000..82c6b0eb62b9c386636027ee68973a20de718fd0 --- /dev/null +++ b/src/main/webapp/templates/weatherStationDataSourceList.ftl @@ -0,0 +1,39 @@ +<#-- + Copyright (c) 2024 NIBIO <http://www.nibio.no/>. + + This file is part of VIPSLogic. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. +--><#include "master.ftl"> +<#macro page_head> + <title>${i18nBundle.weatherStationDataSources}</title> +</#macro> +<#macro custom_css> +</#macro> +<#macro custom_js> +</#macro> +<#macro page_contents> +<div class="singleBlockContainer"> +<#if messageKey?has_content> + <div class="alert alert-success">${i18nBundle(messageKey)}</div> +</#if> +<h1>${i18nBundle.weatherStationDataSources}</h1> +<ul> +<#list weatherStationDataSources as dSource> +<li><a href="/weatherstationdatasource?action=editWeatherStationDataSource&weatherStationDataSourceId=${dSource.weatherStationDataSourceId}">${dSource.name}</a></li> +</#list> +</ul> +<p><a href="/weatherstationdatasource?action=newWeatherStationDataSource" class="btn btn-default back" role="button">${i18nBundle.newWeatherStationDataSource}</a></p> +</div> +</#macro> +<@page_html/>