Skip to content
Snippets Groups Projects
Commit 5ad3b8fa authored by Lene Wasskog's avatar Lene Wasskog
Browse files

feat: Endpoint for generating excel file with observation data

parent a96e06cd
No related branches found
No related tags found
No related merge requests found
...@@ -266,6 +266,21 @@ ...@@ -266,6 +266,21 @@
<version>2.0.11</version> <version>2.0.11</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.17.0</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
......
/* /*
* 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 * 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 * 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 ...@@ -95,15 +95,15 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
private Boolean broadcastMessage; private Boolean broadcastMessage;
private Boolean locationIsPrivate; private Boolean locationIsPrivate;
private PolygonService polygonService; private PolygonService polygonService;
private ObservationDataSchema observationDataSchema; private ObservationDataSchema observationDataSchema;
private VipsLogicUser user; // Transient private VipsLogicUser user; // Transient
private VipsLogicUser lastEditedByUser; // private VipsLogicUser lastEditedByUser; //
private PointOfInterest location; // Transient private PointOfInterest location; // Transient
private Set<ObservationIllustration> observationIllustrationSet; private Set<ObservationIllustration> observationIllustrationSet;
private GISEntityUtil GISEntityUtil; private GISEntityUtil GISEntityUtil;
private GISUtil GISUtil; private GISUtil GISUtil;
...@@ -156,7 +156,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse ...@@ -156,7 +156,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
this.timeOfObservation = timeOfObservation; this.timeOfObservation = timeOfObservation;
} }
// Using PostGIS + Hibernate-spatial + Java Topology Suite to make this work // Using PostGIS + Hibernate-spatial + Java Topology Suite to make this work
/*@JsonIgnore /*@JsonIgnore
@Type(type = "org.hibernate.spatial.GeometryType") @Type(type = "org.hibernate.spatial.GeometryType")
...@@ -168,12 +168,12 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse ...@@ -168,12 +168,12 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
public void setLocation(Point location) { public void setLocation(Point location) {
this.location = location; this.location = location;
}*/ }*/
public void setGeoinfos(List<Gis> geoinfo) public void setGeoinfos(List<Gis> geoinfo)
{ {
this.geoinfo = geoinfo; this.geoinfo = geoinfo;
} }
public void addGeoInfo(Gis geoinfo) public void addGeoInfo(Gis geoinfo)
{ {
if(this.geoinfo == null) if(this.geoinfo == null)
...@@ -182,20 +182,20 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse ...@@ -182,20 +182,20 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
} }
this.geoinfo.add(geoinfo); this.geoinfo.add(geoinfo);
} }
@JsonIgnore @JsonIgnore
@Transient @Transient
public List<Gis> getGeoinfos() public List<Gis> getGeoinfos()
{ {
return this.geoinfo; return this.geoinfo;
} }
public void setGeoinfo(String json) public void setGeoinfo(String json)
{ {
this.setGeoinfos(this.GISEntityUtil.getGisFromGeoJSON(json)); this.setGeoinfos(this.GISEntityUtil.getGisFromGeoJSON(json));
} }
@Transient @Transient
@Override @Override
public String getGeoinfo() public String getGeoinfo()
...@@ -269,37 +269,37 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse ...@@ -269,37 +269,37 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
@Override @Override
public String toString() { public String toString() {
return "Observation{" + return "Observation{" +
"observationId=" + observationId + "observationId=" + observationId +
", timeOfObservation=" + timeOfObservation + ", timeOfObservation=" + timeOfObservation +
", organism=" + organism + ", organism=" + organism +
", cropOrganism=" + cropOrganism + ", cropOrganism=" + cropOrganism +
", userId=" + userId + ", userId=" + userId +
", lastEditedBy=" + lastEditedBy + ", lastEditedBy=" + lastEditedBy +
", geoinfo=" + geoinfo + ", geoinfo=" + geoinfo +
", observationTimeSeries=" + observationTimeSeries + ", observationTimeSeries=" + observationTimeSeries +
", locationPointOfInterestId=" + locationPointOfInterestId + ", locationPointOfInterestId=" + locationPointOfInterestId +
", observationHeading='" + observationHeading + '\'' + ", observationHeading='" + observationHeading + '\'' +
", observationText='" + observationText + '\'' + ", observationText='" + observationText + '\'' +
", statusTypeId=" + statusTypeId + ", statusTypeId=" + statusTypeId +
", statusChangedByUserId=" + statusChangedByUserId + ", statusChangedByUserId=" + statusChangedByUserId +
", statusChangedTime=" + statusChangedTime + ", statusChangedTime=" + statusChangedTime +
", lastEditedTime=" + lastEditedTime + ", lastEditedTime=" + lastEditedTime +
", statusRemarks='" + statusRemarks + '\'' + ", statusRemarks='" + statusRemarks + '\'' +
", observationData='" + observationData + '\'' + ", observationData='" + observationData + '\'' +
", isQuantified=" + isQuantified + ", isQuantified=" + isQuantified +
", isPositive=" + isPositive + ", isPositive=" + isPositive +
", broadcastMessage=" + broadcastMessage + ", broadcastMessage=" + broadcastMessage +
", locationIsPrivate=" + locationIsPrivate + ", locationIsPrivate=" + locationIsPrivate +
", polygonService=" + polygonService + ", polygonService=" + polygonService +
", observationDataSchema=" + observationDataSchema + ", observationDataSchema=" + observationDataSchema +
", user=" + user + ", user=" + user +
", lastEditedByUser=" + lastEditedByUser + ", lastEditedByUser=" + lastEditedByUser +
", location=" + location + ", location=" + location +
", observationIllustrationSet=" + observationIllustrationSet + ", observationIllustrationSet=" + observationIllustrationSet +
", GISEntityUtil=" + GISEntityUtil + ", GISEntityUtil=" + GISEntityUtil +
", GISUtil=" + GISUtil + ", GISUtil=" + GISUtil +
", source=" + source + ", source=" + source +
'}'; '}';
} }
/** /**
...@@ -338,13 +338,13 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse ...@@ -338,13 +338,13 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
public void setOrganism(Organism organism) { public void setOrganism(Organism organism) {
this.organism = organism; this.organism = organism;
} }
@JoinColumn(name = "polygon_service_id", referencedColumnName = "polygon_service_id") @JoinColumn(name = "polygon_service_id", referencedColumnName = "polygon_service_id")
@ManyToOne @ManyToOne
public PolygonService getPolygonService(){ public PolygonService getPolygonService(){
return this.polygonService; return this.polygonService;
} }
public void setPolygonService(PolygonService polygonService) public void setPolygonService(PolygonService polygonService)
{ {
this.polygonService = polygonService; this.polygonService = polygonService;
...@@ -363,7 +363,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse ...@@ -363,7 +363,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
return this.getLocationCoordinate().x; return this.getLocationCoordinate().x;
} }
*/ */
@Override @Override
@Transient @Transient
public String getName() { public String getName() {
...@@ -480,13 +480,13 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse ...@@ -480,13 +480,13 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
public void setObservationData(String observationData) { public void setObservationData(String observationData) {
this.observationData = observationData; this.observationData = observationData;
} }
@Transient @Transient
public ObservationDataSchema getObservationDataSchema() public ObservationDataSchema getObservationDataSchema()
{ {
return this.observationDataSchema != null ? this.observationDataSchema : null; return this.observationDataSchema != null ? this.observationDataSchema : null;
} }
public void setObservationDataSchema(ObservationDataSchema observationDataSchema) public void setObservationDataSchema(ObservationDataSchema observationDataSchema)
{ {
this.observationDataSchema = observationDataSchema; this.observationDataSchema = observationDataSchema;
...@@ -522,7 +522,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse ...@@ -522,7 +522,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
public void setCropOrganism(Organism cropOrganism) { public void setCropOrganism(Organism cropOrganism) {
this.cropOrganism = cropOrganism; this.cropOrganism = cropOrganism;
} }
@Transient @Transient
public Integer getCropOrganismId() { public Integer getCropOrganismId() {
return this.getCropOrganism() != null ? this.getCropOrganism().getOrganismId() : null; return this.getCropOrganism() != null ? this.getCropOrganism().getOrganismId() : null;
...@@ -564,7 +564,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse ...@@ -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") @JoinColumn(name = "observation_time_series_id", referencedColumnName = "observation_time_series_id")
@ManyToOne @ManyToOne
...@@ -678,7 +678,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse ...@@ -678,7 +678,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
/** /**
* Simplifies the public JSON object * Simplifies the public JSON object
* @param locale * @param locale
* @return * @return
*/ */
public ObservationListItem getListItem(String locale, ObservationDataSchema observationDataSchema) public ObservationListItem getListItem(String locale, ObservationDataSchema observationDataSchema)
{ {
...@@ -690,36 +690,41 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse ...@@ -690,36 +690,41 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
this.location.addProperty("timestamp", this.getTimeOfObservation().getTime()); this.location.addProperty("timestamp", this.getTimeOfObservation().getTime());
} }
return new ObservationListItem( return new ObservationListItem(
this.getObservationId(), this.getObservationId(),
this.getObservationTimeSeriesId(), this.userId,
this.getTimeOfObservation(), this.user != null ? this.user.getFullName() : null,
this.getOrganismId(), this.getObservationTimeSeriesId(),
! this.getOrganism().getLocalName(locale).trim().isBlank() ? this.getOrganism().getLocalName(locale) : this.getOrganism().getLatinName(), this.getTimeOfObservation(),
this.getCropOrganismId(), this.getOrganismId(),
! this.getCropOrganism().getLocalName(locale).trim().isBlank() ? this.getCropOrganism().getLocalName(locale) : this.getCropOrganism().getLatinName(), ! this.getOrganism().getLocalName(locale).trim().isBlank() ? this.getOrganism().getLocalName(locale) : this.getOrganism().getLatinName(),
this.observationTimeSeries != null ? this.observationTimeSeries.getLabel() : null, this.getCropOrganismId(),
// Specific geoInfo trumps location. This is to be interpreted ! this.getCropOrganism().getLocalName(locale).trim().isBlank() ? this.getCropOrganism().getLocalName(locale) : this.getCropOrganism().getLatinName(),
// as that the observation has been geographically masked by this.observationTimeSeries != null ? this.observationTimeSeries.getLabel() : null,
// choice of the observer // Specific geoInfo trumps location. This is to be interpreted
this.location != null && this.geoinfo == null ? this.location.getGeoJSON() : this.getGeoinfo(), // as that the observation has been geographically masked by
this.getObservationHeading(), // choice of the observer
this.getBroadcastMessage(), this.location != null ? this.location.getPointOfInterestId() : null,
this.getLocationIsPrivate(), this.location != null ? this.location.getName() : null,
this.getIsPositive(), this.location != null && this.geoinfo == null ? this.location.getGeoJSON() : this.getGeoinfo(),
this.getObservationData(), this.getObservationHeading(),
observationDataSchema this.getObservationText(),
this.getBroadcastMessage(),
this.getLocationIsPrivate(),
this.getIsPositive(),
this.getObservationData(),
observationDataSchema
); );
} }
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
@Column(name = "last_edited_time") @Column(name = "last_edited_time")
public Date getLastEditedTime() { public Date getLastEditedTime() {
return lastEditedTime; return lastEditedTime;
} }
public void setLastEditedTime(Date lastEditedTime) { public void setLastEditedTime(Date lastEditedTime) {
this.lastEditedTime = lastEditedTime; this.lastEditedTime = lastEditedTime;
} }
@Column(name = "is_positive") @Column(name = "is_positive")
public Boolean getIsPositive() { public Boolean getIsPositive() {
......
/* /*
* 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 * 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 * it under the terms of the GNU Affero General Public License as published by
...@@ -63,7 +63,7 @@ import java.util.UUID; ...@@ -63,7 +63,7 @@ import java.util.UUID;
@NamedQuery(name = "VipsLogicUser.findByCompletePhoneNumber", query = "SELECT v FROM VipsLogicUser v WHERE v.phoneCountryCode || v.phone = :completePhoneNumber") @NamedQuery(name = "VipsLogicUser.findByCompletePhoneNumber", query = "SELECT v FROM VipsLogicUser v WHERE v.phoneCountryCode || v.phone = :completePhoneNumber")
}) })
public class VipsLogicUser implements Serializable, Comparable{ public class VipsLogicUser implements Serializable, Comparable{
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private Integer userId; private Integer userId;
//if the field contains email address consider using this annotation to enforce field validation //if the field contains email address consider using this annotation to enforce field validation
...@@ -93,7 +93,7 @@ public class VipsLogicUser implements Serializable, Comparable{ ...@@ -93,7 +93,7 @@ public class VipsLogicUser implements Serializable, Comparable{
private Integer vipsCoreUserId; private Integer vipsCoreUserId;
private boolean approvesSmsBilling; private boolean approvesSmsBilling;
private boolean freeSms; private boolean freeSms;
private UUID userUuid; private UUID userUuid;
public VipsLogicUser() { public VipsLogicUser() {
...@@ -143,6 +143,12 @@ public class VipsLogicUser implements Serializable, Comparable{ ...@@ -143,6 +143,12 @@ public class VipsLogicUser implements Serializable, Comparable{
this.lastName = lastName; this.lastName = lastName;
} }
@JsonIgnore
@Transient
public String getFullName() {
return firstName + " " + lastName;
}
@OneToMany(cascade = CascadeType.ALL, mappedBy = "vipsLogicUser", fetch=FetchType.EAGER) @OneToMany(cascade = CascadeType.ALL, mappedBy = "vipsLogicUser", fetch=FetchType.EAGER)
@XmlTransient @XmlTransient
@JsonIgnore @JsonIgnore
...@@ -160,7 +166,7 @@ public class VipsLogicUser implements Serializable, Comparable{ ...@@ -160,7 +166,7 @@ public class VipsLogicUser implements Serializable, Comparable{
public Organization getOrganizationId() { public Organization getOrganizationId() {
return organizationId; return organizationId;
} }
@Transient @Transient
public Integer getOrganization_id(){ public Integer getOrganization_id(){
return organizationId.getOrganizationId(); return organizationId.getOrganizationId();
...@@ -249,11 +255,11 @@ public class VipsLogicUser implements Serializable, Comparable{ ...@@ -249,11 +255,11 @@ public class VipsLogicUser implements Serializable, Comparable{
@ManyToMany(fetch = FetchType.EAGER) @ManyToMany(fetch = FetchType.EAGER)
@JsonIgnore @JsonIgnore
@JoinTable( @JoinTable(
name = "user_vips_logic_role", name = "user_vips_logic_role",
joinColumns = { joinColumns = {
@JoinColumn(name = "user_id")}, @JoinColumn(name = "user_id")},
inverseJoinColumns = { inverseJoinColumns = {
@JoinColumn(name = "vips_logic_role_id")} @JoinColumn(name = "vips_logic_role_id")}
) )
public Set<VipsLogicRole> getVipsLogicRoles() { public Set<VipsLogicRole> getVipsLogicRoles() {
return vipsLogicRoles; return vipsLogicRoles;
...@@ -287,7 +293,7 @@ public class VipsLogicUser implements Serializable, Comparable{ ...@@ -287,7 +293,7 @@ public class VipsLogicUser implements Serializable, Comparable{
} }
return false; return false;
} }
@JsonIgnore @JsonIgnore
@Transient @Transient
public boolean isObservationAuthority(){ public boolean isObservationAuthority(){
...@@ -298,7 +304,7 @@ public class VipsLogicUser implements Serializable, Comparable{ ...@@ -298,7 +304,7 @@ public class VipsLogicUser implements Serializable, Comparable{
} }
return false; return false;
} }
@JsonIgnore @JsonIgnore
@Transient @Transient
public boolean isOrganismEditor() { public boolean isOrganismEditor() {
...@@ -309,7 +315,7 @@ public class VipsLogicUser implements Serializable, Comparable{ ...@@ -309,7 +315,7 @@ public class VipsLogicUser implements Serializable, Comparable{
} }
return false; return false;
} }
@JsonIgnore @JsonIgnore
@Transient @Transient
public boolean isAppleFruitMothAdministrator(){ public boolean isAppleFruitMothAdministrator(){
...@@ -320,7 +326,7 @@ public class VipsLogicUser implements Serializable, Comparable{ ...@@ -320,7 +326,7 @@ public class VipsLogicUser implements Serializable, Comparable{
} }
return false; return false;
} }
@JsonIgnore @JsonIgnore
@Transient @Transient
public boolean isMessageAuthor(){ public boolean isMessageAuthor(){
...@@ -499,7 +505,7 @@ public class VipsLogicUser implements Serializable, Comparable{ ...@@ -499,7 +505,7 @@ public class VipsLogicUser implements Serializable, Comparable{
public boolean isFreeSms() { public boolean isFreeSms() {
return freeSms; return freeSms;
} }
/** /**
* @param freeSms the freeSms to set * @param freeSms the freeSms to set
...@@ -513,6 +519,6 @@ public class VipsLogicUser implements Serializable, Comparable{ ...@@ -513,6 +519,6 @@ public class VipsLogicUser implements Serializable, Comparable{
VipsLogicUser other = (VipsLogicUser)o; VipsLogicUser other = (VipsLogicUser)o;
return (this.getLastName() + ", " + this.getFirstName()).compareTo(other.getLastName() + ", " + other.getFirstName()); return (this.getLastName() + ", " + this.getFirstName()).compareTo(other.getLastName() + ", " + other.getFirstName());
} }
} }
/* /*
* 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 * 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 * it under the terms of the GNU Affero General Public License as published by
...@@ -28,11 +28,16 @@ import no.nibio.vips.observationdata.ObservationDataSchema; ...@@ -28,11 +28,16 @@ import no.nibio.vips.observationdata.ObservationDataSchema;
*/ */
public class ObservationListItem implements Comparable{ public class ObservationListItem implements Comparable{
private Integer observationId, observationTimeSeriesId, organismId, cropOrganismId; private Integer observationId, observationTimeSeriesId, organismId, cropOrganismId;
private Integer observerId;
private String observerName;
private Date timeOfObservation; private Date timeOfObservation;
private String organismName, cropOrganismName; private String organismName, cropOrganismName;
private String observationTimeSeriesLabel; private String observationTimeSeriesLabel;
private Integer locationPointOfInterestId;
private String locationPointOfInterestName;
private String geoInfo; private String geoInfo;
private String observationHeading; private String observationHeading;
private String observationText;
private String observationData; private String observationData;
private ObservationDataSchema observationDataSchema; private ObservationDataSchema observationDataSchema;
private Boolean broadcastMessage; private Boolean broadcastMessage;
...@@ -40,23 +45,29 @@ public class ObservationListItem implements Comparable{ ...@@ -40,23 +45,29 @@ public class ObservationListItem implements Comparable{
private Boolean isPositive; private Boolean isPositive;
public ObservationListItem( public ObservationListItem(
Integer observationId, Integer observationId,
Integer observationTimeSeriesId, Integer observerId,
Date timeOfObservation, String observerName,
Integer organismId, Integer observationTimeSeriesId,
String organismName, Date timeOfObservation,
Integer cropOrganismId, Integer organismId,
String cropOrganismName, String organismName,
String observationTimeSeriesLabel, Integer cropOrganismId,
String geoinfo, String cropOrganismName,
String observationHeading, String observationTimeSeriesLabel,
Boolean broadcastMessage, Integer poiId,
Boolean locationIsPrivate, String poiName,
Boolean isPositive, String geoinfo,
String observationData, String observationHeading,
ObservationDataSchema observationDataSchema String observationText,
){ Boolean broadcastMessage,
Boolean locationIsPrivate,
Boolean isPositive,
String observationData,
ObservationDataSchema observationDataSchema){
this.observationId = observationId; this.observationId = observationId;
this.observerId = observerId;
this.observerName = observerName;
this.observationTimeSeriesId = observationTimeSeriesId; this.observationTimeSeriesId = observationTimeSeriesId;
this.timeOfObservation = timeOfObservation; this.timeOfObservation = timeOfObservation;
this.organismId = organismId; this.organismId = organismId;
...@@ -64,15 +75,18 @@ public class ObservationListItem implements Comparable{ ...@@ -64,15 +75,18 @@ public class ObservationListItem implements Comparable{
this.cropOrganismId = cropOrganismId; this.cropOrganismId = cropOrganismId;
this.cropOrganismName = cropOrganismName; this.cropOrganismName = cropOrganismName;
this.observationTimeSeriesLabel = observationTimeSeriesLabel; this.observationTimeSeriesLabel = observationTimeSeriesLabel;
this.locationPointOfInterestId = poiId;
this.locationPointOfInterestName = poiName;
this.geoInfo = geoinfo; this.geoInfo = geoinfo;
this.observationHeading = observationHeading; this.observationHeading = observationHeading;
this.observationText = observationText;
this.broadcastMessage = broadcastMessage; this.broadcastMessage = broadcastMessage;
this.locationIsPrivate = locationIsPrivate; this.locationIsPrivate = locationIsPrivate;
this.isPositive = isPositive; this.isPositive = isPositive;
this.observationData = observationData; this.observationData = observationData;
this.observationDataSchema = observationDataSchema; this.observationDataSchema = observationDataSchema;
} }
@Override @Override
public int compareTo(Object otherOne) public int compareTo(Object otherOne)
...@@ -83,7 +97,7 @@ public class ObservationListItem implements Comparable{ ...@@ -83,7 +97,7 @@ public class ObservationListItem implements Comparable{
} }
return this.getTimeOfObservation().compareTo(((ObservationListItem) otherOne).getTimeOfObservation()); return this.getTimeOfObservation().compareTo(((ObservationListItem) otherOne).getTimeOfObservation());
} }
/** /**
* @return the observationId * @return the observationId
*/ */
...@@ -98,6 +112,34 @@ public class ObservationListItem implements Comparable{ ...@@ -98,6 +112,34 @@ public class ObservationListItem implements Comparable{
this.observationId = observationId; this.observationId = observationId;
} }
/**
* @return The ID of the observer
*/
public Integer getObserverId() {
return observerId;
}
/**
* @param observerId The ID to set
*/
public void setObserverId(Integer observerId) {
this.observerId = observerId;
}
/**
* @return The full name of the observer
*/
public String getObserverName() {
return observerName;
}
/**
* @param observerName The observer name to set
*/
public void setObserverName(String observerName) {
this.observerName = observerName;
}
/** /**
* @return the observationTimeSeriesId * @return the observationTimeSeriesId
*/ */
...@@ -162,6 +204,22 @@ public class ObservationListItem implements Comparable{ ...@@ -162,6 +204,22 @@ public class ObservationListItem implements Comparable{
this.observationTimeSeriesLabel = observationTimeSeriesLabel; this.observationTimeSeriesLabel = observationTimeSeriesLabel;
} }
public Integer getLocationPointOfInterestId() {
return locationPointOfInterestId;
}
public void setLocationPointOfInterestId(Integer locationPointOfInterestId) {
this.locationPointOfInterestId = locationPointOfInterestId;
}
public String getLocationPointOfInterestName() {
return locationPointOfInterestName;
}
public void setLocationPointOfInterestName(String locationPointOfInterestName) {
this.locationPointOfInterestName = locationPointOfInterestName;
}
/** /**
* @return the geoInfo * @return the geoInfo
*/ */
...@@ -190,6 +248,20 @@ public class ObservationListItem implements Comparable{ ...@@ -190,6 +248,20 @@ public class ObservationListItem implements Comparable{
this.observationHeading = observationHeading; this.observationHeading = observationHeading;
} }
/**
* @return the observation text
*/
public String getObservationText() {
return observationText;
}
/**
* @param observationText The observation text to set
*/
public void setObservationText(String observationText) {
this.observationText = observationText;
}
/** /**
* @return the organismId * @return the organismId
*/ */
......
package no.nibio.vips.logic.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ibm.icu.util.ULocale;
import no.nibio.vips.logic.entity.VipsLogicUser;
import no.nibio.vips.logic.entity.rest.ObservationListItem;
import no.nibio.vips.observationdata.ObservationDataSchema;
import org.apache.poi.common.usermodel.HyperlinkType;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
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();
}
}
/**
* 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;
}
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);
}
}
/**
* 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);
}
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);
}
}
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));
}
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());
}
}
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 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;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment