diff --git a/pom.xml b/pom.xml index b942c53a5dd01548fee6ae7d81bd110e56e68088..f47d0f176c8d12fb4fcf1a816044c878b88a91da 100755 --- a/pom.xml +++ b/pom.xml @@ -266,6 +266,21 @@ <version>2.0.11</version> <scope>provided</scope> </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.13.0</version> + </dependency> </dependencies> <build> 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 daf359258690e26b9c4a7393a5b2ce1e945554c3..c23dd322845d1f9a574eed3192676edd64ea4a7d 100755 --- a/src/main/java/no/nibio/vips/logic/service/ObservationService.java +++ b/src/main/java/no/nibio/vips/logic/service/ObservationService.java @@ -32,6 +32,7 @@ import no.nibio.vips.logic.entity.rest.ObservationListItem; import no.nibio.vips.logic.entity.rest.PointMappingResponse; import no.nibio.vips.logic.entity.rest.ReferencedPoint; import no.nibio.vips.logic.messaging.MessagingBean; +import no.nibio.vips.logic.util.ExcelFileGenerator; import no.nibio.vips.logic.util.GISEntityUtil; import no.nibio.vips.logic.util.Globals; import org.jboss.resteasy.annotations.GZIP; @@ -59,6 +60,8 @@ import java.net.URI; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Instant; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; @@ -130,12 +133,13 @@ public class ObservationService { } /** - * @param organizationId Database ID of the organization - * @param pestId Database ID of the pest - * @param cropId Database ID of the crop - * @param cropCategoryId cropCategoryId Database IDs of the crop category/categories - * @param fromStr format "yyyy-MM-dd" - * @param toStr format "yyyy-MM-dd" + * @param organizationId Database ID of the organization + * @param observationTimeSeriesId Database ID of the observation time series + * @param pestId Database ID of the pest + * @param cropId Database ID of the crop + * @param cropCategoryId cropCategoryId Database IDs of the crop category/categories + * @param fromStr format "yyyy-MM-dd" + * @param toStr format "yyyy-MM-dd" * @return Observation objects for which the user is authorized to observe with properties relevant for lists */ @GET @@ -158,6 +162,55 @@ public class ObservationService { return Response.ok().entity(this.getFilteredObservationListItems(organizationId, observationTimeSeriesId, pestId, cropId, cropCategoryId, fromStr, toStr, userUUID, localeStr, isPositive)).build(); } + /** + * @param organizationId Database ID of the organization + * @param observationTimeSeriesId Database ID of the observation time series + * @param pestId Database ID of the pest + * @param cropId Database ID of the crop + * @param cropCategoryId cropCategoryId Database IDs of the crop category/categories + * @param fromStr format "yyyy-MM-dd" + * @param toStr format "yyyy-MM-dd" + * @return Observation objects for which the user is authorized to observe with properties relevant for lists + */ + @GET + @Path("list/filter/{organizationId}/xlsx") + @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 + ) { + LOGGER.info("Generate xlsx file for observations"); + VipsLogicUser user = getVipsLogicUser(userUUID); + ULocale locale = new ULocale(localeStr != null ? localeStr : + user != null ? user.getOrganizationId().getDefaultLocale() : + userBean.getOrganization(organizationId).getDefaultLocale()); + + + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + String filenameTimestamp = now.format(formatter); + + try { + byte[] excelFile = ExcelFileGenerator.generateExcel(getFilteredObservationListItems(organizationId, observationTimeSeriesId, pestId, cropId, cropCategoryId, fromStr, toStr, userUUID, localeStr, isPositive), locale); + + return Response + .ok(excelFile) + .header("Content-Disposition", "attachment; filename=\"" +filenameTimestamp+ "-observations.xlsx\"") + .build(); + } catch (IOException e) { + return Response.serverError().entity("Error generating Excel file: " + e.getMessage()).build(); + } + } + private List<ObservationListItem> getFilteredObservationListItems( Integer organizationId, Integer observationTimeSeriesId, @@ -169,11 +222,7 @@ public class ObservationService { String userUUID, String localeStr, Boolean isPositive) { - VipsLogicUser user = (VipsLogicUser) httpServletRequest.getSession().getAttribute("user"); - - if (user == null && userUUID != null) { - user = userBean.findVipsLogicUser(UUID.fromString(userUUID)); - } + VipsLogicUser user = getVipsLogicUser(userUUID); ULocale locale = new ULocale(localeStr != null ? localeStr : user != null ? user.getOrganizationId().getDefaultLocale() : userBean.getOrganization(organizationId).getDefaultLocale()); @@ -766,14 +815,14 @@ public class ObservationService { } /** - * @param organizationId Database id of the organization + * @param organizationId Database id of the organization * @param observationTimeSeriesId Database id of the observation time series - * @param pestId Database id of the pest - * @param cropId Database id of the crop - * @param cropCategoryId Database ids of the crop categories - * @param fromStr format "yyyy-MM-dd" - * @param toStr format "yyyy-MM-dd" - * @param user The user that requests this (used for authorization) + * @param pestId Database id of the pest + * @param cropId Database id of the crop + * @param cropCategoryId Database ids of the crop categories + * @param fromStr format "yyyy-MM-dd" + * @param toStr format "yyyy-MM-dd" + * @param user The user that requests this (used for authorization) * @return A list of observations that meets the filter criteria */ private List<Observation> getFilteredObservationsFromBackend( @@ -803,10 +852,10 @@ public class ObservationService { LOGGER.info("Return {} masked public observations for unregistered user", retVal.size()); return sortObservationsByDateAndId(retVal); } - // Else: This is a registered user without special privileges. Show public observations + user's own + // Else: This is a registered user without special privileges. Show public observations + user's own // Making sure we don't add duplicates - Set<Integer> obsIds = retVal.stream().map(o->o.getObservationId()).collect(Collectors.toSet()); - retVal.addAll(observationBean.getObservationsForUser(user).stream().filter(o->!obsIds.contains(o.getObservationId())).collect(Collectors.toList())); + Set<Integer> obsIds = retVal.stream().map(o -> o.getObservationId()).collect(Collectors.toSet()); + retVal.addAll(observationBean.getObservationsForUser(user).stream().filter(o -> !obsIds.contains(o.getObservationId())).collect(Collectors.toList())); LOGGER.info("Return {} masked public observations and user's own observations for registered user {}", retVal.size(), user.getUserId()); return sortObservationsByDateAndId(retVal); } @@ -1085,6 +1134,21 @@ public class ObservationService { .collect(Collectors.toList()); } + /** + * Find VipsLogic user from session or given userUUID + * + * @param userUUID the UUID of the user + * @return the corresponding VipsLogicUser + */ + private VipsLogicUser getVipsLogicUser(String userUUID) { + VipsLogicUser user = (VipsLogicUser) httpServletRequest.getSession().getAttribute("user"); + if (user == null && userUUID != null) { + user = userBean.findVipsLogicUser(UUID.fromString(userUUID)); + } + return user; + } + + /** * Utility method for getting the string value of a given property in a given map. * diff --git a/src/main/java/no/nibio/vips/logic/util/ExcelFileGenerator.java b/src/main/java/no/nibio/vips/logic/util/ExcelFileGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..4380680bd9a820e78107e340796ee2a4256e8206 --- /dev/null +++ b/src/main/java/no/nibio/vips/logic/util/ExcelFileGenerator.java @@ -0,0 +1,54 @@ +package no.nibio.vips.logic.util; + +import com.ibm.icu.util.ULocale; +import no.nibio.vips.logic.entity.rest.ObservationListItem; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.apache.poi.ss.usermodel.*; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.ResourceBundle; + +public class ExcelFileGenerator { + public static byte[] generateExcel(List<ObservationListItem> filteredObservationListItems, ULocale locale) throws IOException { + try (XSSFWorkbook workbook = new XSSFWorkbook(); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + + ResourceBundle rb = ResourceBundle.getBundle("no.nibio.vips.logic.i18n.vipslogictexts", locale.toLocale()); + // Prøvde å bruke i18n, fikk Invalid char (/) found at index (13) in sheet name 'Observasjoner/f?rstefunn' + Sheet sheet = workbook.createSheet("Observations"); + + Row headerRow = sheet.createRow(0); + headerRow.createCell(0).setCellValue(rb.getString("timeOfObservation")); + headerRow.createCell(1).setCellValue(rb.getString("organism")); + headerRow.createCell(2).setCellValue(rb.getString("cropOrganismId")); + headerRow.createCell(3).setCellValue(rb.getString("observationHeading")); + + int rowNum = 1; + for (ObservationListItem item : filteredObservationListItems) { + // TODO Better way of getting current timezone? + LocalDate localDateOfObservation = item.getTimeOfObservation().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + Row row = sheet.createRow(rowNum++); + row.createCell(0).setCellValue(localDateOfObservation.format(formatter)); + row.createCell(1).setCellValue(item.getOrganismName()); + row.createCell(2).setCellValue(item.getCropOrganismName()); + row.createCell(3).setCellValue(item.getObservationHeading()); + + } + + // Auto-size columns to fit content + for (int i = 0; i <= 3; i++) { + sheet.autoSizeColumn(i); + } + + workbook.write(out); + return out.toByteArray(); + } + } +} 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 1e03844cc22e2c12fe721b2eb34d16f56e097f44..db00fc266365213c9c50b7885c415baa578a339a 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 @@ -354,7 +354,7 @@ greeting = Velkommen til groupMembers = Gruppemedlemmer -heading = Overskrift +heading = Tittel help = Hjelp @@ -558,7 +558,7 @@ observationDataField_unit = M\u00e5leenhet observationDeleted = Observasjonen ble slettet -observationHeading = Observasjons-overskrift +observationHeading = Observasjonstittel observationMap = Observasjonskart @@ -894,7 +894,7 @@ thresholdRelativeHumidity = Terskelverdi relativ luftfuktighet (%) tillageMethod = Jordarbeiding -timeOfObservation = Observasjonstidspunkt +timeOfObservation = Observasjonsdato timeZone = Tidssone