diff --git a/pom.xml b/pom.xml index e372339da515df616f6d0ebcc41bc1d67186e204..52674d76843ca55e6a7e0cecf18991132f2aea52 100755 --- a/pom.xml +++ b/pom.xml @@ -279,7 +279,7 @@ <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> - <version>2.13.0</version> + <version>2.17.0</version> </dependency> </dependencies> diff --git a/src/main/java/no/nibio/vips/logic/authenticate/AuthenticationFilter.java b/src/main/java/no/nibio/vips/logic/authenticate/AuthenticationFilter.java index aee204c1c02e3a3362d02c81af8724b3db8d5346..475c68bf2df86d301dd46a0ef92440ac85adf73b 100755 --- a/src/main/java/no/nibio/vips/logic/authenticate/AuthenticationFilter.java +++ b/src/main/java/no/nibio/vips/logic/authenticate/AuthenticationFilter.java @@ -1,18 +1,16 @@ /* - * Copyright (c) 2022 NIBIO <http://www.nibio.no/>. + * Copyright (c) 2022 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 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. + * 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/>. + * 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/>. * */ @@ -23,7 +21,12 @@ import java.io.IOException; import java.net.URLEncoder; import java.util.UUID; import javax.ejb.EJB; -import javax.servlet.*; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -34,79 +37,69 @@ import no.nibio.vips.util.ServletUtil; /** * Ensures that user accessing a restricted resource is actually logged in. Redirects to login page if not + * * @copyright 2013-2022 <a href="http://www.nibio.no">NIBIO</a> * @author Tor-Einar Skog <tor-einar.skog@nibio.no> */ -public class AuthenticationFilter implements Filter{ - +public class AuthenticationFilter implements Filter { + @EJB UserBean userBean; // The URLs that do not require login private String[] unprivilegedURLs; + @Override public void init(FilterConfig filterConfig) throws ServletException { this.setUnprivilegedURLs(Globals.UNPRIVILEGED_URLS); - } + } @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { - HttpServletRequest httpRequest = (HttpServletRequest)request; + HttpServletRequest httpRequest = (HttpServletRequest) request; /* - // For debugging - BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream())); - String line; - while((line = reader.readLine()) != null) - { - System.out.println(line); - }*/ - if(isUnprivilegedURL(httpRequest)) - { + * // For debugging BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream())); + * String line; while((line = reader.readLine()) != null) { System.out.println(line); } + */ + if (isUnprivilegedURL(httpRequest)) { chain.doFilter(request, response); - //return; - } - else - { + // return; + } else { // First: Check for session variable - boolean clientAuthenticated = (httpRequest.getSession().getAttribute("user") != null && httpRequest.getSession().getAttribute("user") instanceof VipsLogicUser); + boolean clientAuthenticated = (httpRequest.getSession().getAttribute("user") != null + && httpRequest.getSession().getAttribute("user") instanceof VipsLogicUser); // Then for UUID cookie that has not expired boolean clientRemembered = false; Cookie remembered = ServletUtil.getCookie(httpRequest, "rememberedUser"); - if(remembered != null) - { + if (remembered != null) { VipsLogicUser user = userBean.findVipsLogicUser(UUID.fromString(remembered.getValue())); - if(user != null) - { + if (user != null) { httpRequest.getSession().setAttribute("user", user); clientRemembered = true; } } - - if(! clientAuthenticated && ! clientRemembered) - { + + if (!clientAuthenticated && !clientRemembered) { String nextPageDirective = ""; - if(!httpRequest.getServletPath().equals("/login")) - { + if (!httpRequest.getServletPath().equals("/login")) { String nextPage = ServletUtil.getFullRequestURI(httpRequest); - nextPageDirective= "?nextPage=" + URLEncoder.encode(nextPage, "UTF-8"); + nextPageDirective = "?nextPage=" + URLEncoder.encode(nextPage, "UTF-8"); } - ((HttpServletResponse)response).sendRedirect(Globals.PROTOCOL + "://" + ServletUtil.getServerName(httpRequest) + "/login" + nextPageDirective); - } - else - { + ((HttpServletResponse) response).sendRedirect(Globals.PROTOCOL + "://" + + ServletUtil.getServerName(httpRequest) + "/login" + nextPageDirective); + } else { chain.doFilter(request, response); } - //return; + // return; } } - + private boolean isUnprivilegedURL(HttpServletRequest request) { String path = request.getServletPath(); - for (String unprivilegedURL : this.getUnprivilegedURLs()) - { - if (path.contains(unprivilegedURL)) - { + for (String unprivilegedURL : this.getUnprivilegedURLs()) { + if (path.contains(unprivilegedURL)) { return true; } } @@ -115,12 +108,10 @@ public class AuthenticationFilter implements Filter{ @Override public void destroy() { - + } - - /** * @return the upriviligerteURLer @@ -136,6 +127,6 @@ public class AuthenticationFilter implements Filter{ this.unprivilegedURLs = unprivilegedURLs; } - - + + } diff --git a/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java b/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java index 7c79e6dd1ea1a0e21c8cdb622a191246708ec01e..99a5f3c304414d0c40f218fd7a03323e694c7631 100755 --- a/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java +++ b/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java @@ -61,7 +61,7 @@ import org.wololo.geojson.GeoJSONFactory; */ @Stateless public class ObservationBean { - private static Logger LOGGER = LoggerFactory.getLogger(ObservationBean.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ObservationBean.class); @PersistenceContext(unitName = "VIPSLogic-PU") EntityManager em; @EJB diff --git a/src/main/java/no/nibio/vips/logic/entity/Observation.java b/src/main/java/no/nibio/vips/logic/entity/Observation.java index 7d971682885caa3fe572f0df23933e2762e477ef..0b244dcfbb9170f91e9fe3c0eb296c30cd90d419 100755 --- a/src/main/java/no/nibio/vips/logic/entity/Observation.java +++ b/src/main/java/no/nibio/vips/logic/entity/Observation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 NIBIO <http://www.nibio.no/>. + * Copyright (c) 2014 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 @@ -95,15 +95,15 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse private Boolean broadcastMessage; private Boolean locationIsPrivate; private PolygonService polygonService; - + private ObservationDataSchema observationDataSchema; - + private VipsLogicUser user; // Transient private VipsLogicUser lastEditedByUser; // private PointOfInterest location; // Transient - + private Set<ObservationIllustration> observationIllustrationSet; - + private GISEntityUtil GISEntityUtil; private GISUtil GISUtil; @@ -156,7 +156,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse this.timeOfObservation = timeOfObservation; } - + // Using PostGIS + Hibernate-spatial + Java Topology Suite to make this work /*@JsonIgnore @Type(type = "org.hibernate.spatial.GeometryType") @@ -168,12 +168,12 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse public void setLocation(Point location) { this.location = location; }*/ - + public void setGeoinfos(List<Gis> geoinfo) { this.geoinfo = geoinfo; } - + public void addGeoInfo(Gis geoinfo) { if(this.geoinfo == null) @@ -182,20 +182,20 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse } this.geoinfo.add(geoinfo); } - + @JsonIgnore @Transient public List<Gis> getGeoinfos() { return this.geoinfo; } - - + + public void setGeoinfo(String json) { this.setGeoinfos(this.GISEntityUtil.getGisFromGeoJSON(json)); } - + @Transient @Override public String getGeoinfo() @@ -269,37 +269,37 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse @Override public String toString() { return "Observation{" + - "observationId=" + observationId + - ", timeOfObservation=" + timeOfObservation + - ", organism=" + organism + - ", cropOrganism=" + cropOrganism + - ", userId=" + userId + - ", lastEditedBy=" + lastEditedBy + - ", geoinfo=" + geoinfo + - ", observationTimeSeries=" + observationTimeSeries + - ", locationPointOfInterestId=" + locationPointOfInterestId + - ", observationHeading='" + observationHeading + '\'' + - ", observationText='" + observationText + '\'' + - ", statusTypeId=" + statusTypeId + - ", statusChangedByUserId=" + statusChangedByUserId + - ", statusChangedTime=" + statusChangedTime + - ", lastEditedTime=" + lastEditedTime + - ", statusRemarks='" + statusRemarks + '\'' + - ", observationData='" + observationData + '\'' + - ", isQuantified=" + isQuantified + - ", isPositive=" + isPositive + - ", broadcastMessage=" + broadcastMessage + - ", locationIsPrivate=" + locationIsPrivate + - ", polygonService=" + polygonService + - ", observationDataSchema=" + observationDataSchema + - ", user=" + user + - ", lastEditedByUser=" + lastEditedByUser + - ", location=" + location + - ", observationIllustrationSet=" + observationIllustrationSet + - ", GISEntityUtil=" + GISEntityUtil + - ", GISUtil=" + GISUtil + - ", source=" + source + - '}'; + "observationId=" + observationId + + ", timeOfObservation=" + timeOfObservation + + ", organism=" + organism + + ", cropOrganism=" + cropOrganism + + ", userId=" + userId + + ", lastEditedBy=" + lastEditedBy + + ", geoinfo=" + geoinfo + + ", observationTimeSeries=" + observationTimeSeries + + ", locationPointOfInterestId=" + locationPointOfInterestId + + ", observationHeading='" + observationHeading + '\'' + + ", observationText='" + observationText + '\'' + + ", statusTypeId=" + statusTypeId + + ", statusChangedByUserId=" + statusChangedByUserId + + ", statusChangedTime=" + statusChangedTime + + ", lastEditedTime=" + lastEditedTime + + ", statusRemarks='" + statusRemarks + '\'' + + ", observationData='" + observationData + '\'' + + ", isQuantified=" + isQuantified + + ", isPositive=" + isPositive + + ", broadcastMessage=" + broadcastMessage + + ", locationIsPrivate=" + locationIsPrivate + + ", polygonService=" + polygonService + + ", observationDataSchema=" + observationDataSchema + + ", user=" + user + + ", lastEditedByUser=" + lastEditedByUser + + ", location=" + location + + ", observationIllustrationSet=" + observationIllustrationSet + + ", GISEntityUtil=" + GISEntityUtil + + ", GISUtil=" + GISUtil + + ", source=" + source + + '}'; } /** @@ -338,13 +338,13 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse public void setOrganism(Organism organism) { this.organism = organism; } - + @JoinColumn(name = "polygon_service_id", referencedColumnName = "polygon_service_id") @ManyToOne public PolygonService getPolygonService(){ return this.polygonService; } - + public void setPolygonService(PolygonService polygonService) { this.polygonService = polygonService; @@ -363,7 +363,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse return this.getLocationCoordinate().x; } */ - + @Override @Transient public String getName() { @@ -480,13 +480,13 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse public void setObservationData(String observationData) { this.observationData = observationData; } - + @Transient public ObservationDataSchema getObservationDataSchema() { return this.observationDataSchema != null ? this.observationDataSchema : null; } - + public void setObservationDataSchema(ObservationDataSchema observationDataSchema) { this.observationDataSchema = observationDataSchema; @@ -522,7 +522,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse public void setCropOrganism(Organism cropOrganism) { this.cropOrganism = cropOrganism; } - + @Transient public Integer getCropOrganismId() { return this.getCropOrganism() != null ? this.getCropOrganism().getOrganismId() : null; @@ -564,7 +564,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse } /** - * @return the observation time series + * @return the observation time series */ @JoinColumn(name = "observation_time_series_id", referencedColumnName = "observation_time_series_id") @ManyToOne @@ -678,7 +678,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse /** * Simplifies the public JSON object * @param locale - * @return + * @return */ public ObservationListItem getListItem(String locale, ObservationDataSchema observationDataSchema) { @@ -690,41 +690,41 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse this.location.addProperty("timestamp", this.getTimeOfObservation().getTime()); } return new ObservationListItem( - this.getObservationId(), - this.userId, - this.user != null ? this.user.getFullName() : null, - this.getObservationTimeSeriesId(), - this.getTimeOfObservation(), - this.getOrganismId(), - ! this.getOrganism().getLocalName(locale).trim().isBlank() ? this.getOrganism().getLocalName(locale) : this.getOrganism().getLatinName(), - this.getCropOrganismId(), - ! this.getCropOrganism().getLocalName(locale).trim().isBlank() ? this.getCropOrganism().getLocalName(locale) : this.getCropOrganism().getLatinName(), - this.observationTimeSeries != null ? this.observationTimeSeries.getLabel() : null, - // Specific geoInfo trumps location. This is to be interpreted - // as that the observation has been geographically masked by - // choice of the observer - this.location != null ? this.location.getPointOfInterestId() : null, - this.location != null ? this.location.getName() : null, - this.location != null && this.geoinfo == null ? this.location.getGeoJSON() : this.getGeoinfo(), - this.getObservationHeading(), - this.getObservationText(), - this.getBroadcastMessage(), - this.getLocationIsPrivate(), - this.getIsPositive(), - this.getObservationData(), - observationDataSchema + this.getObservationId(), + this.userId, + this.user != null ? this.user.getFullName() : null, + this.getObservationTimeSeriesId(), + this.getTimeOfObservation(), + this.getOrganismId(), + ! this.getOrganism().getLocalName(locale).trim().isBlank() ? this.getOrganism().getLocalName(locale) : this.getOrganism().getLatinName(), + this.getCropOrganismId(), + ! this.getCropOrganism().getLocalName(locale).trim().isBlank() ? this.getCropOrganism().getLocalName(locale) : this.getCropOrganism().getLatinName(), + this.observationTimeSeries != null ? this.observationTimeSeries.getLabel() : null, + // Specific geoInfo trumps location. This is to be interpreted + // as that the observation has been geographically masked by + // choice of the observer + this.location != null ? this.location.getPointOfInterestId() : null, + this.location != null ? this.location.getName() : null, + this.location != null && this.geoinfo == null ? this.location.getGeoJSON() : this.getGeoinfo(), + this.getObservationHeading(), + this.getObservationText(), + this.getBroadcastMessage(), + this.getLocationIsPrivate(), + this.getIsPositive(), + this.getObservationData(), + observationDataSchema ); } @Temporal(TemporalType.TIMESTAMP) @Column(name = "last_edited_time") - public Date getLastEditedTime() { - return lastEditedTime; - } + public Date getLastEditedTime() { + return lastEditedTime; + } - public void setLastEditedTime(Date lastEditedTime) { - this.lastEditedTime = lastEditedTime; - } + public void setLastEditedTime(Date lastEditedTime) { + this.lastEditedTime = lastEditedTime; + } @Column(name = "is_positive") public Boolean getIsPositive() { diff --git a/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java b/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java index 0d9c0f09189b5ed1e42c857df8748a611b141114..27102b4dd8893a1bc2857c365a4cec3b6a7f6446 100755 --- a/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java +++ b/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 NIBIO <http://www.nibio.no/>. + * Copyright (c) 2015-2019 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 @@ -63,7 +63,7 @@ import java.util.UUID; @NamedQuery(name = "VipsLogicUser.findByCompletePhoneNumber", query = "SELECT v FROM VipsLogicUser v WHERE v.phoneCountryCode || v.phone = :completePhoneNumber") }) public class VipsLogicUser implements Serializable, Comparable{ - + private static final long serialVersionUID = 1L; private Integer userId; //if the field contains email address consider using this annotation to enforce field validation @@ -93,7 +93,7 @@ public class VipsLogicUser implements Serializable, Comparable{ private Integer vipsCoreUserId; private boolean approvesSmsBilling; private boolean freeSms; - + private UUID userUuid; public VipsLogicUser() { @@ -166,7 +166,7 @@ public class VipsLogicUser implements Serializable, Comparable{ public Organization getOrganizationId() { return organizationId; } - + @Transient public Integer getOrganization_id(){ return organizationId.getOrganizationId(); @@ -255,11 +255,11 @@ public class VipsLogicUser implements Serializable, Comparable{ @ManyToMany(fetch = FetchType.EAGER) @JsonIgnore @JoinTable( - name = "user_vips_logic_role", - joinColumns = { - @JoinColumn(name = "user_id")}, - inverseJoinColumns = { - @JoinColumn(name = "vips_logic_role_id")} + name = "user_vips_logic_role", + joinColumns = { + @JoinColumn(name = "user_id")}, + inverseJoinColumns = { + @JoinColumn(name = "vips_logic_role_id")} ) public Set<VipsLogicRole> getVipsLogicRoles() { return vipsLogicRoles; @@ -293,7 +293,7 @@ public class VipsLogicUser implements Serializable, Comparable{ } return false; } - + @JsonIgnore @Transient public boolean isObservationAuthority(){ @@ -304,7 +304,7 @@ public class VipsLogicUser implements Serializable, Comparable{ } return false; } - + @JsonIgnore @Transient public boolean isOrganismEditor() { @@ -315,7 +315,7 @@ public class VipsLogicUser implements Serializable, Comparable{ } return false; } - + @JsonIgnore @Transient public boolean isAppleFruitMothAdministrator(){ @@ -326,7 +326,7 @@ public class VipsLogicUser implements Serializable, Comparable{ } return false; } - + @JsonIgnore @Transient public boolean isMessageAuthor(){ @@ -505,7 +505,7 @@ public class VipsLogicUser implements Serializable, Comparable{ public boolean isFreeSms() { return freeSms; } - + /** * @param freeSms the freeSms to set @@ -519,6 +519,6 @@ public class VipsLogicUser implements Serializable, Comparable{ VipsLogicUser other = (VipsLogicUser)o; return (this.getLastName() + ", " + this.getFirstName()).compareTo(other.getLastName() + ", " + other.getFirstName()); } - - + + } diff --git a/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java b/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java index 5082c7089796662fdd85982ab4ff6529e0e41b90..95d83c07dfcc4494d8fbc4022afebdff8fbb58aa 100644 --- a/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java +++ b/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 NIBIO <http://www.nibio.no/>. + * Copyright (c) 2018 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 @@ -45,26 +45,26 @@ public class ObservationListItem implements Comparable{ private Boolean isPositive; public ObservationListItem( - Integer observationId, - Integer observerId, - String observerName, - Integer observationTimeSeriesId, - Date timeOfObservation, - Integer organismId, - String organismName, - Integer cropOrganismId, - String cropOrganismName, - String observationTimeSeriesLabel, - Integer poiId, - String poiName, - String geoinfo, - String observationHeading, - String observationText, - Boolean broadcastMessage, - Boolean locationIsPrivate, - Boolean isPositive, - String observationData, - ObservationDataSchema observationDataSchema){ + Integer observationId, + Integer observerId, + String observerName, + Integer observationTimeSeriesId, + Date timeOfObservation, + Integer organismId, + String organismName, + Integer cropOrganismId, + String cropOrganismName, + String observationTimeSeriesLabel, + Integer poiId, + String poiName, + String geoinfo, + String observationHeading, + String observationText, + Boolean broadcastMessage, + Boolean locationIsPrivate, + Boolean isPositive, + String observationData, + ObservationDataSchema observationDataSchema){ this.observationId = observationId; this.observerId = observerId; this.observerName = observerName; @@ -86,7 +86,7 @@ public class ObservationListItem implements Comparable{ this.observationData = observationData; this.observationDataSchema = observationDataSchema; } - + @Override public int compareTo(Object otherOne) @@ -97,7 +97,7 @@ public class ObservationListItem implements Comparable{ } return this.getTimeOfObservation().compareTo(((ObservationListItem) otherOne).getTimeOfObservation()); } - + /** * @return the observationId */ diff --git a/src/main/java/no/nibio/vips/logic/service/ObservationService.java b/src/main/java/no/nibio/vips/logic/service/ObservationService.java index 12aa050d36c928713615101745cb370c492db080..75d4ac8ba5def7edab317ebfe851561a08c28b84 100755 --- a/src/main/java/no/nibio/vips/logic/service/ObservationService.java +++ b/src/main/java/no/nibio/vips/logic/service/ObservationService.java @@ -111,24 +111,24 @@ public class ObservationService { @Produces("application/json;charset=UTF-8") @TypeHint(Observation[].class) public Response getFilteredObservations( - @PathParam("organizationId") Integer organizationId, - @QueryParam("observationTimeSeriesId") Integer observationTimeSeriesId, - @QueryParam("pestId") Integer pestId, - @QueryParam("cropId") Integer cropId, - @QueryParam("cropCategoryId") List<Integer> cropCategoryId, - @QueryParam("from") String fromStr, - @QueryParam("to") String toStr, - @QueryParam("isPositive") Boolean isPositive + @PathParam("organizationId") Integer organizationId, + @QueryParam("observationTimeSeriesId") Integer observationTimeSeriesId, + @QueryParam("pestId") Integer pestId, + @QueryParam("cropId") Integer cropId, + @QueryParam("cropCategoryId") List<Integer> cropCategoryId, + @QueryParam("from") String fromStr, + @QueryParam("to") String toStr, + @QueryParam("isPositive") Boolean isPositive ) { return Response.ok().entity(getFilteredObservationsFromBackend( - organizationId, - observationTimeSeriesId, - pestId, - cropId, - cropCategoryId, - fromStr, - toStr, - isPositive + organizationId, + observationTimeSeriesId, + pestId, + cropId, + cropCategoryId, + fromStr, + toStr, + isPositive )).build(); } @@ -148,16 +148,16 @@ public class ObservationService { @Produces("application/json;charset=UTF-8") @TypeHint(ObservationListItem.class) public Response getFilteredObservationListItemsAsJson( - @PathParam("organizationId") Integer organizationId, - @QueryParam("observationTimeSeriesId") Integer observationTimeSeriesId, - @QueryParam("pestId") Integer pestId, - @QueryParam("cropId") Integer cropId, - @QueryParam("cropCategoryId") List<Integer> cropCategoryId, - @QueryParam("from") String fromStr, - @QueryParam("to") String toStr, - @QueryParam("userUUID") String userUUID, - @QueryParam("locale") String localeStr, - @QueryParam("isPositive") Boolean isPositive + @PathParam("organizationId") Integer organizationId, + @QueryParam("observationTimeSeriesId") Integer observationTimeSeriesId, + @QueryParam("pestId") Integer pestId, + @QueryParam("cropId") Integer cropId, + @QueryParam("cropCategoryId") List<Integer> cropCategoryId, + @QueryParam("from") String fromStr, + @QueryParam("to") String toStr, + @QueryParam("userUUID") String userUUID, + @QueryParam("locale") String localeStr, + @QueryParam("isPositive") Boolean isPositive ) { return Response.ok().entity(this.getFilteredObservationListItems(organizationId, observationTimeSeriesId, pestId, cropId, cropCategoryId, fromStr, toStr, userUUID, localeStr, isPositive)).build(); } @@ -177,21 +177,21 @@ public class ObservationService { @GZIP @Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") public Response getFilteredObservationListItemsAsXlsx( - @PathParam("organizationId") Integer organizationId, - @QueryParam("observationTimeSeriesId") Integer observationTimeSeriesId, - @QueryParam("pestId") Integer pestId, - @QueryParam("cropId") Integer cropId, - @QueryParam("cropCategoryId") List<Integer> cropCategoryId, - @QueryParam("from") String fromStr, - @QueryParam("to") String toStr, - @QueryParam("userUUID") String userUUID, - @QueryParam("locale") String localeStr, - @QueryParam("isPositive") Boolean isPositive + @PathParam("organizationId") Integer organizationId, + @QueryParam("observationTimeSeriesId") Integer observationTimeSeriesId, + @QueryParam("pestId") Integer pestId, + @QueryParam("cropId") Integer cropId, + @QueryParam("cropCategoryId") List<Integer> cropCategoryId, + @QueryParam("from") String fromStr, + @QueryParam("to") String toStr, + @QueryParam("userUUID") String userUUID, + @QueryParam("locale") String localeStr, + @QueryParam("isPositive") Boolean isPositive ) { VipsLogicUser user = getVipsLogicUser(userUUID); ULocale locale = new ULocale(localeStr != null ? localeStr : - user != null ? user.getOrganizationId().getDefaultLocale() : - userBean.getOrganization(organizationId).getDefaultLocale()); + user != null ? user.getOrganizationId().getDefaultLocale() : + userBean.getOrganization(organizationId).getDefaultLocale()); LOGGER.info("Generate xlsx file for observations for user {} from {} to {}", user != null ? user.getUserId() : "unregistered", fromStr, toStr); LocalDateTime now = LocalDateTime.now(); @@ -203,9 +203,9 @@ public class ObservationService { byte[] excelFile = ExcelFileGenerator.generateExcel(user, locale, now, fromStr, toStr, observations); return Response - .ok(excelFile) - .header("Content-Disposition", "attachment; filename=\"" + filenameTimestamp + "-observations.xlsx\"") - .build(); + .ok(excelFile) + .header("Content-Disposition", "attachment; filename=\"" + filenameTimestamp + "-observations.xlsx\"") + .build(); } catch (IOException e) { LOGGER.error(e.getMessage()); return Response.serverError().entity("Error generating Excel file: " + e.getMessage()).build(); @@ -213,40 +213,40 @@ public class ObservationService { } private List<ObservationListItem> getFilteredObservationListItems( - Integer organizationId, - Integer observationTimeSeriesId, - Integer pestId, - Integer cropId, - List<Integer> cropCategoryId, - String fromStr, - String toStr, - String userUUID, - String localeStr, - Boolean isPositive) { + Integer organizationId, + Integer observationTimeSeriesId, + Integer pestId, + Integer cropId, + List<Integer> cropCategoryId, + String fromStr, + String toStr, + String userUUID, + String localeStr, + Boolean isPositive) { VipsLogicUser user = getVipsLogicUser(userUUID); ULocale locale = new ULocale(localeStr != null ? localeStr : - user != null ? user.getOrganizationId().getDefaultLocale() : - userBean.getOrganization(organizationId).getDefaultLocale()); + user != null ? user.getOrganizationId().getDefaultLocale() : + userBean.getOrganization(organizationId).getDefaultLocale()); LOGGER.info("Get filtered observations for user {}", user != null ? user.getUserId() : "<no user>"); List<ObservationListItem> observations = getFilteredObservationsFromBackend( - organizationId, - observationTimeSeriesId, - pestId, - cropId, - cropCategoryId, - fromStr, - toStr, - isPositive, - user + organizationId, + observationTimeSeriesId, + pestId, + cropId, + cropCategoryId, + fromStr, + toStr, + isPositive, + user ).stream().map(obs -> { try { return obs.getListItem(locale.getLanguage(), - observationBean.getLocalizedObservationDataSchema( - observationBean.getObservationDataSchema(organizationId, obs.getOrganismId()), - httpServletRequest, - locale - ) + observationBean.getLocalizedObservationDataSchema( + observationBean.getObservationDataSchema(organizationId, obs.getOrganismId()), + httpServletRequest, + locale + ) ); } catch (IOException e) { LOGGER.error("Exception when getting localized observation data schema for observation " + obs.getObservationId(), e); @@ -272,16 +272,16 @@ public class ObservationService { @Produces("text/csv;charset=UTF-8") @TypeHint(ObservationListItem.class) public Response getFilteredObservationListItemsAsCSV( - @PathParam("organizationId") Integer organizationId, - @QueryParam("observationTimeSeriesId") Integer observationTimeSeriesId, - @QueryParam("pestId") Integer pestId, - @QueryParam("cropId") Integer cropId, - @QueryParam("cropCategoryId") List<Integer> cropCategoryId, - @QueryParam("from") String fromStr, - @QueryParam("to") String toStr, - @QueryParam("userUUID") String userUUID, - @QueryParam("locale") String localeStr, - @QueryParam("isPositive") Boolean isPositive + @PathParam("organizationId") Integer organizationId, + @QueryParam("observationTimeSeriesId") Integer observationTimeSeriesId, + @QueryParam("pestId") Integer pestId, + @QueryParam("cropId") Integer cropId, + @QueryParam("cropCategoryId") List<Integer> cropCategoryId, + @QueryParam("from") String fromStr, + @QueryParam("to") String toStr, + @QueryParam("userUUID") String userUUID, + @QueryParam("locale") String localeStr, + @QueryParam("isPositive") Boolean isPositive ) { List<ObservationListItem> observations = this.getFilteredObservationListItems(organizationId, observationTimeSeriesId, pestId, cropId, cropCategoryId, fromStr, toStr, userUUID, localeStr, isPositive); Collections.sort(observations); @@ -295,12 +295,12 @@ public class ObservationService { c = ((Point) geometries.get(0)).getCoordinate(); } retVal += "\n" + obs.getObservationId() - + ";" + obs.getOrganismName() - + ";" + obs.getCropOrganismName() - + ";" + obs.getTimeOfObservation() - + ";" + (c != null ? c.getY() + "," + c.getX() : "") - + ";" + obs.getObservationHeading() - + ";" + obs.getObservationData(); + + ";" + obs.getOrganismName() + + ";" + obs.getCropOrganismName() + + ";" + obs.getTimeOfObservation() + + ";" + (c != null ? c.getY() + "," + c.getX() : "") + + ";" + obs.getObservationHeading() + + ";" + obs.getObservationData(); } return Response.ok().entity(retVal).build(); } @@ -316,14 +316,14 @@ public class ObservationService { * @return Observation objects for which the user is authorized to observe with properties relevant for lists */ private List<Observation> getFilteredObservationsFromBackend( - Integer organizationId, - Integer observationTimeSeriesId, - Integer pestId, - Integer cropId, - List<Integer> cropCategoryId, - String fromStr, - String toStr, - Boolean isPositive) { + Integer organizationId, + Integer observationTimeSeriesId, + Integer pestId, + Integer cropId, + List<Integer> cropCategoryId, + String fromStr, + String toStr, + Boolean isPositive) { SimpleDateFormat format = new SimpleDateFormat(Globals.defaultDateFormat); //TODO Set correct timeZone!!! Date from = null; @@ -336,14 +336,14 @@ public class ObservationService { } return observationBean.getFilteredObservations( - organizationId, - observationTimeSeriesId, - pestId, - cropId, - cropCategoryId, - from, - to, - isPositive + organizationId, + observationTimeSeriesId, + pestId, + cropId, + cropCategoryId, + from, + to, + isPositive ); } @@ -461,14 +461,14 @@ public class ObservationService { @Produces("application/json;charset=UTF-8") @TypeHint(GeoJSON.class) public Response getFilteredObservationsAsGeoJSON( - @PathParam("organizationId") Integer organizationId, - @QueryParam("observationTimeSeriesId") Integer observationTimeSeriesId, - @QueryParam("pestId") Integer pestId, - @QueryParam("cropId") Integer cropId, - @QueryParam("cropCategoryId") List<Integer> cropCategoryId, - @QueryParam("from") String fromStr, - @QueryParam("to") String toStr, - @QueryParam("isPositive") Boolean isPositive + @PathParam("organizationId") Integer organizationId, + @QueryParam("observationTimeSeriesId") Integer observationTimeSeriesId, + @QueryParam("pestId") Integer pestId, + @QueryParam("cropId") Integer cropId, + @QueryParam("cropCategoryId") List<Integer> cropCategoryId, + @QueryParam("from") String fromStr, + @QueryParam("to") String toStr, + @QueryParam("isPositive") Boolean isPositive ) { SimpleDateFormat format = new SimpleDateFormat(Globals.defaultDateFormat); @@ -483,14 +483,14 @@ public class ObservationService { } List<Observation> filteredObservations = this.getFilteredObservationsFromBackend( - organizationId, - observationTimeSeriesId, - pestId, - cropId, - cropCategoryId, - fromStr, - toStr, - isPositive + organizationId, + observationTimeSeriesId, + pestId, + cropId, + cropCategoryId, + fromStr, + toStr, + isPositive ); GISEntityUtil gisUtil = new GISEntityUtil(); @@ -555,7 +555,7 @@ public class ObservationService { @Produces("application/json;charset=UTF-8") @TypeHint(Observation[].class) public Response getObservationsForUser( - @QueryParam("observationIds") String observationIds + @QueryParam("observationIds") String observationIds ) { LOGGER.info("getObservationsForUser for observationIds={}", observationIds); try { @@ -566,14 +566,14 @@ public class ObservationService { LOGGER.info("Found {} observations for user {}", allObs.size(), user.getUserId()); if (observationIds != null) { Set<Integer> observationIdSet = Arrays.asList(observationIds.split(",")).stream() - .map(s -> Integer.valueOf(s)) - .collect(Collectors.toSet()); + .map(s -> Integer.valueOf(s)) + .collect(Collectors.toSet()); return Response.ok().entity( - allObs.stream() - .filter(obs -> observationIdSet.contains(obs.getObservationId())) - .collect(Collectors.toList()) - ) - .build(); + allObs.stream() + .filter(obs -> observationIdSet.contains(obs.getObservationId())) + .collect(Collectors.toList()) + ) + .build(); } return Response.ok().entity(allObs).build(); } else { @@ -600,7 +600,7 @@ public class ObservationService { VipsLogicUser user = userBean.getUserFromUUID(httpServletRequest); if (user != null) { return Response.ok().entity(observationBean.getObservationsForUser(user).stream() - .map(obs -> new ObservationSyncInfo(obs)).collect(Collectors.toList())).build(); + .map(obs -> new ObservationSyncInfo(obs)).collect(Collectors.toList())).build(); } else { return Response.status(Status.UNAUTHORIZED).build(); } @@ -618,13 +618,13 @@ public class ObservationService { @Produces("application/json;charset=UTF-8") @TypeHint(Observation[].class) public Response getBroadcastObservations( - @PathParam("organizationId") Integer organizationId, - @QueryParam("season") Integer season, - @QueryParam("timeOfObservationFrom") String timeOfObservationFrom, - @QueryParam("timeOfObservationTo") String timeOfObservationTo + @PathParam("organizationId") Integer organizationId, + @QueryParam("season") Integer season, + @QueryParam("timeOfObservationFrom") String timeOfObservationFrom, + @QueryParam("timeOfObservationTo") String timeOfObservationTo ) { if ((timeOfObservationFrom != null && !timeOfObservationFrom.isEmpty()) - || (timeOfObservationTo != null && !timeOfObservationTo.isEmpty())) { + || (timeOfObservationTo != null && !timeOfObservationTo.isEmpty())) { Date from = null; Date to = null; try { @@ -656,8 +656,8 @@ public class ObservationService { @Produces("application/json;charset=UTF-8") @TypeHint(Observation.class) public Response getObservation( - @PathParam("observationId") Integer observationId, - @QueryParam("userUUID") String userUUID + @PathParam("observationId") Integer observationId, + @QueryParam("userUUID") String userUUID ) { // Observation needs to be masked here as well, or does it create trouble for VIPSLogic observation admin? Observation o = observationBean.getObservation(observationId); @@ -686,9 +686,9 @@ public class ObservationService { List<Observation> intermediary = new ArrayList<>(); intermediary.add(o); intermediary = this.maskObservations(o.getPolygonService(), - observationBean.getObservationsWithLocations( - observationBean.getObservationsWithGeoInfo(intermediary) - ) + observationBean.getObservationsWithLocations( + observationBean.getObservationsWithGeoInfo(intermediary) + ) ); o = intermediary.get(0); } @@ -709,7 +709,7 @@ public class ObservationService { @Produces("application/json;charset=UTF-8") @TypeHint(PolygonService[].class) public Response getPolygonServicesForOrganization( - @PathParam("organizationId") Integer organizationId + @PathParam("organizationId") Integer organizationId ) { return Response.ok().entity(observationBean.getPolygonServicesForOrganization(organizationId)).build(); } @@ -729,10 +729,10 @@ public class ObservationService { return Response.status(Response.Status.UNAUTHORIZED).build(); } if (!userBean.authorizeUser(user, - VipsLogicRole.OBSERVER, - VipsLogicRole.OBSERVATION_AUTHORITY, - VipsLogicRole.ORGANIZATION_ADMINISTRATOR, - VipsLogicRole.SUPERUSER + VipsLogicRole.OBSERVER, + VipsLogicRole.OBSERVATION_AUTHORITY, + VipsLogicRole.ORGANIZATION_ADMINISTRATOR, + VipsLogicRole.SUPERUSER ) ) { return Response.status(Response.Status.FORBIDDEN).build(); @@ -762,10 +762,10 @@ public class ObservationService { return Response.status(Response.Status.UNAUTHORIZED).build(); } if (!userBean.authorizeUser(user, - VipsLogicRole.OBSERVER, - VipsLogicRole.OBSERVATION_AUTHORITY, - VipsLogicRole.ORGANIZATION_ADMINISTRATOR, - VipsLogicRole.SUPERUSER + VipsLogicRole.OBSERVER, + VipsLogicRole.OBSERVATION_AUTHORITY, + VipsLogicRole.ORGANIZATION_ADMINISTRATOR, + VipsLogicRole.SUPERUSER ) ) { return Response.status(Response.Status.FORBIDDEN).build(); @@ -794,7 +794,7 @@ public class ObservationService { public Response getFirstObservation(@PathParam("organismId") Integer organismId) { Date firstObsTime = observationBean.getFirstObservationTime(organismId); return firstObsTime != null ? Response.ok().entity(firstObsTime).build() - : Response.status(404).entity("No observations of organism with id=" + organismId).build(); + : Response.status(404).entity("No observations of organism with id=" + organismId).build(); } /** @@ -827,15 +827,15 @@ public class ObservationService { * @return A list of observations that meets the filter criteria */ private List<Observation> getFilteredObservationsFromBackend( - Integer organizationId, - Integer observationTimeSeriesId, - Integer pestId, - Integer cropId, - List<Integer> cropCategoryId, - String fromStr, - String toStr, - Boolean isPositive, - VipsLogicUser user + Integer organizationId, + Integer observationTimeSeriesId, + Integer pestId, + Integer cropId, + List<Integer> cropCategoryId, + String fromStr, + String toStr, + Boolean isPositive, + VipsLogicUser user ) { List<Observation> filteredObservations = this.getFilteredObservationsFromBackend(organizationId, observationTimeSeriesId, pestId, cropId, cropCategoryId, fromStr, toStr, isPositive); @@ -889,7 +889,7 @@ public class ObservationService { Map<Integer, Observation> maskedObservations = new HashMap<>(); registeredPolygonServicesInObservationList.keySet().forEach((pService) -> { this.maskObservations(pService, registeredPolygonServicesInObservationList.get(pService)) - .forEach(o -> maskedObservations.put(o.getObservationId(), o)); + .forEach(o -> maskedObservations.put(o.getObservationId(), o)); }); // Adding the rest of the observations (the ones that don't need masking) @@ -908,19 +908,19 @@ public class ObservationService { Client client = ClientBuilder.newClient(); WebTarget target = client.target(polygonService.getGisSearchUrlTemplate()); List<ReferencedPoint> points = observations.stream() - .filter(obs -> (obs.getGeoinfos() != null && !obs.getGeoinfos().isEmpty()) || obs.getLocation() != null) - .map(obs -> { - ReferencedPoint rp = new ReferencedPoint(); - rp.setId(String.valueOf(obs.getObservationId())); - if (obs.getGeoinfos() != null) { - rp.setLon(obs.getGeoinfos().get(0).getGisGeom().getCoordinate().x); - rp.setLat(obs.getGeoinfos().get(0).getGisGeom().getCoordinate().y); - } else { - rp.setLon(obs.getLocation().getLongitude()); - rp.setLat(obs.getLocation().getLatitude()); - } - return rp; - }).collect(Collectors.toList()); + .filter(obs -> (obs.getGeoinfos() != null && !obs.getGeoinfos().isEmpty()) || obs.getLocation() != null) + .map(obs -> { + ReferencedPoint rp = new ReferencedPoint(); + rp.setId(String.valueOf(obs.getObservationId())); + if (obs.getGeoinfos() != null) { + rp.setLon(obs.getGeoinfos().get(0).getGisGeom().getCoordinate().x); + rp.setLat(obs.getGeoinfos().get(0).getGisGeom().getCoordinate().y); + } else { + rp.setLon(obs.getLocation().getLongitude()); + rp.setLat(obs.getLocation().getLatitude()); + } + return rp; + }).collect(Collectors.toList()); /*System.out.println("maskobservations - target.request() about to be called"); ObjectMapper oMapper = new ObjectMapper(); try { @@ -929,7 +929,7 @@ public class ObservationService { Logger.getLogger(ObservationService.class.getName()).log(Level.SEVERE, null, ex); }*/ PointMappingResponse response = target.request(MediaType.APPLICATION_JSON) - .post(Entity.entity(points.toArray(new ReferencedPoint[points.size()]), MediaType.APPLICATION_JSON), PointMappingResponse.class); + .post(Entity.entity(points.toArray(new ReferencedPoint[points.size()]), MediaType.APPLICATION_JSON), PointMappingResponse.class); // We need to loop through the observations and find corresponding featurecollections and replace those Map<Integer, Feature> indexedPolygons = new HashMap<>(); for (Feature feature : response.getFeatureCollection().getFeatures()) { @@ -965,7 +965,7 @@ public class ObservationService { @Produces("application/json;charset=UTF-8") @TypeHint(Observation.class) public Response syncObservationFromApp( - String observationJson + String observationJson ) { LOGGER.info("In syncObservationFromApp"); @@ -1124,15 +1124,15 @@ public class ObservationService { */ private List<Observation> sortObservationsByDateAndId(List<Observation> observations) { return observations.stream() - .sorted((o1, o2) -> { - int timeCompare = o2.getTimeOfObservation().compareTo(o1.getTimeOfObservation()); - if (timeCompare != 0) { - return timeCompare; - } else { - return Integer.compare(o2.getObservationId(), o1.getObservationId()); - } - }) - .collect(Collectors.toList()); + .sorted((o1, o2) -> { + int timeCompare = o2.getTimeOfObservation().compareTo(o1.getTimeOfObservation()); + if (timeCompare != 0) { + return timeCompare; + } else { + return Integer.compare(o2.getObservationId(), o1.getObservationId()); + } + }) + .collect(Collectors.toList()); } /** diff --git a/src/main/java/no/nibio/vips/logic/util/ExcelFileGenerator.java b/src/main/java/no/nibio/vips/logic/util/ExcelFileGenerator.java index 1e1125d1fc2af4f21f6afdfff1a5a22c25066442..242306b53b1674404bb49a9f6503cc17e18e8cf0 100644 --- a/src/main/java/no/nibio/vips/logic/util/ExcelFileGenerator.java +++ b/src/main/java/no/nibio/vips/logic/util/ExcelFileGenerator.java @@ -23,420 +23,419 @@ import java.util.*; public final class ExcelFileGenerator { - private static final Logger LOGGER = LoggerFactory.getLogger(ExcelFileGenerator.class); - private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - private static final ObjectMapper objectMapper = new ObjectMapper(); - - // TODO Dette må fikses før deploy til prod - private static final String VIPSWEB = "https://testvips.nibio.no"; - private static final String VIPSLOGIC = "https://logic.testvips.nibio.no"; - - private enum ColumnIndex { - ID(false, 0, 0, "observationId"), - DATE(false, 1, 1, "timeOfObservation"), - POI_NAME(false, 2, 2, "location"), - OBSERVER_NAME(true, null, 3, "observer"), - OBSERVATION_TIME_SERIES_LABEL(false, 3, 4, "observationTimeSeriesLabel"), - ORGANISM(false, 4, 5, "organism"), - CROP_ORGANISM(false, 5, 6, "cropOrganismId"), - HEADING(false, 6, 7, "observationHeading"), - DESCRIPTION(false, 7, 8, "observationText"), - BROADCAST(false, 8, 9, "isBroadcast"), - POSITIVE(false, 9, 10, "isPositiveRegistration"), - INDEX_DATA(false, 10, 11, null); - - private final boolean isSensitive; - private final Integer openIndex; - private final Integer adminIndex; - private final String rbKey; - - ColumnIndex(boolean isSensitive, Integer openIndex, Integer adminIndex, String rbKey) { - this.isSensitive = isSensitive; - this.openIndex = openIndex; - this.adminIndex = adminIndex; - this.rbKey = rbKey; - } - - public static List<ColumnIndex> forUser(boolean isAdmin) { - if (!isAdmin) { - return Arrays.stream(ColumnIndex.values()).filter(columnIndex -> !columnIndex.isSensitive).toList(); - } - return Arrays.stream(ColumnIndex.values()).toList(); - } - - public String getColumnHeading(ResourceBundle rb) { - return rbKey != null && !rbKey.isBlank() ? rb.getString(rbKey) : ""; - } - - public Integer getIndex(boolean admin) { - if (admin) { - return adminIndex; - } - return openIndex; - } - } - - public static byte[] generateExcel(VipsLogicUser user, ULocale locale, LocalDateTime now, String fromStr, String toStr, List<ObservationListItem> observations) throws IOException { - ResourceBundle rb = ResourceBundle.getBundle("no.nibio.vips.logic.i18n.vipslogictexts", locale.toLocale()); - boolean isAdmin = user != null && (user.isSuperUser() || user.isOrganizationAdmin()); - LOGGER.info("Create Excel file containing {} observations for {} user", observations.size(), isAdmin ? "admin" : "regular"); - try (XSSFWorkbook workbook = new XSSFWorkbook(); - ByteArrayOutputStream out = new ByteArrayOutputStream()) { - - Font font = workbook.createFont(); - font.setBold(true); - CellStyle headerStyle = workbook.createCellStyle(); - headerStyle.setFont(font); - - // Create main sheet for all observations, with header row - Sheet mainSheet = workbook.createSheet(rb.getString("allObservations")); - createHeaderRow(isAdmin, mainSheet, headerStyle, rb); - - int mainSheetRowIndex = 1; - // Add one row for each observation in list of all observations - for (ObservationListItem item : observations) { - createItemRow(isAdmin, mainSheet, mainSheetRowIndex++, item, rb); - } - autoSizeColumns(mainSheet, 0, ColumnIndex.INDEX_DATA.getIndex(isAdmin) - 1); - - // Create meta sheet for information about the download - Sheet metaSheet = workbook.createSheet(rb.getString("downloadInfo")); - addMetaInfo(metaSheet, user, now, fromStr, toStr, observations, headerStyle, rb); - - // Prepare list of observations for each type of pest - Map<Integer, List<ObservationListItem>> pestObservations = getObservationsForEachPest(observations); - - // Create sheets for each individual pest type - for (Integer pestId : pestObservations.keySet()) { - List<ObservationListItem> observationsForPest = pestObservations.get(pestId); - ObservationListItem firstObservationForPest = observationsForPest.get(0); - String pestName = firstObservationForPest.getOrganismName(); - Sheet pestSheet = workbook.createSheet(sanitizeSheetName(pestId, pestName)); - Row headerRow = createHeaderRow(isAdmin, pestSheet, headerStyle, rb); - - // Add column titles for observation data - Map<String, String> dataColumnTitles = getObservationDataColumnTitles(firstObservationForPest.getObservationDataSchema()); - int pestSheetColIndex = ColumnIndex.INDEX_DATA.getIndex(isAdmin); - for (String key : dataColumnTitles.keySet()) { - Cell cell = headerRow.createCell(pestSheetColIndex++); - cell.setCellStyle(headerStyle); - cell.setCellValue(dataColumnTitles.get(key)); - } - - int pestSheetRowIndex = 1; - for (ObservationListItem item : observationsForPest) { - Row row = createItemRow(isAdmin, pestSheet, pestSheetRowIndex++, item, rb); - - if (item.getObservationData() != null) { - Map<String, Object> observationDataMap = objectMapper.readValue(item.getObservationData(), HashMap.class); - if (observationDataMap != null) { - pestSheetColIndex = ColumnIndex.INDEX_DATA.getIndex(isAdmin); - for (String key : dataColumnTitles.keySet()) { - pestSheetColIndex = addValueToCell(row, pestSheetColIndex, observationDataMap.get(key)); - } - } - } - } - autoSizeColumns(pestSheet, ColumnIndex.ID.getIndex(isAdmin), ColumnIndex.INDEX_DATA.getIndex(isAdmin) + dataColumnTitles.size()); - } - - workbook.write(out); - return out.toByteArray(); - } + private static final Logger LOGGER = LoggerFactory.getLogger(ExcelFileGenerator.class); + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private static final String VIPSWEB = "https://www.vips-landbruk.no"; + private static final String VIPSLOGIC = "https://logic.vips.nibio.no"; + + private enum ColumnIndex { + ID(false, 0, 0, "observationId"), + DATE(false, 1, 1, "timeOfObservation"), + POI_NAME(false, 2, 2, "location"), + OBSERVER_NAME(true, null, 3, "observer"), + OBSERVATION_TIME_SERIES_LABEL(false, 3, 4, "observationTimeSeriesLabel"), + ORGANISM(false, 4, 5, "organism"), + CROP_ORGANISM(false, 5, 6, "cropOrganismId"), + HEADING(false, 6, 7, "observationHeading"), + DESCRIPTION(false, 7, 8, "observationText"), + BROADCAST(false, 8, 9, "isBroadcast"), + POSITIVE(false, 9, 10, "isPositiveRegistration"), + INDEX_DATA(false, 10, 11, null); + + private final boolean isSensitive; + private final Integer openIndex; + private final Integer adminIndex; + private final String rbKey; + + ColumnIndex(boolean isSensitive, Integer openIndex, Integer adminIndex, String rbKey) { + this.isSensitive = isSensitive; + this.openIndex = openIndex; + this.adminIndex = adminIndex; + this.rbKey = rbKey; } - /** - * Add meta information to given sheet - * - * @param metaSheet The sheet in which to add content - * @param user The current user - * @param now The current timestamp - * @param fromStr The start of the period for which we have observations - * @param toStr The end of the period for which we have observations - * @param observations The list of observations - * @param headerStyle How to style the title cells - * @param rb Resource bundle with translations - */ - private static void addMetaInfo(Sheet metaSheet, VipsLogicUser user, LocalDateTime now, String fromStr, String toStr, List<ObservationListItem> observations, CellStyle headerStyle, ResourceBundle rb) { - Row userRow = metaSheet.createRow(0); - Cell downloadedByCell = userRow.createCell(0); - downloadedByCell.setCellStyle(headerStyle); - downloadedByCell.setCellValue(rb.getString("downloadedBy")); - userRow.createCell(1).setCellValue(user != null ? user.getFullName() : rb.getString("unregisteredUser")); - Row timeRow = metaSheet.createRow(1); - Cell downloadedTimeCell = timeRow.createCell(0); - downloadedTimeCell.setCellStyle(headerStyle); - downloadedTimeCell.setCellValue(rb.getString("downloadedTime")); - timeRow.createCell(1).setCellValue(DATE_TIME_FORMATTER.format(now)); - Row fromRow = metaSheet.createRow(2); - Cell dateFromCell = fromRow.createCell(0); - dateFromCell.setCellStyle(headerStyle); - dateFromCell.setCellValue(rb.getString("dateStart")); - fromRow.createCell(1).setCellValue(fromStr); - Row toRow = metaSheet.createRow(3); - Cell dateToCell = toRow.createCell(0); - dateToCell.setCellStyle(headerStyle); - dateToCell.setCellValue(rb.getString("dateEnd")); - toRow.createCell(1).setCellValue(toStr); - Row countRow = metaSheet.createRow(4); - Cell countCell = countRow.createCell(0); - countCell.setCellStyle(headerStyle); - countCell.setCellValue(rb.getString("observationCount")); - countRow.createCell(1).setCellValue(observations.size()); - autoSizeColumns(metaSheet, 0, 1); + public static List<ColumnIndex> forUser(boolean isAdmin) { + if (!isAdmin) { + return Arrays.stream(ColumnIndex.values()).filter(columnIndex -> !columnIndex.isSensitive).toList(); + } + return Arrays.stream(ColumnIndex.values()).toList(); } - /** - * Create sheet name without invalid characters and within size limits - * - * @param pestId The id of the pest - * @param pestName The name of the pest - * @return a sanitized string to be used as sheet name - */ - private static String sanitizeSheetName(Integer pestId, String pestName) { - if (pestName == null || pestName.isBlank()) { - return "Id=" + pestId; - } - return pestName.replaceAll("[\\[\\]\\*/:?\\\\]", "_").substring(0, Math.min(pestName.length(), 31)); + public String getColumnHeading(ResourceBundle rb) { + return rbKey != null && !rbKey.isBlank() ? rb.getString(rbKey) : ""; } - - /** - * Auto-size columns of given sheet, from startIndex to endIndex, to ensure that content fits - * - * @param sheet The sheet of which to auto-size columns - * @param startIndex The index of the first column to auto-size - * @param endIndex The index of the last column to auto-size - */ - private static void autoSizeColumns(Sheet sheet, int startIndex, int endIndex) { - for (int i = startIndex; i <= endIndex; i++) { - sheet.autoSizeColumn(i); - } + public Integer getIndex(boolean admin) { + if (admin) { + return adminIndex; + } + return openIndex; } - - /** - * Create map with data property name as key, and corresponding localized name as value - * - * @param observationDataSchema The observation data schema which contains localized names and other info - * @return result map - */ - private static Map<String, String> getObservationDataColumnTitles(ObservationDataSchema observationDataSchema) throws JsonProcessingException { - JsonNode rootNode = objectMapper.readTree(observationDataSchema.getDataSchema()); - JsonNode propertiesNode = rootNode.path("properties"); - Map<String, String> resultMap = new HashMap<>(); - Iterator<Map.Entry<String, JsonNode>> fields = propertiesNode.fields(); - while (fields.hasNext()) { - Map.Entry<String, JsonNode> field = fields.next(); - String key = field.getKey(); - String value = field.getValue().path("title").asText(); - resultMap.put(key, value); + } + + public static byte[] generateExcel(VipsLogicUser user, ULocale locale, LocalDateTime now, String fromStr, String toStr, List<ObservationListItem> observations) throws IOException { + ResourceBundle rb = ResourceBundle.getBundle("no.nibio.vips.logic.i18n.vipslogictexts", locale.toLocale()); + boolean isAdmin = user != null && (user.isSuperUser() || user.isOrganizationAdmin()); + LOGGER.info("Create Excel file containing {} observations for {} user", observations.size(), isAdmin ? "admin" : "regular"); + try (XSSFWorkbook workbook = new XSSFWorkbook(); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + + Font font = workbook.createFont(); + font.setBold(true); + CellStyle headerStyle = workbook.createCellStyle(); + headerStyle.setFont(font); + + // Create main sheet for all observations, with header row + Sheet mainSheet = workbook.createSheet(rb.getString("allObservations")); + createHeaderRow(isAdmin, mainSheet, headerStyle, rb); + + int mainSheetRowIndex = 1; + // Add one row for each observation in list of all observations + for (ObservationListItem item : observations) { + createItemRow(isAdmin, mainSheet, mainSheetRowIndex++, item, rb); + } + autoSizeColumns(mainSheet, 0, ColumnIndex.INDEX_DATA.getIndex(isAdmin) - 1); + + // Create meta sheet for information about the download + Sheet metaSheet = workbook.createSheet(rb.getString("downloadInfo")); + addMetaInfo(metaSheet, user, now, fromStr, toStr, observations, headerStyle, rb); + + // Prepare list of observations for each type of pest + Map<Integer, List<ObservationListItem>> pestObservations = getObservationsForEachPest(observations); + + // Create sheets for each individual pest type + for (Integer pestId : pestObservations.keySet()) { + List<ObservationListItem> observationsForPest = pestObservations.get(pestId); + ObservationListItem firstObservationForPest = observationsForPest.get(0); + String pestName = firstObservationForPest.getOrganismName(); + Sheet pestSheet = workbook.createSheet(sanitizeSheetName(pestId, pestName)); + Row headerRow = createHeaderRow(isAdmin, pestSheet, headerStyle, rb); + + // Add column titles for observation data + Map<String, String> dataColumnTitles = getObservationDataColumnTitles(firstObservationForPest.getObservationDataSchema()); + int pestSheetColIndex = ColumnIndex.INDEX_DATA.getIndex(isAdmin); + for (String key : dataColumnTitles.keySet()) { + Cell cell = headerRow.createCell(pestSheetColIndex++); + cell.setCellStyle(headerStyle); + cell.setCellValue(dataColumnTitles.get(key)); } - return resultMap; - } - /** - * Create map with pestId as key, and list of corresponding observations as value - * - * @param observations The original list of observations - * @return result map - */ - private static Map<Integer, List<ObservationListItem>> getObservationsForEachPest(List<ObservationListItem> observations) { - Map<Integer, List<ObservationListItem>> pestObservations = new HashMap<>(); - for (ObservationListItem observation : observations) { - Integer pestId = observation.getOrganismId(); - if (!pestObservations.containsKey(pestId)) { - List<ObservationListItem> observationList = new ArrayList<>(); - observationList.add(observation); - pestObservations.put(pestId, observationList); - } else { - pestObservations.get(pestId).add(observation); + int pestSheetRowIndex = 1; + for (ObservationListItem item : observationsForPest) { + Row row = createItemRow(isAdmin, pestSheet, pestSheetRowIndex++, item, rb); + + if (item.getObservationData() != null) { + Map<String, Object> observationDataMap = objectMapper.readValue(item.getObservationData(), HashMap.class); + if (observationDataMap != null) { + pestSheetColIndex = ColumnIndex.INDEX_DATA.getIndex(isAdmin); + for (String key : dataColumnTitles.keySet()) { + pestSheetColIndex = addValueToCell(row, pestSheetColIndex, observationDataMap.get(key)); + } } + } } - return pestObservations; - } + autoSizeColumns(pestSheet, ColumnIndex.ID.getIndex(isAdmin), ColumnIndex.INDEX_DATA.getIndex(isAdmin) + dataColumnTitles.size()); + } - /** - * Create first row of given sheet, with column titles dependent on user privileges - * - * @param isAdmin Whether the user is a logged in admin - * @param sheet The sheet to which a row will be added - * @param headerStyle The style to be applied to each cell in the header row - * @param rb A resource bundle enabling localized messages - * @return the newly created header row - */ - public static Row createHeaderRow(boolean isAdmin, Sheet sheet, CellStyle headerStyle, ResourceBundle rb) { - Row headerRow = sheet.createRow(0); - for (ColumnIndex columnIndex : ColumnIndex.forUser(isAdmin)) { - Cell cell = headerRow.createCell(columnIndex.getIndex(isAdmin)); - cell.setCellStyle(headerStyle); - cell.setCellValue(columnIndex.getColumnHeading(rb)); - } - return headerRow; + workbook.write(out); + return out.toByteArray(); } - - /** - * Create row with given index, for given observation list item - * - * @param isAdmin Whether the user is a logged in admin - * @param sheet The sheet to which a row will be added - * @param rowIndex The index of the row - * @param item The item of which to add data - * @return the newly created row - */ - private static Row createItemRow(boolean isAdmin, Sheet sheet, int rowIndex, ObservationListItem item, ResourceBundle rb) throws JsonProcessingException { - LocalDate localDateOfObservation = item.getTimeOfObservation().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); - Row row = sheet.createRow(rowIndex); - addObservationLink(row, ColumnIndex.ID.getIndex(isAdmin), item.getObservationId()); - addValueToCell(row, ColumnIndex.DATE.getIndex(isAdmin), localDateOfObservation.format(DATE_FORMATTER)); - - if (item.getLocationPointOfInterestId() != null) { - Integer poiNameIndex = ColumnIndex.POI_NAME.getIndex(isAdmin); - if(isAdmin) { - addPoiLink(row, poiNameIndex, item.getLocationPointOfInterestId(), item.getLocationPointOfInterestName()); - } else { - addValueToCell(row, poiNameIndex, item.getLocationPointOfInterestName()); - } - } - - if (isAdmin) { - addUserLink(row, ColumnIndex.OBSERVER_NAME.getIndex(isAdmin), item.getObserverId(), item.getObserverName()); - } - if (item.getObservationTimeSeriesId() != null) { - addTimeSeriesLink(row, ColumnIndex.OBSERVATION_TIME_SERIES_LABEL.getIndex(isAdmin), item.getObservationTimeSeriesId(), item.getObservationTimeSeriesLabel()); - } - addValueToCell(row, ColumnIndex.ORGANISM.getIndex(isAdmin), item.getOrganismName()); - addValueToCell(row, ColumnIndex.CROP_ORGANISM.getIndex(isAdmin), item.getCropOrganismName()); - addValueToCell(row, ColumnIndex.HEADING.getIndex(isAdmin), item.getObservationHeading()); - addValueToCell(row, ColumnIndex.DESCRIPTION.getIndex(isAdmin), item.getObservationText()); - - addValueToCell(row, ColumnIndex.BROADCAST.getIndex(isAdmin), getBooleanStringValue(rb, item.getBroadcastMessage())); - addValueToCell(row, ColumnIndex.POSITIVE.getIndex(isAdmin), getBooleanStringValue(rb, item.getIsPositive())); - return row; + } + + /** + * Add meta information to given sheet + * + * @param metaSheet The sheet in which to add content + * @param user The current user + * @param now The current timestamp + * @param fromStr The start of the period for which we have observations + * @param toStr The end of the period for which we have observations + * @param observations The list of observations + * @param headerStyle How to style the title cells + * @param rb Resource bundle with translations + */ + private static void addMetaInfo(Sheet metaSheet, VipsLogicUser user, LocalDateTime now, String fromStr, String toStr, List<ObservationListItem> observations, CellStyle headerStyle, ResourceBundle rb) { + Row userRow = metaSheet.createRow(0); + Cell downloadedByCell = userRow.createCell(0); + downloadedByCell.setCellStyle(headerStyle); + downloadedByCell.setCellValue(rb.getString("downloadedBy")); + userRow.createCell(1).setCellValue(user != null ? user.getFullName() : rb.getString("unregisteredUser")); + Row timeRow = metaSheet.createRow(1); + Cell downloadedTimeCell = timeRow.createCell(0); + downloadedTimeCell.setCellStyle(headerStyle); + downloadedTimeCell.setCellValue(rb.getString("downloadedTime")); + timeRow.createCell(1).setCellValue(DATE_TIME_FORMATTER.format(now)); + Row fromRow = metaSheet.createRow(2); + Cell dateFromCell = fromRow.createCell(0); + dateFromCell.setCellStyle(headerStyle); + dateFromCell.setCellValue(rb.getString("dateStart")); + fromRow.createCell(1).setCellValue(fromStr); + Row toRow = metaSheet.createRow(3); + Cell dateToCell = toRow.createCell(0); + dateToCell.setCellStyle(headerStyle); + dateToCell.setCellValue(rb.getString("dateEnd")); + toRow.createCell(1).setCellValue(toStr); + Row countRow = metaSheet.createRow(4); + Cell countCell = countRow.createCell(0); + countCell.setCellStyle(headerStyle); + countCell.setCellValue(rb.getString("observationCount")); + countRow.createCell(1).setCellValue(observations.size()); + autoSizeColumns(metaSheet, 0, 1); + } + + /** + * Create sheet name without invalid characters and within size limits + * + * @param pestId The id of the pest + * @param pestName The name of the pest + * @return a sanitized string to be used as sheet name + */ + private static String sanitizeSheetName(Integer pestId, String pestName) { + if (pestName == null || pestName.isBlank()) { + return "Id=" + pestId; } - - /** - * Add value to cell - * - * @param row The row to which the value should be added - * @param colIndex The index of the column to add the value to - * @param value The value - * @return The next index value - */ - private static Integer addValueToCell(Row row, Integer colIndex, Object value) { - Integer index = colIndex; - if (value == null) { - row.createCell(index++).setCellValue(""); - } else if (value instanceof Number) { - row.createCell(index++).setCellValue(((Number) value).intValue()); - } else { - try { - int intValue = Integer.parseInt(value.toString()); - row.createCell(index++).setCellValue(intValue); - } catch (NumberFormatException e) { - row.createCell(index++).setCellValue(value.toString()); - } - } - return index; + return pestName.replaceAll("[\\[\\]\\*/:?\\\\]", "_").substring(0, Math.min(pestName.length(), 31)); + } + + + /** + * Auto-size columns of given sheet, from startIndex to endIndex, to ensure that content fits + * + * @param sheet The sheet of which to auto-size columns + * @param startIndex The index of the first column to auto-size + * @param endIndex The index of the last column to auto-size + */ + private static void autoSizeColumns(Sheet sheet, int startIndex, int endIndex) { + for (int i = startIndex; i <= endIndex; i++) { + sheet.autoSizeColumn(i); } - - /** - * Get a localized String representing either true or false - * - * @param rb The resource bundle to get the localized string from - * @param value Either true or false - * @return A localized String - */ - private static String getBooleanStringValue(ResourceBundle rb, Boolean value) { - if (value == null) { - return null; - } else if (value) { - return rb.getString("yes"); - } - return rb.getString("no"); + } + + /** + * Create map with data property name as key, and corresponding localized name as value + * + * @param observationDataSchema The observation data schema which contains localized names and other info + * @return result map + */ + private static Map<String, String> getObservationDataColumnTitles(ObservationDataSchema observationDataSchema) throws JsonProcessingException { + JsonNode rootNode = objectMapper.readTree(observationDataSchema.getDataSchema()); + JsonNode propertiesNode = rootNode.path("properties"); + Map<String, String> resultMap = new HashMap<>(); + Iterator<Map.Entry<String, JsonNode>> fields = propertiesNode.fields(); + while (fields.hasNext()) { + Map.Entry<String, JsonNode> field = fields.next(); + String key = field.getKey(); + String value = field.getValue().path("title").asText(); + resultMap.put(key, value); } - - /** - * Add link to observation details in column containing the observation Id - * - * @param row A reference to the current row - * @param colIndex The index of the column in which the link should be added - * @param observationId The id of the observation - */ - private static void addObservationLink(Row row, Integer colIndex, Integer observationId) { - Cell cell = row.createCell(colIndex); - cell.setCellValue(observationId); - - Workbook workbook = row.getSheet().getWorkbook(); - cell.setHyperlink(createHyperlink(workbook, VIPSWEB + "/observations/" + observationId)); - cell.setCellStyle(hyperlinkCellStyle(workbook)); + return resultMap; + } + + /** + * Create map with pestId as key, and list of corresponding observations as value + * + * @param observations The original list of observations + * @return result map + */ + private static Map<Integer, List<ObservationListItem>> getObservationsForEachPest(List<ObservationListItem> observations) { + Map<Integer, List<ObservationListItem>> pestObservations = new HashMap<>(); + for (ObservationListItem observation : observations) { + Integer pestId = observation.getOrganismId(); + if (!pestObservations.containsKey(pestId)) { + List<ObservationListItem> observationList = new ArrayList<>(); + observationList.add(observation); + pestObservations.put(pestId, observationList); + } else { + pestObservations.get(pestId).add(observation); + } } - - /** - * Add link to timeseries details in column with given index - * - * @param row A reference to the current row - * @param colIndex The index of the column in which the link should be added - * @param timeSeriesId The id of the timeseries - * @param timeSeriesLabel The text which should be displayed in the cell - */ - private static void addTimeSeriesLink(Row row, Integer colIndex, Integer timeSeriesId, String timeSeriesLabel) { - Cell cell = row.createCell(colIndex); - cell.setCellValue(timeSeriesLabel); - - Workbook workbook = row.getSheet().getWorkbook(); - cell.setHyperlink(createHyperlink(workbook, VIPSWEB + "/observations/timeseries/" + timeSeriesId)); - cell.setCellStyle(hyperlinkCellStyle(workbook)); + return pestObservations; + } + + /** + * Create first row of given sheet, with column titles dependent on user privileges + * + * @param isAdmin Whether the user is a logged in admin + * @param sheet The sheet to which a row will be added + * @param headerStyle The style to be applied to each cell in the header row + * @param rb A resource bundle enabling localized messages + * @return the newly created header row + */ + public static Row createHeaderRow(boolean isAdmin, Sheet sheet, CellStyle headerStyle, ResourceBundle rb) { + Row headerRow = sheet.createRow(0); + for (ColumnIndex columnIndex : ColumnIndex.forUser(isAdmin)) { + Cell cell = headerRow.createCell(columnIndex.getIndex(isAdmin)); + cell.setCellStyle(headerStyle); + cell.setCellValue(columnIndex.getColumnHeading(rb)); } - - /** - * Add link to poi details in column with given index - * - * @param row A reference to the current row - * @param colIndex The index of the column in which the link should be added - * @param poiId The id of the poi - * @param poiName The text which should be displayed in the cell - */ - private static void addPoiLink(Row row, Integer colIndex, Integer poiId, String poiName) { - Cell cell = row.createCell(colIndex); - cell.setCellValue(poiName); - - Workbook workbook = row.getSheet().getWorkbook(); - cell.setHyperlink(createHyperlink(workbook, VIPSLOGIC + "/weatherStation?pointOfInterestId=" + poiId)); - cell.setCellStyle(hyperlinkCellStyle(workbook)); + return headerRow; + } + + /** + * Create row with given index, for given observation list item + * + * @param isAdmin Whether the user is a logged in admin + * @param sheet The sheet to which a row will be added + * @param rowIndex The index of the row + * @param item The item of which to add data + * @return the newly created row + */ + private static Row createItemRow(boolean isAdmin, Sheet sheet, int rowIndex, ObservationListItem item, ResourceBundle rb) throws JsonProcessingException { + LocalDate localDateOfObservation = item.getTimeOfObservation().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + Row row = sheet.createRow(rowIndex); + addObservationLink(row, ColumnIndex.ID.getIndex(isAdmin), item.getObservationId()); + addValueToCell(row, ColumnIndex.DATE.getIndex(isAdmin), localDateOfObservation.format(DATE_FORMATTER)); + + if (item.getLocationPointOfInterestId() != null) { + Integer poiNameIndex = ColumnIndex.POI_NAME.getIndex(isAdmin); + if(isAdmin) { + addPoiLink(row, poiNameIndex, item.getLocationPointOfInterestId(), item.getLocationPointOfInterestName()); + } else { + addValueToCell(row, poiNameIndex, item.getLocationPointOfInterestName()); + } } - /** - * Add link to user details in column with given index - * - * @param row A reference to the current row - * @param colIndex The index of the column in which the link should be added - * @param userId The id of the user - * @param userName The text which should be displayed in the cell - */ - private static void addUserLink(Row row, Integer colIndex, Integer userId, String userName) { - Cell cell = row.createCell(colIndex); - cell.setCellValue(userName); - - Workbook workbook = row.getSheet().getWorkbook(); - cell.setHyperlink(createHyperlink(workbook, VIPSLOGIC + "/user?action=viewUser&userId=" + userId)); - cell.setCellStyle(hyperlinkCellStyle(workbook)); + if (isAdmin) { + addUserLink(row, ColumnIndex.OBSERVER_NAME.getIndex(isAdmin), item.getObserverId(), item.getObserverName()); } - - private static Hyperlink createHyperlink(Workbook workbook, String url) { - CreationHelper creationHelper = workbook.getCreationHelper(); - Hyperlink hyperlink = creationHelper.createHyperlink(HyperlinkType.URL); - hyperlink.setAddress(url); - return hyperlink; + if (item.getObservationTimeSeriesId() != null) { + addTimeSeriesLink(row, ColumnIndex.OBSERVATION_TIME_SERIES_LABEL.getIndex(isAdmin), item.getObservationTimeSeriesId(), item.getObservationTimeSeriesLabel()); } - - private static CellStyle hyperlinkCellStyle(Workbook workbook) { - CellStyle hyperlinkStyle = workbook.createCellStyle(); - Font hlinkFont = workbook.createFont(); - hlinkFont.setUnderline(Font.U_SINGLE); - hlinkFont.setColor(IndexedColors.BLUE.getIndex()); - hyperlinkStyle.setFont(hlinkFont); - return hyperlinkStyle; + addValueToCell(row, ColumnIndex.ORGANISM.getIndex(isAdmin), item.getOrganismName()); + addValueToCell(row, ColumnIndex.CROP_ORGANISM.getIndex(isAdmin), item.getCropOrganismName()); + addValueToCell(row, ColumnIndex.HEADING.getIndex(isAdmin), item.getObservationHeading()); + addValueToCell(row, ColumnIndex.DESCRIPTION.getIndex(isAdmin), item.getObservationText()); + + addValueToCell(row, ColumnIndex.BROADCAST.getIndex(isAdmin), getBooleanStringValue(rb, item.getBroadcastMessage())); + addValueToCell(row, ColumnIndex.POSITIVE.getIndex(isAdmin), getBooleanStringValue(rb, item.getIsPositive())); + return row; + } + + /** + * Add value to cell + * + * @param row The row to which the value should be added + * @param colIndex The index of the column to add the value to + * @param value The value + * @return The next index value + */ + private static Integer addValueToCell(Row row, Integer colIndex, Object value) { + Integer index = colIndex; + if (value == null) { + row.createCell(index++).setCellValue(""); + } else if (value instanceof Number) { + row.createCell(index++).setCellValue(((Number) value).intValue()); + } else { + try { + int intValue = Integer.parseInt(value.toString()); + row.createCell(index++).setCellValue(intValue); + } catch (NumberFormatException e) { + row.createCell(index++).setCellValue(value.toString()); + } + } + return index; + } + + /** + * Get a localized String representing either true or false + * + * @param rb The resource bundle to get the localized string from + * @param value Either true or false + * @return A localized String + */ + private static String getBooleanStringValue(ResourceBundle rb, Boolean value) { + if (value == null) { + return null; + } else if (value) { + return rb.getString("yes"); } + return rb.getString("no"); + } + + /** + * Add link to observation details in column containing the observation Id + * + * @param row A reference to the current row + * @param colIndex The index of the column in which the link should be added + * @param observationId The id of the observation + */ + private static void addObservationLink(Row row, Integer colIndex, Integer observationId) { + Cell cell = row.createCell(colIndex); + cell.setCellValue(observationId); + + Workbook workbook = row.getSheet().getWorkbook(); + cell.setHyperlink(createHyperlink(workbook, VIPSWEB + "/observations/" + observationId)); + cell.setCellStyle(hyperlinkCellStyle(workbook)); + } + + /** + * Add link to timeseries details in column with given index + * + * @param row A reference to the current row + * @param colIndex The index of the column in which the link should be added + * @param timeSeriesId The id of the timeseries + * @param timeSeriesLabel The text which should be displayed in the cell + */ + private static void addTimeSeriesLink(Row row, Integer colIndex, Integer timeSeriesId, String timeSeriesLabel) { + Cell cell = row.createCell(colIndex); + cell.setCellValue(timeSeriesLabel); + + Workbook workbook = row.getSheet().getWorkbook(); + cell.setHyperlink(createHyperlink(workbook, VIPSWEB + "/observations/timeseries/" + timeSeriesId)); + cell.setCellStyle(hyperlinkCellStyle(workbook)); + } + + /** + * Add link to poi details in column with given index + * + * @param row A reference to the current row + * @param colIndex The index of the column in which the link should be added + * @param poiId The id of the poi + * @param poiName The text which should be displayed in the cell + */ + private static void addPoiLink(Row row, Integer colIndex, Integer poiId, String poiName) { + Cell cell = row.createCell(colIndex); + cell.setCellValue(poiName); + + Workbook workbook = row.getSheet().getWorkbook(); + cell.setHyperlink(createHyperlink(workbook, VIPSLOGIC + "/weatherStation?pointOfInterestId=" + poiId)); + cell.setCellStyle(hyperlinkCellStyle(workbook)); + } + + /** + * Add link to user details in column with given index + * + * @param row A reference to the current row + * @param colIndex The index of the column in which the link should be added + * @param userId The id of the user + * @param userName The text which should be displayed in the cell + */ + private static void addUserLink(Row row, Integer colIndex, Integer userId, String userName) { + Cell cell = row.createCell(colIndex); + cell.setCellValue(userName); + + Workbook workbook = row.getSheet().getWorkbook(); + cell.setHyperlink(createHyperlink(workbook, VIPSLOGIC + "/user?action=viewUser&userId=" + userId)); + cell.setCellStyle(hyperlinkCellStyle(workbook)); + } + + private static Hyperlink createHyperlink(Workbook workbook, String url) { + CreationHelper creationHelper = workbook.getCreationHelper(); + Hyperlink hyperlink = creationHelper.createHyperlink(HyperlinkType.URL); + hyperlink.setAddress(url); + return hyperlink; + } + + private static CellStyle hyperlinkCellStyle(Workbook workbook) { + CellStyle hyperlinkStyle = workbook.createCellStyle(); + Font hlinkFont = workbook.createFont(); + hlinkFont.setUnderline(Font.U_SINGLE); + hlinkFont.setColor(IndexedColors.BLUE.getIndex()); + hyperlinkStyle.setFont(hlinkFont); + return hyperlinkStyle; + } } 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 13230b38ed8e0db010f43a5ee3dd22fdef17d584..a9b44ce07122a6969226b89c41b1e1c4410d5756 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 @@ -1,20 +1,20 @@ # - # Copyright (c) 2014 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/>. - # +# Copyright (c) 2014 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/>. +# isPositiveRegistration=Skadegj\u00f8rer p\u00e5vist ALTERNARIA = Alternariamodell diff --git a/src/main/webapp/css/mapModal.css b/src/main/webapp/css/mapModal.css index 161c578b24d60905e1992d2e42be772c7aec0201..40990f46c4d456ba5612050c98205438245b592d 100644 --- a/src/main/webapp/css/mapModal.css +++ b/src/main/webapp/css/mapModal.css @@ -2,19 +2,22 @@ display: none; position: fixed; z-index: 1000; - left: 0; - top: 0; - width: 100%; - height: 100%; + left: 50%; + top: 50%; + width: 90%; + height: 90%; overflow: hidden; background-color: rgba(0, 0, 0, 0.9); + transform: translate(-50%, -50%); + border: 2px solid #fff; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); + border-radius: 15px; } -.map-modal-content { - position: relative; - height: 100%; - width: 100%; - background-color: #fefefe; +.map-modal.show { + display: flex; + justify-content: center; + align-items: center; } #selected-point-info-panel { @@ -72,4 +75,4 @@ #new-point-form { z-index: 1200; position: absolute; -} \ No newline at end of file +} diff --git a/src/main/webapp/js/mapModal.js b/src/main/webapp/js/mapModal.js index 33770f1f148cca2ee05790f2e39c774c3811e150..b5876c773dccebc3f433444db61f575973f23762 100644 --- a/src/main/webapp/js/mapModal.js +++ b/src/main/webapp/js/mapModal.js @@ -122,7 +122,7 @@ class MapModal { addMapContainer(parentDiv, id) { const mapContainer = document.createElement('div'); mapContainer.id = id; - mapContainer.style.height = '100vh'; + mapContainer.style.height = '100%'; mapContainer.style.width = '100%'; mapContainer.style.position = 'relative'; parentDiv.appendChild(mapContainer); @@ -288,7 +288,7 @@ class MapModal { const containerPoint = this.map.latLngToContainerPoint(latlng); this.selectedNewPointMarker = circleMarker(latlng, this.styleOfSelectedPointMarker(true)).addTo(this.map); - const newPointFormElement = this.addHtmlElementNewPointForm(containerPoint.x + 25, containerPoint.y - 25, latlng.lat, latlng.lng) + const newPointFormElement = this.addHtmlElementNewPointForm(containerPoint.x, containerPoint.y, latlng.lat, latlng.lng) DomEvent.disableClickPropagation(newPointFormElement); document.addEventListener('click', this.handleClickOutsidePointForm.bind(this), true); @@ -390,43 +390,57 @@ class MapModal { * @returns {Element} */ addHtmlElementNewPointForm(positionX, positionY, latitude, longitude) { - const html = ` - <div id="new-point-form" class="panel panel-default" style="top: ${positionY}px; left: ${positionX}px;"> - <div class="panel-heading"> - <h4 class="panel-title">${this.translations.createNewLocation}</h4> - <span id="close-button" style="position: absolute; top: 5px; right: 10px; cursor: pointer; font-size: 18px;">×</span> + const form = document.createElement("div"); + form.id="new-point-form" + form.classList.add("panel"); + form.classList.add("panel-default"); + + // Adjust position so that the form is always displayed within the map + const mapWidth = this.mapModalElement.offsetWidth; + const mapHeight = this.mapModalElement.offsetHeight; + const formWidth = 200; // approximately + const formHeight = 400; // approximately + if (positionX + formWidth > mapWidth) { + positionX = mapWidth - formWidth - 10; // 10px padding from the edge + } + if (positionY + formHeight > mapHeight) { + positionY = mapHeight - formHeight - 10; // 10px padding from the edge + } + form.style.left = `${positionX}px`; + form.style.top = `${positionY}px`; + + form.innerHTML = ` + <div class="panel-heading"> + <h4 class="panel-title">${this.translations.createNewLocation}</h4> + <span id="close-button" style="position: absolute; top: 5px; right: 10px; cursor: pointer; font-size: 18px;">×</span> + </div> + <div class="panel-body"> + <div class="form-group"> + <label for="name">${this.translations.name}:</label> + <input type="text" class="form-control" id="name" name="name"> + </div> + <div class="form-group"> + <label for="latitude">${this.translations.latitude}:</label> + <input type="text" class="form-control" id="latitude" name="latitude" value="${latitude.toFixed(this.coordinatePrecision)}"> + </div> + <div class="form-group"> + <label for="longitude">${this.translations.longitude}:</label> + <input type="text" class="form-control" id="longitude" name="longitude" value="${longitude.toFixed(this.coordinatePrecision)}"> </div> - <div class="panel-body"> - <div class="form-group"> - <label for="name">${this.translations.name}:</label> - <input type="text" class="form-control" id="name" name="name"> - </div> - <div class="form-group"> - <label for="latitude">${this.translations.latitude}:</label> - <input type="text" class="form-control" id="latitude" name="latitude" value="${latitude.toFixed(this.coordinatePrecision)}"> - </div> - <div class="form-group"> - <label for="longitude">${this.translations.longitude}:</label> - <input type="text" class="form-control" id="longitude" name="longitude" value="${longitude.toFixed(this.coordinatePrecision)}"> - </div> - <div class="form-group"> - <label for="poiTypeSelect">${this.translations.type}:</label> - <select class="form-control" id="type" name="type"> - <option value="2">${this.typeNameMap[2]}</option> - <option value="3">${this.typeNameMap[3]}</option> - <option value="5">${this.typeNameMap[5]}</option> - </select> - </div> - <div class="form-group text-right"> - <button id="submit-button" class="btn btn-primary">${this.translations.submitLocation}</button> - </div> + <div class="form-group"> + <label for="poiTypeSelect">${this.translations.type}:</label> + <select class="form-control" id="type" name="type"> + <option value="2">${this.typeNameMap[2]}</option> + <option value="3">${this.typeNameMap[3]}</option> + <option value="5">${this.typeNameMap[5]}</option> + </select> </div> + <div class="form-group text-right"> + <button id="submit-button" class="btn btn-primary">${this.translations.submitLocation}</button> + </div> </div>`; - const tmpContainer = document.createElement("div"); - tmpContainer.innerHTML = html; - const htmlElement = tmpContainer.querySelector('#new-point-form'); - this.mapContainerElement.appendChild(htmlElement); - return htmlElement; + this.mapContainerElement.appendChild(form); + return form; } /** @@ -438,7 +452,9 @@ class MapModal { * @param zoomLevel */ openModal(selectedPointOfInterestId, latitude, longitude, zoomLevel) { - this.mapModalElement.style.display = 'block'; + this.mapModalElement.style.display = 'flex'; + this.mapModalElement.style.justifyContent = 'center'; + this.mapModalElement.style.alignItems = 'center'; this.initMap(latitude, longitude, zoomLevel); if (selectedPointOfInterestId) { this.selectPointById(selectedPointOfInterestId); @@ -643,4 +659,4 @@ const LegendControl = Control.extend({ }); // Export the module -export default MapModal; \ No newline at end of file +export default MapModal; diff --git a/src/main/webapp/map_applications/phytophthora/js/map.js b/src/main/webapp/map_applications/phytophthora/js/map.js index cbbf27c69596b7afdbbacd7a3404fbc0072138f3..52e59a2ed79de25d5fce8cb5b9b47ee4ea2cff73 100755 --- a/src/main/webapp/map_applications/phytophthora/js/map.js +++ b/src/main/webapp/map_applications/phytophthora/js/map.js @@ -36,8 +36,7 @@ var geolocation; * Initializes the map with all its layers * @returns {undefined} */ -async function initMap() -{ +async function initMap() { var features = new ol.Collection(); // Icon styling for the observations layer @@ -45,52 +44,52 @@ async function initMap() var styles = { // Bøk = rød 'fagus sylvatica': [new ol.style.Style({ - image: new ol.style.Circle({ - fill: new ol.style.Fill({color: [255, 0, 0, 1]}), - stroke: new ol.style.Stroke({width: 1, color: [0, 0, 0, 1]}), - radius: iconRadius - }) - })], + image: new ol.style.Circle({ + fill: new ol.style.Fill({ color: [255, 0, 0, 1] }), + stroke: new ol.style.Stroke({ width: 1, color: [0, 0, 0, 1] }), + radius: iconRadius + }) + })], // Gråor = dyp oransje 'alnus incana': [new ol.style.Style({ - image: new ol.style.Circle({ - fill: new ol.style.Fill({color: [239, 133, 19, 1]}), - stroke: new ol.style.Stroke({width: 1, color: [0, 0, 0, 1]}), - radius: iconRadius - }) - })], + image: new ol.style.Circle({ + fill: new ol.style.Fill({ color: [239, 133, 19, 1] }), + stroke: new ol.style.Stroke({ width: 1, color: [0, 0, 0, 1] }), + radius: iconRadius + }) + })], // Eik = gul 'quercus': [new ol.style.Style({ - image: new ol.style.Circle({ - fill: new ol.style.Fill({color: [239, 236, 19, 1]}), - stroke: new ol.style.Stroke({width: 1, color: [0, 0, 0, 1]}), - radius: iconRadius - }) - })], + image: new ol.style.Circle({ + fill: new ol.style.Fill({ color: [239, 236, 19, 1] }), + stroke: new ol.style.Stroke({ width: 1, color: [0, 0, 0, 1] }), + radius: iconRadius + }) + })], // Lønn = grønn 'acer': [new ol.style.Style({ - image: new ol.style.Circle({ - fill: new ol.style.Fill({color: [0, 255, 0, 1]}), - stroke: new ol.style.Stroke({width: 1, color: [0, 0, 0, 1]}), - radius: iconRadius - }) - })], + image: new ol.style.Circle({ + fill: new ol.style.Fill({ color: [0, 255, 0, 1] }), + stroke: new ol.style.Stroke({ width: 1, color: [0, 0, 0, 1] }), + radius: iconRadius + }) + })], // Svartor = grågrønn - 'svartor': [new ol.style.Style({ - image: new ol.style.Circle({ - fill: new ol.style.Fill({color: [122, 175, 131, 1]}), - stroke: new ol.style.Stroke({width: 1, color: [0, 0, 0, 1]}), - radius: iconRadius - }) - })], + 'alnus glutinosa': [new ol.style.Style({ + image: new ol.style.Circle({ + fill: new ol.style.Fill({ color: [122, 175, 131, 1] }), + stroke: new ol.style.Stroke({ width: 1, color: [0, 0, 0, 1] }), + radius: iconRadius + }) + })], // Planteriket = blå 'plantae': [new ol.style.Style({ - image: new ol.style.Circle({ - fill: new ol.style.Fill({color: [0, 0, 255, 1]}), - stroke: new ol.style.Stroke({width: 1, color: [0, 0, 0, 1]}), - radius: iconRadius - }) + image: new ol.style.Circle({ + fill: new ol.style.Fill({ color: [0, 0, 255, 1] }), + stroke: new ol.style.Stroke({ width: 1, color: [0, 0, 0, 1] }), + radius: iconRadius }) + }) ] }; @@ -102,24 +101,21 @@ async function initMap() style: function (feature, resolution) { // Site that has been cleared is all black var observationData = JSON.parse(feature.get("observationData")); - + var retVal = null; - if (feature.get("cropOrganism") != null && feature.get("cropOrganism")["latinName"] != null) - { + if (feature.get("cropOrganism") != null && feature.get("cropOrganism")["latinName"] != null) { retVal = styles[feature.get("cropOrganism")["latinName"].toLowerCase()]; - } else - { + } else { retVal = styles["plantae"]; } //console.info(retVal[0].getImage().getStroke().getWidth()); // If symptom has been registered, mark with inner black dot - if (observationData["symptom"] != "Ikke symptom" && observationData["symptom"] != "Irrelevant") - { + if (observationData["symptom"] != "Ikke symptom" && observationData["symptom"] != "Irrelevant") { retVal = [ new ol.style.Style({ image: new ol.style.Circle({ - fill: new ol.style.Fill({color: [0, 0, 0, 1]}), - stroke: new ol.style.Stroke({width: 8, color: retVal[0].getImage().getFill().getColor()}), + fill: new ol.style.Fill({ color: [0, 0, 0, 1] }), + stroke: new ol.style.Stroke({ width: 8, color: retVal[0].getImage().getFill().getColor() }), radius: iconRadius }) }) @@ -136,12 +132,12 @@ async function initMap() features: new ol.Collection() }), style: [new ol.style.Style({ - image: new ol.style.Circle({ - fill: new ol.style.Fill({color: [255, 255, 255, 1]}), - stroke: new ol.style.Stroke({color: [0, 0, 0, 1], width: 3, lineDash: [2, 2]}), - radius: 10 - }) - })] + image: new ol.style.Circle({ + fill: new ol.style.Fill({ color: [255, 255, 255, 1] }), + stroke: new ol.style.Stroke({ color: [0, 0, 0, 1], width: 3, lineDash: [2, 2] }), + radius: 10 + }) + })] }); @@ -157,15 +153,15 @@ async function initMap() }); var topo = - new ol.layer.Tile({ - opacity: 1, - source: new ol.source.WMTS(/** @type {!olx.source.WMTSOptions} */ (options)) - }); - - + new ol.layer.Tile({ + opacity: 1, + source: new ol.source.WMTS(/** @type {!olx.source.WMTSOptions} */(options)) + }); + + map = new ol.Map({ target: 'map', - controls: ol.control.defaults({attribution: false}), // Hide the attribution + controls: ol.control.defaults({ attribution: false }), // Hide the attribution layers: [ topo, featureOverlay, @@ -186,19 +182,19 @@ async function initMap() }); var positionFeature = new ol.Feature(); positionFeature.setStyle( - new ol.style.Style({ - image: new ol.style.Circle({ - radius: 6, - fill: new ol.style.Fill({ - color: '#3399CC', - }), - stroke: new ol.style.Stroke({ - color: '#fff', - width: 2, - }), + new ol.style.Style({ + image: new ol.style.Circle({ + radius: 6, + fill: new ol.style.Fill({ + color: '#3399CC', }), - }) - ); + stroke: new ol.style.Stroke({ + color: '#fff', + width: 2, + }), + }), + }) + ); geolocation.on('change:position', function () { var coordinates = geolocation.getPosition(); @@ -223,9 +219,9 @@ async function initMap() map.on('click', function (evt) { //features = [] var feature = map.forEachFeatureAtPixel( - evt.pixel, function (ft, l) { - return ft; - } + evt.pixel, function (ft, l) { + return ft; + } ); var vectorSource = newFeatureOverlay.getSource(); @@ -237,8 +233,7 @@ async function initMap() var fakeFeature = createFeature(feature.getGeometry().getCoordinates()); vectorSource.addFeature(fakeFeature); displayFeature(feature); - } else if (registration) - { + } else if (registration) { var newFeature = createFeature(map.getCoordinateFromPixel(evt.pixel)); vectorSource.addFeature(newFeature); editFeature(newFeature.getId()); @@ -251,10 +246,8 @@ async function initMap() let openLayersDefaultStyle = undefined; -function getOpenLayersDefaultStyle() -{ - if (openLayersDefaultStyle == undefined) - { +function getOpenLayersDefaultStyle() { + if (openLayersDefaultStyle == undefined) { var fill = new ol.style.Fill({ color: 'rgba(255,255,255,0.4)' }); @@ -284,8 +277,7 @@ function getOpenLayersDefaultStyle() * @param {type} fromSeason * @returns {undefined} */ -function getAndRenderObservations(fromSeason) -{ +function getAndRenderObservations(fromSeason) { //console.info("getAndRenderObservations(" + season + ")"); $.getJSON("/rest/observation/filter/1/geoJSON?from=" + fromSeason + "-01-01&pestId=" + phytophthora.organismId, function (geoData) { //console.info(geoData) @@ -308,66 +300,58 @@ function getAndRenderObservations(fromSeason) * @param {type} countyNo * @returns {undefined} */ -function getAndRenderObservationsForReport(fromSeason, countyNo = "-1") -{ +function getAndRenderObservationsForReport(fromSeason, countyNo = "-1") { //console.info("getAndRenderObservations(" + season + ")"); $.getJSON("/rest/observation/filter/1/geoJSON?from=" + fromSeason + "-01-01&pestId=" + phytophthora.organismId, function (geoData) { //console.info(geoData) // Filter by county $.getJSON("/corsproxy/https://ws.geonorge.no/kommuneinfo/v1/fylker/" + countyNo + "/omrade") - .always(function (serviceResponse) { - //console.info(geoData); - //console.info(serviceResponse); - - if (parseInt(countyNo) > 0) - { - var filteredFeatures = []; - for (var i = 0; i < geoData.features.length; i++) - { - var featureToBeFiltered = geoData.features[i]; - coordinate = proj4("EPSG:4326", "EPSG:4258", [featureToBeFiltered.geometry.coordinates[0], featureToBeFiltered.geometry.coordinates[1]]); - // For some weird reason, d3 returns NOT contains in our case - if (!d3.geoContains(serviceResponse.omrade, coordinate)) - { - //console.info(featureToBeFiltered); - filteredFeatures.push(featureToBeFiltered); - } + .always(function (serviceResponse) { + //console.info(geoData); + //console.info(serviceResponse); + + if (parseInt(countyNo) > 0) { + var filteredFeatures = []; + for (var i = 0; i < geoData.features.length; i++) { + var featureToBeFiltered = geoData.features[i]; + coordinate = proj4("EPSG:4326", "EPSG:4258", [featureToBeFiltered.geometry.coordinates[0], featureToBeFiltered.geometry.coordinates[1]]); + // For some weird reason, d3 returns NOT contains in our case + if (!d3.geoContains(serviceResponse.omrade, coordinate)) { //console.info(featureToBeFiltered); + filteredFeatures.push(featureToBeFiltered); } - //console.info(filteredFeatures); - geoData.features = filteredFeatures; + //console.info(featureToBeFiltered); } + //console.info(filteredFeatures); + geoData.features = filteredFeatures; + } - var format = new ol.format.GeoJSON(); + var format = new ol.format.GeoJSON(); - var drawnfeatures = format.readFeatures(geoData, { - //dataProjection: "EPSG:32633", - dataProjection: "EPSG:4326", - featureProjection: map.getView().getProjection().getCode() - }); - featureOverlay.getSource().clear(); - featureOverlay.getSource().addFeatures(drawnfeatures); - } - ); + var drawnfeatures = format.readFeatures(geoData, { + //dataProjection: "EPSG:32633", + dataProjection: "EPSG:4326", + featureProjection: map.getView().getProjection().getCode() + }); + featureOverlay.getSource().clear(); + featureOverlay.getSource().addFeatures(drawnfeatures); + } + ); }); } -function toggleRegistration(theButton) -{ - if (registration) - { +function toggleRegistration(theButton) { + if (registration) { theButton.title = "Registrering er AV"; theButton.style.color = "white"; - } else - { + } else { theButton.title = "Registrering er PÅ"; theButton.style.color = "red"; } registration = !registration; } -function toggleTracking(theButton) -{ +function toggleTracking(theButton) { geolocation.setTracking(!geolocation.getTracking()); theButton.style.backgroundColor = geolocation.getTracking() ? "green" : "rgba(0,60,136,.5)"; theButton.title = geolocation.getTracking() ? "Vis min posisjon er PÅ" : "Vis min posisjon er AV"; @@ -378,10 +362,8 @@ function toggleTracking(theButton) * @param {type} coordinate * @returns {createFeature.newFeature|ol.Feature} */ -var createFeature = function (coordinate) -{ - if (coordinate.length == 2) - { +var createFeature = function (coordinate) { + if (coordinate.length == 2) { coordinate = [coordinate[0], coordinate[1], 0]; } var point = new ol.geom.Point(coordinate); @@ -401,8 +383,7 @@ var createFeature = function (coordinate) return newFeature; } -var displayFeature = function (feature) -{ +var displayFeature = function (feature) { var featureForm = document.getElementById("featureForm"); var observationData = JSON.parse(feature.get("observationData")); @@ -440,8 +421,7 @@ var forekomsttypeLatinskeNavn = [ var forekomsttyper = []; var phytophthora = {}; -function initForekomsttyper() -{ +function initForekomsttyper() { $.getJSON("/rest/organism/search/latinnames?keywords=" + forekomsttypeLatinskeNavn.join(","), function (data) { forekomsttyper = data; }); @@ -449,70 +429,65 @@ function initForekomsttyper() function initPhytophthora() { $.getJSON("/rest/organism/search/latinnames?keywords=Phytophthora sp", function (data) { - if (data.length == 1) - { + if (data.length == 1) { phytophthora = data[0]; initMap(); } }); } -var getCropOrganism = function (organismId) -{ - for (var i = 0; i < forekomsttyper.length; i++) - { - if (forekomsttyper[i].organismId == organismId) - { +var getCropOrganism = function (organismId) { + for (var i = 0; i < forekomsttyper.length; i++) { + if (forekomsttyper[i].organismId == organismId) { return forekomsttyper[i]; } } } -var symptoms = ["Flekker", "Glisne kroner","Oppsprekking","Død","Andre symptom","Andre skader","Ikke symptom","Irrelevant"]; +var symptoms = ["Flekker", "Glisne kroner", "Oppsprekking", "Død", "Andre symptom", "Andre skader", "Ikke symptom", "Irrelevant"]; var funns = ["[Ukjent]", "Phytophthora gonapodyides", "Phytophthora lacustris", "Phytophthora plurivora", "Phytophthora cambivora", "Phytophthora cactorum", "Phytophthora sp"] -var provetypes = ["[Ikke prøve]","Jord","Vev","Vann","Blad","Bait"]; +var provetypes = ["[Ikke prøve]", "Jord", "Vev", "Vann", "Blad", "Bait"]; -var editFeature = function (featureId) -{ +var editFeature = function (featureId) { var feature = featureId > 0 ? featureOverlay.getSource().getFeatureById(featureId) - : newFeatureOverlay.getSource().getFeatureById(featureId); + : newFeatureOverlay.getSource().getFeatureById(featureId); var observationData = JSON.parse(feature.get("observationData")); var timeOfObservation = new moment(feature.get("timeOfObservation")); var featureForm = document.getElementById("featureForm"); var html = - '<button type="button" onclick="unFocusForm()" title="Avbryt">X</button>' + - (featureId > 0 ? '<button type="button" onclick="deleteFeature(' + featureId + ')">Delete</button>' : '') + - '<h3>' + (featureId > 0 ? "R" : "Ny r") + 'egistrering</h3>' + - '<table>' + - '<tr><td>Plante</td><td>' + - generateCropSelect("forekomsttype", forekomsttyper, feature.get("cropOrganism")["organismId"]) + - '</td></tr>' + - '<tr><td>Plante spes.</td><td>' + - '<input type="text" id="plantespes" name="plantespes" size="15" value="' + (observationData["plantespes"] != null ? observationData["plantespes"] : "") + '"/></td></tr>' + - '<tr><td>Symptom</td><td>' + - generateSelect("symptom", symptoms, observationData["symptom"]) + - '</td></tr>' + - '<tr><td>Sym spes.</td><td>' + - '<input type="text" id="symspes" name="symspes" size="15" value="' + (observationData["symspes"] != null ? observationData["symspes"] : "") + '"/></td></tr>' + - - '<tr><td>Prøvetype</td><td>' + - generateSelect("provetype", provetypes, observationData["provetype"]) + - '</td></tr>' + - '<tr><td>Prøvenummer</td><td>' + - '<input type="text" id="provenummer" name="provenummer" size="15" value="' + (observationData["provenummer"] != null ? observationData["provenummer"] : "") + '"/></td></tr>' + - '<tr><td>Funn</td><td>' + - generateSelect("funn", funns, observationData["funn"]) + - '</td></tr>' + - '<tr><td>Mer info</td><td>' + - '<textarea id="beskrivelse" name="beskrivelse">' + (feature.get("observationText") != null ? feature.get("observationText") : "") + '</textarea>' + - '</td></tr>' + - '<tr><td>Dato</td><td>' + - '<input type="text" id="dato" name="dato" size="10" value="' + timeOfObservation.format("DD.MM.YYYY") + '"/></td></tr>' + - '<tr><td></td><td>' + - '<input type="submit" value="Lagre" onclick="storeFeature(' + feature.getId() + ');"/></td></tr>' + - '</table>'; + '<button type="button" onclick="unFocusForm()" title="Avbryt">X</button>' + + (featureId > 0 ? '<button type="button" onclick="deleteFeature(' + featureId + ')">Delete</button>' : '') + + '<h3>' + (featureId > 0 ? "R" : "Ny r") + 'egistrering</h3>' + + '<table>' + + '<tr><td>Plante</td><td>' + + generateCropSelect("forekomsttype", forekomsttyper, feature.get("cropOrganism")["organismId"]) + + '</td></tr>' + + '<tr><td>Plante spes.</td><td>' + + '<input type="text" id="plantespes" name="plantespes" size="15" value="' + (observationData["plantespes"] != null ? observationData["plantespes"] : "") + '"/></td></tr>' + + '<tr><td>Symptom</td><td>' + + generateSelect("symptom", symptoms, observationData["symptom"]) + + '</td></tr>' + + '<tr><td>Sym spes.</td><td>' + + '<input type="text" id="symspes" name="symspes" size="15" value="' + (observationData["symspes"] != null ? observationData["symspes"] : "") + '"/></td></tr>' + + + '<tr><td>Prøvetype</td><td>' + + generateSelect("provetype", provetypes, observationData["provetype"]) + + '</td></tr>' + + '<tr><td>Prøvenummer</td><td>' + + '<input type="text" id="provenummer" name="provenummer" size="15" value="' + (observationData["provenummer"] != null ? observationData["provenummer"] : "") + '"/></td></tr>' + + '<tr><td>Funn</td><td>' + + generateSelect("funn", funns, observationData["funn"]) + + '</td></tr>' + + '<tr><td>Mer info</td><td>' + + '<textarea id="beskrivelse" name="beskrivelse">' + (feature.get("observationText") != null ? feature.get("observationText") : "") + '</textarea>' + + '</td></tr>' + + '<tr><td>Dato</td><td>' + + '<input type="text" id="dato" name="dato" size="10" value="' + timeOfObservation.format("DD.MM.YYYY") + '"/></td></tr>' + + '<tr><td></td><td>' + + '<input type="submit" value="Lagre" onclick="storeFeature(' + feature.getId() + ');"/></td></tr>' + + '</table>'; featureForm.innerHTML = html; @@ -520,10 +495,9 @@ var editFeature = function (featureId) //console.info(feature); }; -var storeFeature = function (featureId) -{ +var storeFeature = function (featureId) { var feature = featureId > 0 ? featureOverlay.getSource().getFeatureById(featureId) - : newFeatureOverlay.getSource().getFeatureById(featureId); + : newFeatureOverlay.getSource().getFeatureById(featureId); // Store, clear newFeature layer // Need to add feature as payload @@ -541,8 +515,7 @@ var storeFeature = function (featureId) var observationText = document.getElementById("beskrivelse").value; var observationHeading = "Registrering av phytophthora"; var timeOfObservation = moment(document.getElementById("dato").value + "+0200", "DD.MM.YYYYZ"); - if (timeOfObservation.year() < 2000) - { + if (timeOfObservation.year() < 2000) { alert("Feil dato (før år 2000). Datoformat er DD.MM.ÅÅÅÅ"); return; } @@ -585,22 +558,18 @@ var storeFeature = function (featureId) // If storing an existing feature, remove the one // that was there before storing, since the returned // one has a new gisId (featureId) - if (featureId > 0) - { + if (featureId > 0) { featureOverlay.getSource().removeFeature(feature); } featureOverlay.getSource().addFeatures(drawnfeatures); unFocusForm(); }, error: function (jqXHR, textStatus, errorThrown) { - if (jqXHR.status == 401) - { - if (confirm("Kan ikke lagre fordi du er logget ut av applikasjonen. Klikk OK for å logge inn.")) - { + if (jqXHR.status == 401) { + if (confirm("Kan ikke lagre fordi du er logget ut av applikasjonen. Klikk OK for å logge inn.")) { window.location.reload(); } - } else - { + } else { alert("Beklager, en feil oppsto. Status = " + jqXHR.status + ", eventuell feilmelding: " + textStatus); } } @@ -614,10 +583,8 @@ var storeFeature = function (featureId) * @param {type} featureId * @returns {undefined} */ -var deleteFeature = function (featureId) -{ - if (!confirm("Er du sikker på at du vil slette?")) - { +var deleteFeature = function (featureId) { + if (!confirm("Er du sikker på at du vil slette?")) { return; } @@ -631,51 +598,41 @@ var deleteFeature = function (featureId) // If storing an existing feature, remove the one // that was there before storing, since the returned // one has a new gisId (featureId) - if (featureId > 0) - { + if (featureId > 0) { featureOverlay.getSource().removeFeature(feature); } unFocusForm(); }, error: function (jqXHR, textStatus, errorThrown) { - if (jqXHR.status == 401) - { - if (confirm("Kan ikke slette fordi du er logget ut av applikasjonen. Klikk OK for å logge inn.")) - { + if (jqXHR.status == 401) { + if (confirm("Kan ikke slette fordi du er logget ut av applikasjonen. Klikk OK for å logge inn.")) { window.location.reload(); } - } else - { + } else { alert("Beklager, en feil oppsto. Status = " + jqXHR.status + ", eventuell feilmelding: " + textStatus); } } }); } -var generateSelect = function (selectName, options, preselect) -{ +var generateSelect = function (selectName, options, preselect) { var retVal = '<select id="' + selectName + '" name="' + selectName + '">'; - for (var i = 0; i < options.length; i++) - { + for (var i = 0; i < options.length; i++) { retVal += '<option value="' + options[i] + '"' + (options[i] == preselect ? " selected=\"selected\"" : "") + '">' + options[i] + '</option>'; } retVal += '</select>'; return retVal; } -var generateCropSelect = function (selectName, cropOrganisms, preselect) -{ +var generateCropSelect = function (selectName, cropOrganisms, preselect) { var retVal = '<select id="' + selectName + '" name="' + selectName + '">'; - for (var c = 0; c < forekomsttypeLatinskeNavn.length; c++) - { + for (var c = 0; c < forekomsttypeLatinskeNavn.length; c++) { currentLatinName = forekomsttypeLatinskeNavn[c]; - for (var i = 0; i < cropOrganisms.length; i++) - { - if (cropOrganisms[i].latinName == currentLatinName) - { + for (var i = 0; i < cropOrganisms.length; i++) { + if (cropOrganisms[i].latinName == currentLatinName) { retVal += '<option value="' + cropOrganisms[i].organismId + '"' + (cropOrganisms[i].organismId == preselect ? " selected=\"selected\"" : "") + '">' + - (currentLatinName == "Plantae" ? "Annet" : getLocalizedOrganismName(cropOrganisms[i], hardcodedLanguage)) - + '</option>'; + (currentLatinName == "Plantae" ? "Annet" : getLocalizedOrganismName(cropOrganisms[i], hardcodedLanguage)) + + '</option>'; } } } @@ -683,22 +640,19 @@ var generateCropSelect = function (selectName, cropOrganisms, preselect) return retVal; } -var focusForm = function () -{ +var focusForm = function () { var featureForm = document.getElementById("featureForm"); featureForm.style.display = "block"; } -var unFocusForm = function () -{ +var unFocusForm = function () { var featureForm = document.getElementById("featureForm"); featureForm.style.display = "none"; // Also remove feature (if one) on the New feature overlay newFeatureOverlay.getSource().clear(); } -var navigateTo = function (center) -{ +var navigateTo = function (center) { var centerPosition = ol.proj.transform(center, 'EPSG:4326', 'EPSG:3857'); view = new ol.View({ center: centerPosition, @@ -717,16 +671,13 @@ var navigateTo = function (center) */ function showLocation() { if (navigator.geolocation) { - if (window.location.protocol === "http:") - { + if (window.location.protocol === "http:") { navigator.geolocation.getCurrentPosition(function (geopositionObj) { // TODO: position and display location icon } ); - } else - { - if (confirm("Lokalisering fungerer bare over https (sikker tilkobling mellom nettleser og tjener). Klikk OK for å gå til sikker tilkobling.")) - { + } else { + if (confirm("Lokalisering fungerer bare over https (sikker tilkobling mellom nettleser og tjener). Klikk OK for å gå til sikker tilkobling.")) { window.location = "https:" + window.location.href.substring(window.location.protocol.length); } } @@ -737,16 +688,13 @@ function showLocation() { function navToLocation() { if (navigator.geolocation) { - if (window.location.protocol === "https:") - { + if (window.location.protocol === "https:") { navigator.geolocation.getCurrentPosition(function (geopositionObj) { navigateTo([geopositionObj.coords.longitude, geopositionObj.coords.latitude]); } ); - } else - { - if (confirm("Lokalisering fungerer bare over https (sikker tilkobling mellom nettleser og tjener). Klikk OK for å gå til sikker tilkobling.")) - { + } else { + if (confirm("Lokalisering fungerer bare over https (sikker tilkobling mellom nettleser og tjener). Klikk OK for å gå til sikker tilkobling.")) { window.location = "https:" + window.location.href.substring(window.location.protocol.length); } } @@ -760,41 +708,36 @@ function navToLocation() { * @param {type} selectedSeason * @returns {undefined} */ -function initSeasonSelectList(selectedSeason) -{ +function initSeasonSelectList(selectedSeason) { // What's the current year? var thisYear = new Date().getFullYear(); // How many years do we go back? $.ajax({ url: "/rest/observation/first/" + phytophthora.organismId }) - .done(function (data) { - var time = moment(data); - var firstYear = time.toDate().getFullYear(); - // Loop gjennom år, lag valgliste :-) - var startSeasonList = document.getElementById("startSeason"); - for (var i = firstYear; i <= thisYear; i++) - { - var yearOpt = new Option("", i); - yearOpt.innerHTML = i + " →" - if (i == selectedSeason) - { - yearOpt.selected = true; - } - startSeasonList.options[startSeasonList.options.length] = yearOpt; - } - }) - .fail(function (jqXHR, textStatus, errorThrown) { - if(jqXHR.status==404) - { - alert("Could not find any observations of organism with id=" + phytophthora.organismId); - document.getElementById("startSeason").options[0] = new Option("" + thisYear, thisYear); - } - else - { - alert(textStatus + ": " + errorThrown); + .done(function (data) { + var time = moment(data); + var firstYear = time.toDate().getFullYear(); + // Loop gjennom år, lag valgliste :-) + var startSeasonList = document.getElementById("startSeason"); + for (var i = firstYear; i <= thisYear; i++) { + var yearOpt = new Option("", i); + yearOpt.innerHTML = i + " →" + if (i == selectedSeason) { + yearOpt.selected = true; } - }); + startSeasonList.options[startSeasonList.options.length] = yearOpt; + } + }) + .fail(function (jqXHR, textStatus, errorThrown) { + if (jqXHR.status == 404) { + alert("Could not find any observations of organism with id=" + phytophthora.organismId); + document.getElementById("startSeason").options[0] = new Option("" + thisYear, thisYear); + } + else { + alert(textStatus + ": " + errorThrown); + } + }); } diff --git a/src/main/webapp/templates/observationForm.ftl b/src/main/webapp/templates/observationForm.ftl index 4945cddf3dc3a466fb916e030ac26cb8e97f097a..ed54a05a75721de21b52149fca468f43adb1bba6 100755 --- a/src/main/webapp/templates/observationForm.ftl +++ b/src/main/webapp/templates/observationForm.ftl @@ -42,7 +42,7 @@ <script type="text/javascript" src="/js/poiFormMap.js"></script> <script type="text/javascript"> var organizationId = ${user.organizationId.organizationId}; - var selectedCropId = <#if observation.cropOrganism?has_content>${observation.cropOrganism.organismId}<#else>null</#if>; + var selectedCropId = <#if observation.cropOrganism?has_content>${observation.cropOrganism.organismId?c}<#else>null</#if>; $(document).ready(function() {