diff --git a/pom.xml b/pom.xml index b8ceac8b56308f61e77674966d403e3ff546f112..2c64c2310d53ccf0989d634cf62b8afcd3eeab37 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,11 @@ <artifactId>findbugs-annotations</artifactId> <version>3.0.1</version> </dependency> + <dependency> + <groupId>org.apache.poi</groupId> + <artifactId>poi-ooxml</artifactId> + <version>5.2.2</version> + </dependency> <dependency> <groupId>org.springdoc</groupId> diff --git a/src/main/java/org/openepics/names/rest/api/v1/INames.java b/src/main/java/org/openepics/names/rest/api/v1/INames.java index 45f9112c639c804750ea0922d9024969ef1ee98d..70f80fc723276e75e60ee360f979c0b9df5e785f 100644 --- a/src/main/java/org/openepics/names/rest/api/v1/INames.java +++ b/src/main/java/org/openepics/names/rest/api/v1/INames.java @@ -27,6 +27,8 @@ import org.openepics.names.rest.beans.response.Response; import org.openepics.names.rest.beans.response.ResponseBoolean; import org.openepics.names.rest.beans.response.ResponseBooleanList; import org.openepics.names.rest.beans.response.ResponsePageNameElements; +import org.openepics.names.util.ExcelUtil; +import org.springframework.core.io.Resource; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -36,6 +38,7 @@ import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; @@ -61,22 +64,23 @@ public interface INames { /* FieldName - UUID, NAME, NAMEEQUIVALENCE, SYSTEMSTRUCTURE, DEVICESTRUCTURE, DESCRIPTION - NameElement - uuid (UUID) - systemgroup (UUID) - system (UUID) - subsystem (UUID) - devicetype (UUID) - systemstructure (String)(mnemonic path) - devicestructure (String)(mnemonic path) - index (String) - name (String) - description (String) - status (Status) - latest (Boolean) - deleted (Boolean) - when (Date) - who (String) - comment (String) + NameElementCommand - subset of NameElement attributes + NameElement + uuid (UUID) + parentsystemstructure (UUID) + parentdevicestructure (UUID) + index (String) + description (String) + comment (String) + ---------------------------------- + systemstructure (String) + devicestructure (String) + name (String) + status (Status) + latest (Boolean) + deleted (Boolean) + when (Date) + who (String) authentication/authorization 3 levels - no, user, administrator @@ -85,29 +89,34 @@ public interface INames { ( administrator ) Methods - create POST /names - createNames(List<NameElementCommand>) + create POST /names - createNames (List<NameElementCommand>) + create POST /names/upload - createNames (MultipartFile) ---------------------------------------------------------------------------------------------------- - read GET /names - readNames(Boolean, FieldName[], String[], FieldName, Boolean, Integer, Integer) - read GET /names/{name} - readNames(String, FieldName, Boolean, Integer, Integer) - read GET /names/systemstructure/{mnemonicpath} - readNamesSystemStructure(String, FieldName, Boolean, Integer, Integer) - read GET /names/devicestructure/{mnemonicpath} - readNamesDeviceStructure(String, FieldName, Boolean, Integer, Integer) - read GET /names/history/{uuid} - readNamesHistory(String, FieldName, Boolean, Integer, Integer) + read GET /names - readNames (Boolean, FieldName[], String[], FieldName, Boolean, Integer, Integer) + read GET /names/download - readNamesDownload (Boolean, FieldName[], String[], FieldName, Boolean, Integer, Integer) + read GET /names/{name} - readNames (String, FieldName, Boolean, Integer, Integer) + read GET /names/systemstructure/{mnemonicpath} - readNamesSystemStructure (String, FieldName, Boolean, Integer, Integer) + read GET /names/devicestructure/{mnemonicpath} - readNamesDeviceStructure (String, FieldName, Boolean, Integer, Integer) + read GET /names/history/{uuid} - readNamesHistory (String, FieldName, Boolean, Integer, Integer) ---------------------------------------------------------------------------------------------------- - read GET /names/equivalence/{name} - equivalenceName(String) - read GET /names/exists/{name} - existsName(String) - read GET /names/islegacy/{name} - isLegacyName(String) - read GET /names/isvalidtocreate/{name} - isValidToCreateName(String) + read GET /names/equivalence/{name} - equivalenceName (String) + read GET /names/exists/{name} - existsName (String) + read GET /names/islegacy/{name} - isLegacyName (String) + read GET /names/isvalidtocreate/{name} - isValidToCreateName (String) ---------------------------------------------------------------------------------------------------- - read GET /names/validatecreate - validateNamesCreate(List<NameElementCommand>) - read GET /names/validateupdate - validateNamesUpdate(List<NameElementCommand>) - read GET /names/validatedelete - validateNamesDelete(List<NameElementCommand>) + read GET /names/validatecreate - validateNamesCreate (List<NameElementCommand>) + read GET /names/validateupdate - validateNamesUpdate (List<NameElementCommand>) + read GET /names/validatedelete - validateNamesDelete (List<NameElementCommand>) ---------------------------------------------------------------------------------------------------- - update PUT /names - updateNames(List<NameElementCommand>) + update PUT /names - updateNames (List<NameElementCommand>) + update PUT /names/upload - updateNames (MultipartFile) ---------------------------------------------------------------------------------------------------- - delete DELETE /names - deleteNames(List<NameElementCommand>) + delete DELETE /names - deleteNames (List<NameElementCommand>) + delete DELETE /names/upload - deleteNames (MultipartFile) Note read GET /names/{name} - both name and uuid (name - exact and search, uuid exact) + /upload and /download endpoints handle Excel files */ public static final String DEFAULT_PAGE = "0"; @@ -115,7 +124,7 @@ public interface INames { /** * Create names by list of name elements. - * Return list of created name elements. + * Return list of name elements for created names. * * @param nameElements list of name elements * @return list of created name elements @@ -124,20 +133,24 @@ public interface INames { summary = "Create names by array of name elements", description = """ Create names by array of name elements. - Return array of created name elements. + Return array of name elements for created names. Required attributes: - - parentsystemstructure + - parentsystemstructure (uuid) - description - comment + Optional attributes: + - parentdevicestructure (uuid) + - index + Uuid is created by Naming. """ ) @ApiResponses(value = { @ApiResponse( responseCode = "201", - description = "OK. Return array of created name elements.", + description = "OK. Return array of name elements for created names.", content = @Content( mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = NameElement.class)))), @@ -176,6 +189,69 @@ public interface INames { requiredProperties = {"parentsystemstructure","parentdevicestructure","index","description","comment"})))) @RequestBody List<NameElementCommand> nameElements); + /** + * Create names by upload Excel file. + * Return Excel file with name elements for created names. + * + * @param file file + * @return Excel file + */ + @Operation( + summary = "Create names by upload Excel file", + description = """ + Create names by upload Excel file. + Return Excel file with name elements for created names. + + Expected columns as in Excel template for names. + + Required attributes: + - parentsystemstructure (uuid) + - description + - comment + + Optional attributes: + - parentdevicestructure (uuid) + - index + + Uuid is created by Naming. + """ + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "201", + description = "OK. Return Excel file with name elements for created names.", + content = @Content(mediaType = ExcelUtil.MIME_TYPE_EXCEL)), + @ApiResponse( + responseCode = "400", + description = "Bad request. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "422", + description = "Unprocessable entity. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))) + }) + @PostMapping( + value = "/upload", + produces = {"application/vnd.ms-excel"}, + consumes = {"multipart/form-data"}) + public ResponseEntity<Resource> createNames( + @Parameter( + in = ParameterIn.DEFAULT, + description = "Excel file with name elements", + required = true, + content = @Content(mediaType = ExcelUtil.MIME_TYPE_OPENXML_SPREADSHEET)) + @RequestParam("file") MultipartFile file); + // ---------------------------------------------------------------------------------------------------- /** @@ -236,6 +312,63 @@ public interface INames { @Parameter(in = ParameterIn.QUERY, description = "page starting from 0, offset") @RequestParam(required = false, defaultValue = DEFAULT_PAGE) Integer page, @Parameter(in = ParameterIn.QUERY, description = "page size, limit") @RequestParam(required = false, defaultValue = DEFAULT_PAGE_SIZE) Integer pageSize); + /** + * Find valid names (search). + * Return Excel file with paged list of name elements. + * + * @param deleted if deleted-only names are to be included + * (false for non-deleted-only names, true for deleted-only names, not used for both cases) + * @param queryFields search fields + * @param queryValues search values corresponding to search fields + * @param orderBy order by field + * @param isAsc sort order, ascending or descending + * @param page page starting from 0, offset + * @param pageSize page size, limit + * @return Excel file + */ + @Operation( + summary = "Find valid names (search) and download Excel file", + description = """ + Find valid names (search). + Return Excel file with paged list of name elements. + """ + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "OK. Return Excel file with paged list of name elements.", + content = @Content(mediaType = ExcelUtil.MIME_TYPE_EXCEL)), + @ApiResponse( + responseCode = "400", + description = "Bad request. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "422", + description = "Unprocessable entity. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))) + }) + @GetMapping( + value = "/download", + produces = {"application/vnd.ms-excel"}) + public ResponseEntity<Resource> readNamesDownload( + @Parameter(in = ParameterIn.QUERY, description = "if deleted names are to be included or not, omitted for both deleted and not deleted names") @RequestParam(required = false) Boolean deleted, + @Parameter(in = ParameterIn.QUERY, description = "search fields") @RequestParam(required = false) FieldName[] queryFields, + @Parameter(in = ParameterIn.QUERY, description = "search values corresponding to search fields") @RequestParam(required = false) String[] queryValues, + @Parameter(in = ParameterIn.QUERY, description = "order by field") @RequestParam(required = false) FieldName orderBy, + @Parameter(in = ParameterIn.QUERY, description = "sort order, ascending or descending") @RequestParam(required = false) Boolean isAsc, + @Parameter(in = ParameterIn.QUERY, description = "page starting from 0, offset") @RequestParam(required = false, defaultValue = DEFAULT_PAGE) Integer page, + @Parameter(in = ParameterIn.QUERY, description = "page size, limit") @RequestParam(required = false, defaultValue = DEFAULT_PAGE_SIZE) Integer pageSize); + /** * Find valid names by name or uuid (search). * Return paged list of name elements. @@ -663,7 +796,7 @@ public interface INames { // ---------------------------------------------------------------------------------------------------- /** - * Return if name elements are valid to create. + * Return if names are valid to create by list of name elements. * If names are valid to create, successful create of names can be expected. * * <p> @@ -672,25 +805,31 @@ public interface INames { * </p> * * @param nameElements list of name elements - * @return if list of name elements is valid to create + * @return if list of names is valid to create */ @Hidden @Operation( - summary = "Return if name elements are valid to create", + summary = "Return if names are valid to create by list of name elements", description = """ - Return if name elements are valid to create. + Return if names are valid to create by list of name elements. If names are valid to create, successful create of names can be expected. Required attributes: - - parentsystemstructure + - parentsystemstructure (uuid) - description - comment + + Optional attributes: + - parentdevicestructure (uuid) + - index + + Uuid is created by Naming. """ ) @ApiResponses(value = { @ApiResponse( responseCode = "200", - description = "OK. Response is true if all name elements validated ok, false otherwise. " + description = "OK. Response is true if all name elements validated ok, false otherwise." + "Responses contain array with result for each name element. Message and details are available if no response is available.", content = @Content( mediaType = "application/json", @@ -732,7 +871,7 @@ public interface INames { @RequestBody List<NameElementCommand> nameElements); /** - * Return if name elements are valid to update. + * Return if names are valid to update by list of name elements. * If names are valid to update, successful update of names can be expected. * * <p> @@ -745,22 +884,26 @@ public interface INames { */ @Hidden @Operation( - summary = "Return if name elements are valid to update", + summary = "Return if names are valid to update by list of name elements", description = """ - Return if name elements are valid to update. + Return if names are valid to update by list of name elements. If names are valid to update, successful update of names can be expected. Required attributes: - uuid - - parentsystemstructure + - parentsystemstructure (uuid) - description - comment + + Optional attributes: + - parentdevicestructure (uuid) + - index """ ) @ApiResponses(value = { @ApiResponse( responseCode = "200", - description = "OK. Response is true if all name elements validated ok, false otherwise. " + description = "OK. Response is true if all name elements validated ok, false otherwise." + "Responses contain array with result for each name element. Message and details are available if no response is available.", content = @Content( mediaType = "application/json", @@ -802,8 +945,8 @@ public interface INames { @RequestBody List<NameElementCommand> nameElements); /** - * Return if name elements are valid to delete. - * If names are valid to update, successful delete of names can be expected. + * Return if names are valid to delete by list of name elements. + * If names are valid to delete, successful delete of names can be expected. * * <p> * Response is true if all name elements validated ok, false otherwise, responses contain array @@ -815,20 +958,22 @@ public interface INames { */ @Hidden @Operation( - summary = "Return if name elements are valid to delete", + summary = "Return if names are valid to delete by list of name elements", description = """ - Return if name elements are valid to delete. - If names are valid to update, successful delete of names can be expected. + Return if names are valid to delete by list of name elements. + If names are valid to delete, successful delete of names can be expected. Required attributes: - uuid - comment + + Other attributes are not considered. """ ) @ApiResponses(value = { @ApiResponse( responseCode = "200", - description = "OK. Response is true if all name elements validated ok, false otherwise. " + description = "OK. Response is true if all name elements validated ok, false otherwise." + "Responses contain array with result for each name element. Message and details are available if no response is available.", content = @Content( mediaType = "application/json", @@ -873,30 +1018,32 @@ public interface INames { /** * Update names by list of name elements. - * Returns list of updated name elements. + * Return list of name elements for updated names. * * @param nameElements list of name elements - * @return list of updated name elements + * @return list of name elements for updated names */ @Operation( summary = "Update names by array of name elements", description = """ Update names by array of name elements. - Return array of updated name elements. + Return array of name elements for updated names. Required attributes: - uuid - - parentsystemstructure + - parentsystemstructure (uuid) - description - comment - Other attributes are optional. + Optional attributes: + - parentdevicestructure (uuid) + - index """ ) @ApiResponses(value = { @ApiResponse( responseCode = "200", - description = "OK. Return array of updated name elements.", + description = "OK. Return array of name elements for updated names.", content = @Content( mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = NameElement.class)))), @@ -935,20 +1082,82 @@ public interface INames { requiredProperties = {"uuid","parentsystemstructure","description","comment"})))) @RequestBody List<NameElementCommand> nameElements); + /** + * Update names by upload Excel file. + * Return Excel file with name elements for updated names. + * + * @param file file + * @return Excel file + */ + @Operation( + summary = "Update names by upload Excel file", + description = """ + Update names by upload Excel file. + Return Excel file with name elements for updated names. + + Expected columns as in Excel template for names. + + Required attributes: + - uuid + - parentsystemstructure (uuid) + - description + - comment + + Optional attributes: + - parentdevicestructure (uuid) + - index + """ + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "OK. Return Excel file with name elements for updated names.", + content = @Content(mediaType = ExcelUtil.MIME_TYPE_EXCEL)), + @ApiResponse( + responseCode = "400", + description = "Bad request. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "422", + description = "Unprocessable entity. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))) + }) + @PutMapping( + value = "/upload", + produces = {"application/vnd.ms-excel"}, + consumes = {"multipart/form-data"}) + public ResponseEntity<Resource> updateNames( + @Parameter( + in = ParameterIn.DEFAULT, + description = "Excel file with name elements", + required = true, + content = @Content(mediaType = ExcelUtil.MIME_TYPE_OPENXML_SPREADSHEET)) + @RequestParam("file") MultipartFile file); + // ---------------------------------------------------------------------------------------------------- /** * Delete names by list of name elements. - * Returns list of deleted name elements. + * Return list of name elements for deleted names. * - * @param uuid uuid - * @return list of deleted name elements + * @param nameElements list of name elements + * @return list of name elements for deleted names */ @Operation( summary = "Delete names by array of name elements", description = """ Delete names by array of name elements. - Return array of deleted name elements. + Return array of name elements for deleted names. Required attributes: - uuid @@ -960,7 +1169,7 @@ public interface INames { @ApiResponses(value = { @ApiResponse( responseCode = "200", - description = "OK. Return array of deleted name elements.", + description = "OK. Return array of name elements for deleted names.", content = @Content( mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = NameElement.class)))), @@ -999,4 +1208,62 @@ public interface INames { requiredProperties = {"uuid","comment"})))) @RequestBody List<NameElementCommand> nameElements); + /** + * Delete names by upload Excel file. + * Return Excel file with name elements for deleted names. + * + * @param file file + * @return Excel file + */ + @Operation( + summary = "Delete names by upload Excel file", + description = """ + Delete names by upload Excel file. + Return Excel file with name elements for deleted names. + + Expected columns as in Excel template for names. + + Required attributes: + - uuid + - comment + + Other attributes are not considered. + """ + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "OK. Return Excel file with name elements for deleted names.", + content = @Content(mediaType = ExcelUtil.MIME_TYPE_EXCEL)), + @ApiResponse( + responseCode = "400", + description = "Bad request. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "422", + description = "Unprocessable entity. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))) + }) + @DeleteMapping( + value = "/upload", + produces = {"application/vnd.ms-excel"}, + consumes = {"multipart/form-data"}) + public ResponseEntity<Resource> deleteNames( + @Parameter( + in = ParameterIn.DEFAULT, + description = "Excel file with name elements", + required = true, + content = @Content(mediaType = ExcelUtil.MIME_TYPE_OPENXML_SPREADSHEET)) + @RequestParam("file") MultipartFile file); + } diff --git a/src/main/java/org/openepics/names/rest/api/v1/IStructures.java b/src/main/java/org/openepics/names/rest/api/v1/IStructures.java index 17cba1bb25bb8a260ee448ae8cadb7db734249d3..45d962ad63206959d7a94d7e82cf3ccae9f521d3 100644 --- a/src/main/java/org/openepics/names/rest/api/v1/IStructures.java +++ b/src/main/java/org/openepics/names/rest/api/v1/IStructures.java @@ -29,6 +29,8 @@ import org.openepics.names.rest.beans.response.Response; import org.openepics.names.rest.beans.response.ResponseBoolean; import org.openepics.names.rest.beans.response.ResponseBooleanList; import org.openepics.names.rest.beans.response.ResponsePageStructureElements; +import org.openepics.names.util.ExcelUtil; +import org.springframework.core.io.Resource; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -39,6 +41,7 @@ import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; @@ -66,20 +69,23 @@ public interface IStructures { Status - APPROVED, ARCHIVED, CANCELLED, PENDING, REJECTED FieldStructure - UUID, PARENT, NAME, MNEMONIC, MNEMONICEQUIVALENCE, MNEMONICPATH, DESCRIPTION - StructureElement - type (Type) + StructureElementCommand - subset of StructureElement attributes + StructureElement uuid (UUID) + type (Type) parent (UUID) name (String) mnemonic (String) + description (String) + comment (String) + ------------------------- mnemonicpath (String) level (Integer) - description (String) status (Status) latest (Boolean) deleted (Boolean) when (Date) who (String) - comment (String) authentication/authorization 3 levels - no, user, administrator @@ -91,8 +97,10 @@ public interface IStructures { Methods create POST /structures - createStructures (List<StructureElementCommand>) + create POST /structures/upload - createStructures (MultipartFile) ---------------------------------------------------------------------------------------------------- read GET /structures/{type} - readStructures (Type, Status[], Boolean, FieldStructure[], String[], FieldStructure, Boolean, Integer, Integer) + read GET /structures/{type}/download - readStructuresDownload (Type, Status[], Boolean, FieldStructure[], String[], FieldStructure, Boolean, Integer, Integer) read GET /structures/children/{type}/{uuid} - readStructuresChildren (Type, String, FieldStructure, Boolean, Integer, Integer) read GET /structures/mnemonic/{mnemonic} - readStructuresMnemonic (String, FieldStructure, Boolean, Integer, Integer, FieldStructure, Boolean, Integer, Integer) read GET /structures/mnemonicpath/{mnemonicpath} - readStructuresMnemonicpath (String, FieldStructure, Boolean, Integer, Integer, FieldStructure, Boolean, Integer, Integer) @@ -110,12 +118,20 @@ public interface IStructures { read GET /structures/validatereject - validateStructuresReject (List<StructureElementCommand>) ---------------------------------------------------------------------------------------------------- update PUT /structures - updateStructures (List<StructureElementCommand>) + update PUT /structures/upload - updateStructures (MultipartFile) ---------------------------------------------------------------------------------------------------- delete DELETE /structures - deleteStructures (List<StructureElementCommand>) + delete DELETE /structures/upload - deleteStructures (MultipartFile) ---------------------------------------------------------------------------------------------------- update PATCH /structures/approve - approveStructures (List<StructureElementCommand>) + update PATCH /structures/approve/upload - approveStructures (MultipartFile) update PATCH /structures/cancel - cancelStructures (List<StructureElementCommand>) + update PATCH /structures/cancel/upload - cancelStructures (MultipartFile) update PATCH /structures/reject - rejectStructures (List<StructureElementCommand>) + update PATCH /structures/reject/upload - rejectStructures (MultipartFile) + + Note + /upload and /download endpoints handle Excel files */ public static final String DEFAULT_PAGE = "0"; @@ -123,20 +139,7 @@ public interface IStructures { /** * Create (propose) structures by list of structure elements. - * Return list of created structure elements (proposals). - * - * <p> - * StructureElement attributes required: - * <ul> - * <li>type</li> - * <li>parent (System, Subsystem, DeviceGroup, DeviceType)</li> - * <li>name</li> - * <li>mnemonic (System, Subsystem, Discipline, DeviceType) - * (may be set for SystemGroup, not allowed for DeviceGroup)</li> - * <li>description</li> - * <li>comment</li> - * </ul> - * </p> + * Return list of structure elements for created structures (proposals). * * @param structureElements list of structure elements * @return list of structure elements for created structures (proposals) @@ -145,11 +148,11 @@ public interface IStructures { summary = "Create (propose) structures by array of structure elements", description = """ Create (propose) structures by array of structure elements. - Return array of created structure elements (proposals). + Return array of structure elements for created structures (proposals). Required attributes: - type - - parent (System, Subsystem, DeviceGroup, DeviceType) + - parent (System, Subsystem, DeviceGroup, DeviceType)(uuid) - name - mnemonic (System, Subsystem, Discipline, DeviceType, may be set for SystemGroup, not allowed for DeviceGroup) - description @@ -206,6 +209,74 @@ public interface IStructures { requiredProperties = {"type","parent","name","mnemonic","description","comment"})))) @RequestBody List<StructureElementCommand> structureElements); + /** + * Create (propose) structures by upload Excel file. + * Return Excel file with structure elements for created structures (proposals). + * + * @param file file + * @return Excel file + */ + @Operation( + summary = "Create (propose) structures by upload Excel file", + description = """ + Create (propose) structures by upload Excel file. + Return Excel file with structure elements for created structures (proposals). + + Expected columns as in Excel template for structures. + + Required attributes: + - type + - parent (System, Subsystem, DeviceGroup, DeviceType)(uuid) + - name + - mnemonic (System, Subsystem, Discipline, DeviceType, may be set for SystemGroup, not allowed for DeviceGroup) + - description + - comment + + Uuid is created by Naming. + """ + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "201", + description = "OK. Return Excel file with structure elements for created structures (proposals).", + content = @Content(mediaType = ExcelUtil.MIME_TYPE_EXCEL)), + @ApiResponse( + responseCode = "400", + description = "Bad request. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "409", + description = "Conflict. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "422", + description = "Unprocessable entity. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))) + }) + @PostMapping( + value = "/upload", + produces = {"application/vnd.ms-excel"}, + consumes = {"multipart/form-data"}) + public ResponseEntity<Resource> createStructures( + @Parameter( + in = ParameterIn.DEFAULT, + description = "Excel file with structure elements", + required = true, + content = @Content(mediaType = ExcelUtil.MIME_TYPE_OPENXML_SPREADSHEET)) + @RequestParam("file") MultipartFile file); + // ---------------------------------------------------------------------------------------------------- /** @@ -271,6 +342,67 @@ public interface IStructures { @Parameter(in = ParameterIn.QUERY, description = "page starting from 0, offset") @RequestParam(required = false, defaultValue = DEFAULT_PAGE) Integer page, @Parameter(in = ParameterIn.QUERY, description = "page size, limit") @RequestParam(required = false, defaultValue = DEFAULT_PAGE_SIZE) Integer pageSize); + /** + * Find valid structures (search). + * Return Excel file with paged list of structure elements. + * + * @param type type of structure to search in + * @param statuses statuses of structures to search for + * @param deleted if deleted-only structures are to be included + * (false for non-deleted-only structures, true for deleted-only structures, not used for both cases) + * @param queryFields search fields + * @param queryValues search values corresponding to search fields + * @param orderBy order by field + * @param isAsc sort order, ascending or descending + * @param page page starting from 0, offset + * @param pageSize page size, limit + * @return paged list of structure elements + */ + @Operation( + summary = "Find valid structures (search) and download Excel file", + description = """ + Find valid structures (search). + Return Excel file with paged list of structure elements. + """ + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "OK. Return Excel file with paged list of structure elements.", + content = @Content(mediaType = ExcelUtil.MIME_TYPE_EXCEL)), + @ApiResponse( + responseCode = "400", + description = "Bad request. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "422", + description = "Unprocessable entity. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))) + }) + @GetMapping( + value = "/{type}/download", + produces = {"application/vnd.ms-excel"}) + public ResponseEntity<Resource> readStructuresDownload( + @Parameter(in = ParameterIn.PATH, description = "type of structure to search in") @PathVariable("type") Type type, + @Parameter(in = ParameterIn.QUERY, description = "statuses of structures to search for") @RequestParam(required = false) Status[] statuses, + @Parameter(in = ParameterIn.QUERY, description = "if deleted structures are to be included or not, omitted for both deleted and not deleted structures") @RequestParam(required = false) Boolean deleted, + @Parameter(in = ParameterIn.QUERY, description = "search fields") @RequestParam(required = false) FieldStructure[] queryFields, + @Parameter(in = ParameterIn.QUERY, description = "search values corresponding to search fields") @RequestParam(required = false) String[] queryValues, + @Parameter(in = ParameterIn.QUERY, description = "order by field") @RequestParam(required = false) FieldStructure orderBy, + @Parameter(in = ParameterIn.QUERY, description = "sort order, ascending or descending") @RequestParam(required = false) Boolean isAsc, + @Parameter(in = ParameterIn.QUERY, description = "page starting from 0, offset") @RequestParam(required = false, defaultValue = DEFAULT_PAGE) Integer page, + @Parameter(in = ParameterIn.QUERY, description = "page size, limit") @RequestParam(required = false, defaultValue = DEFAULT_PAGE_SIZE) Integer pageSize); + /** * Find valid children structures by type and parent uuid (exact match). * Return paged list of structure elements. @@ -661,8 +793,8 @@ public interface IStructures { // ---------------------------------------------------------------------------------------------------- /** - * Return if structure elements are valid to create (propose). - * If structure elements are valid to create, successful create of structures can be expected. + * Return if structures are valid to create (propose) by list of structure elements. + * If structures are valid to create, successful create of structures can be expected. * * <p> * Returned object has four fields (message, details, response, responses). @@ -675,18 +807,18 @@ public interface IStructures { * </p> * * @param structureElements list of structure elements - * @return if list of structure elements is valid to create (propose) + * @return if list of structures is valid to create (propose) */ @Hidden @Operation( - summary = "Return if structure elements are valid to create (propose)", + summary = "Return if structures are valid to create (propose) by list of structure elements", description = """ - Return if structure elements are valid to create (propose). - If structure elements are valid to create, successful create of structures can be expected. + Return if structures are valid to create (propose) by list of structure elements. + If structures are valid to create, successful create of structures can be expected. Required attributes: - type - - parent (System, Subsystem, DeviceGroup, DeviceType) + - parent (System, Subsystem, DeviceGroup, DeviceType)(uuid) - name - mnemonic (System, Subsystem, Discipline, DeviceType, may be set for SystemGroup, not allowed for DeviceGroup) - description @@ -744,8 +876,8 @@ public interface IStructures { @RequestBody List<StructureElementCommand> structureElements); /** - * Return if structure elements are valid to update (propose). - * If structure elements are valid to update, successful update of structures can be expected. + * Return if structures are valid to update (propose) by list of structure elements. + * If structures are valid to update, successful update of structures can be expected. * * <p> * Returned object has four fields (message, details, response, responses). @@ -758,19 +890,19 @@ public interface IStructures { * </p> * * @param structureElements list of structure elements - * @return if list of structure elements is valid to update (propose) + * @return if list of structures is valid to update (propose) */ @Hidden @Operation( - summary = "Return if structure elements are valid to update (propose)", + summary = "Return if structures are valid to update (propose) by list of structure elements", description = """ - Return if structure elements are valid to update (propose). - If structure elements are valid to update, successful update of structures can be expected. + Return if structures are valid to update (propose) by list of structure elements. + If structures are valid to update, successful update of structures can be expected. Required attributes: - uuid - type - - parent (System, Subsystem, DeviceGroup, DeviceType) + - parent (System, Subsystem, DeviceGroup, DeviceType)(uuid) - name - mnemonic (System, Subsystem, Discipline, DeviceType, may be set for SystemGroup, not allowed for DeviceGroup) - description @@ -828,8 +960,8 @@ public interface IStructures { @RequestBody List<StructureElementCommand> structureElements); /** - * Return if structure elements are valid to delete (propose). - * If structure elements are valid to delete, successful delete of structures can be expected. + * Return if structures are valid to delete (propose) by list of structure elements. + * If structures are valid to delete, successful delete of structures can be expected. * * <p> * Returned object has four fields (message, details, response, responses). @@ -842,14 +974,14 @@ public interface IStructures { * </p> * * @param structureElements list of structure elements - * @return if list of structure elements is valid to delete (propose) + * @return if list of structures is valid to delete (propose) */ @Hidden @Operation( - summary = "Return if structure elements are valid to delete (propose)", + summary = "Return if structures are valid to delete (propose) by list of structure elements", description = """ - Return if structure elements are valid to delete (propose). - If structure elements are valid to delete, successful delete of structures can be expected. + Return if structures are valid to delete (propose) by list of structure elements. + If structures are valid to delete, successful delete of structures can be expected. Required attributes: - uuid @@ -908,8 +1040,8 @@ public interface IStructures { @RequestBody List<StructureElementCommand> structureElements); /** - * Return if structure elements are valid to approve. - * If structure elements are valid to approve, successful approve of structures can be expected. + * Return if structures are valid to approve by list of structure elements. + * If structures are valid to approve, successful approve of structures can be expected. * * <p> * Returned object has four fields (message, details, response, responses). @@ -922,19 +1054,19 @@ public interface IStructures { * </p> * * @param structureElements list of structure elements - * @return if list of structure elements is valid to approve + * @return if list of structures is valid to approve */ @Hidden @Operation( - summary = "Return if structure elements are valid to approve", + summary = "Return if structures are valid to approve by list of structure elements", description = """ - Return if structure elements are valid to approve. - If structure elements are valid to approve, successful approve of structures can be expected. + Return if structures are valid to approve by list of structure elements. + If structures are valid to approve, successful approve of structures can be expected. Required attributes: - uuid - type - - parent (System, Subsystem, DeviceGroup, DeviceType) + - parent (System, Subsystem, DeviceGroup, DeviceType)(uuid) - name - mnemonic (System, Subsystem, Discipline, DeviceType, may be set for SystemGroup, not allowed for DeviceGroup) - description @@ -992,8 +1124,8 @@ public interface IStructures { @RequestBody List<StructureElementCommand> structureElements); /** - * Return if structure elements are valid to cancel. - * If structure elements are valid to cancel, successful cancel of structures can be expected. + * Return if structures are valid to cancel by list of structure elements. + * If structures are valid to cancel, successful cancel of structures can be expected. * * <p> * Returned object has four fields (message, details, response, responses). @@ -1006,19 +1138,19 @@ public interface IStructures { * </p> * * @param structureElements list of structure elements - * @return if list of structure elements is valid to cancel + * @return if list of structures is valid to cancel */ @Hidden @Operation( - summary = "Return if structure elements are valid to cancel", + summary = "Return if structures are valid to cancel by list of structure elements", description = """ - Return if structure elements are valid to cancel. - If structure elements are valid to cancel, successful cancel of structures can be expected. + Return if structures are valid to cancel by list of structure elements. + If structures are valid to cancel, successful cancel of structures can be expected. Required attributes: - uuid - type - - parent (System, Subsystem, DeviceGroup, DeviceType) + - parent (System, Subsystem, DeviceGroup, DeviceType)(uuid) - name - mnemonic (System, Subsystem, Discipline, DeviceType, may be set for SystemGroup, not allowed for DeviceGroup) - description @@ -1076,8 +1208,8 @@ public interface IStructures { @RequestBody List<StructureElementCommand> structureElements); /** - * Return if structure elements are valid to reject. - * If structure elements are valid to reject, successful reject of structures can be expected. + * Return if structures are valid to reject by list of structure elements. + * If structures are valid to reject, successful reject of structures can be expected. * * <p> * Returned object has four fields (message, details, response, responses). @@ -1090,19 +1222,19 @@ public interface IStructures { * </p> * * @param structureElements list of structure elements - * @return if list of structure elements is valid to reject + * @return if list of structures is valid to reject */ @Hidden @Operation( - summary = "Return if structure elements are valid to reject", + summary = "Return if structures are valid to reject by list of structure elements", description = """ - Return if structure elements are valid to reject. - If structure elements are valid to reject, successful reject of structures can be expected. + Return if structures are valid to reject by list of structure elements. + If structures are valid to reject, successful reject of structures can be expected. Required attributes: - uuid - type - - parent (System, Subsystem, DeviceGroup, DeviceType) + - parent (System, Subsystem, DeviceGroup, DeviceType)(uuid) - name - mnemonic (System, Subsystem, Discipline, DeviceType, may be set for SystemGroup, not allowed for DeviceGroup) - description @@ -1163,7 +1295,7 @@ public interface IStructures { /** * Update (propose) structures by list of structure elements. - * Return list of updated structure elements (proposals). + * Return list of structure elements for updated structures (proposals). * * @param structureElements list of structure elements * @return list of structure elements for updated structures (proposals) @@ -1172,12 +1304,12 @@ public interface IStructures { summary = "Update (propose) structures by array of structure elements", description = """ Update (propose) structures by array of structure elements. - Return array of updated structure elements (proposals). + Return array of structure elements for updated structures (proposals). Required attributes: - uuid - type - - parent (System, Subsystem, DeviceGroup, DeviceType) + - parent (System, Subsystem, DeviceGroup, DeviceType)(uuid) - name - mnemonic (System, Subsystem, Discipline, DeviceType, may be set for SystemGroup, not allowed for DeviceGroup) - description @@ -1232,11 +1364,78 @@ public interface IStructures { requiredProperties = {"uuid","type","parent","name","mnemonic","description","comment"})))) @RequestBody List<StructureElementCommand> structureElements); + /** + * Update (propose) structures by upload Excel file. + * Return Excel file with structure elements for updated structures (proposals). + * + * @param file file + * @return Excel file + */ + @Operation( + summary = "Update (propose) structures by upload Excel file", + description = """ + Update (propose) structures by upload Excel file. + Return Excel file with structure elements for updated structures (proposals). + + Expected columns as in Excel template for structures. + + Required attributes: + - uuid + - type + - parent (System, Subsystem, DeviceGroup, DeviceType)(uuid) + - name + - mnemonic (System, Subsystem, Discipline, DeviceType, may be set for SystemGroup, not allowed for DeviceGroup) + - description + - comment + """ + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "OK. Return Excel file with structure elements for updated structures (proposals).", + content = @Content(mediaType = ExcelUtil.MIME_TYPE_EXCEL)), + @ApiResponse( + responseCode = "400", + description = "Bad request. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "409", + description = "Conflict. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "422", + description = "Unprocessable entity. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))) + }) + @PutMapping( + value = "/upload", + produces = {"application/vnd.ms-excel"}, + consumes = {"multipart/form-data"}) + public ResponseEntity<Resource> updateStructures( + @Parameter( + in = ParameterIn.DEFAULT, + description = "Excel file with structure elements", + required = true, + content = @Content(mediaType = ExcelUtil.MIME_TYPE_OPENXML_SPREADSHEET)) + @RequestParam("file") MultipartFile file); + // ---------------------------------------------------------------------------------------------------- /** * Delete (propose) structures by list of structure elements. - * Return list of deleted structure elements (proposals). + * Return list of structure elements for deleted structures (proposals). * * @param structureElements list of structure elements * @return list of structure elements for deleted structures (proposals) @@ -1245,7 +1444,7 @@ public interface IStructures { summary = "Delete (propose) structures by array of structure elements", description = """ Delete (propose) structures by array of structure elements. - Return array of deleted structure elements (proposals). + Return array of structure elements for deleted structures (proposals). Required attributes: - uuid @@ -1303,12 +1502,75 @@ public interface IStructures { requiredProperties = {"uuid","type","comment"})))) @RequestBody List<StructureElementCommand> structureElements); + /** + * Delete (propose) structures by upload Excel file. + * Return Excel file with structure elements for deleted structures (proposals). + * + * @param file file + * @return Excel file + */ + @Operation( + summary = "Delete (propose) structures by upload Excel file", + description = """ + Delete (propose) structures by upload Excel file. + Return Excel file with structure elements for deleted structures (proposals). + + Expected columns as in Excel template for structures. + Required attributes: + - uuid + - type + - comment + + Other attributes are not considered. + """ + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "OK. Return Excel file with structure elements for deleted structures (proposals).", + content = @Content(mediaType = ExcelUtil.MIME_TYPE_EXCEL)), + @ApiResponse( + responseCode = "400", + description = "Bad request. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "409", + description = "Conflict. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "422", + description = "Unprocessable entity. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))) + }) + @DeleteMapping( + value = "/upload", + produces = {"application/vnd.ms-excel"}, + consumes = {"multipart/form-data"}) + public ResponseEntity<Resource> deleteStructures( + @Parameter( + in = ParameterIn.DEFAULT, + description = "Excel file with structure elements", + required = true, + content = @Content(mediaType = ExcelUtil.MIME_TYPE_OPENXML_SPREADSHEET)) + @RequestParam("file") MultipartFile file); // ---------------------------------------------------------------------------------------------------- /** * Approve structures (proposals) by list of structure elements. - * Return list of approved structure elements. + * Return list of structure elements for approved structures. * * @param structureElements list of structure elements * @return list of structure elements for approved structures @@ -1317,12 +1579,12 @@ public interface IStructures { summary = "Approve structures (proposals) by array of structure elements", description = """ Approve structures (proposals) by array of structure elements. - Return array of approved structure elements. + Return array of structure elements for approved structures. Required attributes: - uuid - type - - parent (System, Subsystem, DeviceGroup, DeviceType) + - parent (System, Subsystem, DeviceGroup, DeviceType)(uuid) - name - mnemonic (System, Subsystem, Discipline, DeviceType, may be set for SystemGroup, not allowed for DeviceGroup) - description @@ -1378,6 +1640,73 @@ public interface IStructures { requiredProperties = {"uuid","type","parent","name","mnemonic","description","comment"})))) @RequestBody List<StructureElementCommand> structureElements); + /** + * Approve structures (proposals) by upload Excel file. + * Return Excel file with structure elements for approved structures. + * + * @param structureElements list of structure elements + * @return Excel file + */ + @Operation( + summary = "Approve structures (proposals) by upload Excel file", + description = """ + Approve structures (proposals) by upload Excel file. + Return Excel file with structure elements for for approved structures. + + Expected columns as in Excel template for structures. + + Required attributes: + - uuid + - type + - parent (System, Subsystem, DeviceGroup, DeviceType)(uuid) + - name + - mnemonic (System, Subsystem, Discipline, DeviceType, may be set for SystemGroup, not allowed for DeviceGroup) + - description + - comment + """ + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "OK. Return Excel file with structure elements for approved structures.", + content = @Content(mediaType = ExcelUtil.MIME_TYPE_EXCEL)), + @ApiResponse( + responseCode = "400", + description = "Bad request. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "409", + description = "Conflict. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "422", + description = "Unprocessable entity. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))) + }) + @PatchMapping( + value = "/approve/upload", + produces = {"application/vnd.ms-excel"}, + consumes = {"multipart/form-data"}) + public ResponseEntity<Resource> approveStructures( + @Parameter( + in = ParameterIn.DEFAULT, + description = "Excel file with structure elements", + required = true, + content = @Content(mediaType = ExcelUtil.MIME_TYPE_OPENXML_SPREADSHEET)) + @RequestParam("file") MultipartFile file); + /** * Cancel structures (proposals) by list of structure elements. * Return list of cancelled structure elements. @@ -1389,12 +1718,12 @@ public interface IStructures { summary = "Cancel structures (proposals) by array of structure elements", description = """ Cancel structures (proposals) by array of structure elements. - Return array of cancelled structure elements. + Return array of structure elements for cancelled structures. Required attributes: - uuid - type - - parent (System, Subsystem, DeviceGroup, DeviceType) + - parent (System, Subsystem, DeviceGroup, DeviceType)(uuid) - name - mnemonic (System, Subsystem, Discipline, DeviceType, may be set for SystemGroup, not allowed for DeviceGroup) - description @@ -1450,6 +1779,73 @@ public interface IStructures { requiredProperties = {"uuid","type","parent","name","mnemonic","description","comment"})))) @RequestBody List<StructureElementCommand> structureElements); + /** + * Cancel structures (proposals) by upload Excel file. + * Return Excel file with cancelled structure elements. + * + * @param structureElements list of structure elements + * @return Excel file + */ + @Operation( + summary = "Cancel structures (proposals) by upload Excel file", + description = """ + Cancel structures (proposals) by upload Excel file. + Return Excel file with structure elements for cancelled structures. + + Expected columns as in Excel template for structures. + + Required attributes: + - uuid + - type + - parent (System, Subsystem, DeviceGroup, DeviceType)(uuid) + - name + - mnemonic (System, Subsystem, Discipline, DeviceType, may be set for SystemGroup, not allowed for DeviceGroup) + - description + - comment + """ +) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "OK. Return Excel file with structure elements for cancelled structures.", + content = @Content(mediaType = ExcelUtil.MIME_TYPE_EXCEL)), + @ApiResponse( + responseCode = "400", + description = "Bad request. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "409", + description = "Conflict. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "422", + description = "Unprocessable entity. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))) + }) + @PatchMapping( + value = "/cancel/upload", + produces = {"application/vnd.ms-excel"}, + consumes = {"multipart/form-data"}) + public ResponseEntity<Resource> cancelStructures( + @Parameter( + in = ParameterIn.DEFAULT, + description = "Excel file with structure elements", + required = true, + content = @Content(mediaType = ExcelUtil.MIME_TYPE_OPENXML_SPREADSHEET)) + @RequestParam("file") MultipartFile file); + /** * Reject structures (proposals) by list of structure elements. * Return list of rejected structure elements. @@ -1461,12 +1857,12 @@ public interface IStructures { summary = "Reject structures (proposals) by array of structure elements", description = """ Reject structures (proposals) by array of structure elements. - Return array of rejected structure elements. + Return array of structure elements for rejected structures. Required attributes: - uuid - type - - parent (System, Subsystem, DeviceGroup, DeviceType) + - parent (System, Subsystem, DeviceGroup, DeviceType)(uuid) - name - mnemonic (System, Subsystem, Discipline, DeviceType, may be set for SystemGroup, not allowed for DeviceGroup) - description @@ -1522,4 +1918,71 @@ public interface IStructures { requiredProperties = {"uuid","type","parent","name","mnemonic","description","comment"})))) @RequestBody List<StructureElementCommand> structureElements); + /** + * Reject structures (proposals) by upload Excel file. + * Return Excel file with rejected structure elements. + * + * @param structureElements list of structure elements + * @return Excel file + */ + @Operation( + summary = "Reject structures (proposals) by upload Excel file", + description = """ + Reject structures (proposals) by upload Excel file. + Return Excel file with structure elements for rejected structures. + + Expected columns as in Excel template for structures. + + Required attributes: + - uuid + - type + - parent (System, Subsystem, DeviceGroup, DeviceType)(uuid) + - name + - mnemonic (System, Subsystem, Discipline, DeviceType, may be set for SystemGroup, not allowed for DeviceGroup) + - description + - comment + """ + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "OK. Return Excel file with structure elements for rejected structures.", + content = @Content(mediaType = ExcelUtil.MIME_TYPE_EXCEL)), + @ApiResponse( + responseCode = "400", + description = "Bad request. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "409", + description = "Conflict. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "422", + description = "Unprocessable entity. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error. Reason and information such as message, details, field are available.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Response.class))) + }) + @PatchMapping( + value = "/reject/upload", + produces = {"application/vnd.ms-excel"}, + consumes = {"multipart/form-data"}) + public ResponseEntity<Resource> rejectStructures( + @Parameter( + in = ParameterIn.DEFAULT, + description = "Excel file with structure elements", + required = true, + content = @Content(mediaType = ExcelUtil.MIME_TYPE_OPENXML_SPREADSHEET)) + @RequestParam("file") MultipartFile file); + } diff --git a/src/main/java/org/openepics/names/rest/beans/element/NameElement.java b/src/main/java/org/openepics/names/rest/beans/element/NameElement.java index e3d1cf62bb43608f040d70a40a43abcdfef5af53..f0e1378df9e798925403a92ee3efc3d3eb1471f6 100644 --- a/src/main/java/org/openepics/names/rest/beans/element/NameElement.java +++ b/src/main/java/org/openepics/names/rest/beans/element/NameElement.java @@ -45,17 +45,17 @@ public class NameElement extends NameElementCommand implements Serializable { private String systemstructure; @Schema(description = "Mnemonic path for for the device structure.") private String devicestructure; - @Schema(description = "Name (verbose) of the name.") + @Schema(description = "Name (verbose) of the name entry.") private String name; - @Schema(description = "Status of the name.") + @Schema(description = "Status of the name entry.") private Status status; - @Schema(description = "If the name is latest (with status APPROVED) in its line of entries.") + @Schema(description = "If the name entry is latest (with status APPROVED) in its line of entries.") private Boolean latest; - @Schema(description = "If the name is deleted.") + @Schema(description = "If the name entry is deleted.") private Boolean deleted; - @Schema(description = "Date and time when the name was created.") + @Schema(description = "Date and time when the name entry was created.") private Date when; - @Schema(description = "(User) Name of who created the name.") + @Schema(description = "Name (user) of who created the name entry.") private String who; /** diff --git a/src/main/java/org/openepics/names/rest/beans/element/NameElementCommand.java b/src/main/java/org/openepics/names/rest/beans/element/NameElementCommand.java index b1899d42a8cfb5723cd7d6e7d212ad3b4f6573a6..2f21e63d3d394b5b8d4dc6e0bf938d57aa1dd184 100644 --- a/src/main/java/org/openepics/names/rest/beans/element/NameElementCommand.java +++ b/src/main/java/org/openepics/names/rest/beans/element/NameElementCommand.java @@ -38,17 +38,17 @@ public class NameElementCommand implements Serializable { */ private static final long serialVersionUID = -3166840982436164399L; - @Schema(description = "Identity of the name. Value is set server-side.") + @Schema(description = "Identity (uuid) of the name entry. Value is created server-side.") private UUID uuid; - @Schema(description = "Identity for the system structure parent.") + @Schema(description = "Identity (uuid) for the system structure parent.") private UUID parentsystemstructure; - @Schema(description = "Identity for the device structure parent.") + @Schema(description = "Identity (uuid) for the device structure parent (if the name entry refers to device structure).") private UUID parentdevicestructure; - @Schema(description = "Index (instance) of the name.") + @Schema(description = "Index (instance) of the name entry (if the name entry refers to device structure).") private String index; - @Schema(description = "Description of the name.") + @Schema(description = "Description of the name entry.") private String description; - @Schema(description = "Comment of the name command.") + @Schema(description = "Comment of the name entry command.") private String comment; /** diff --git a/src/main/java/org/openepics/names/rest/beans/element/StructureElement.java b/src/main/java/org/openepics/names/rest/beans/element/StructureElement.java index 68f76cc3e8ead2cd1f2db51c1737aa61ba734251..30d5f1385e644b7565fa789a5eccdc6eed129b24 100644 --- a/src/main/java/org/openepics/names/rest/beans/element/StructureElement.java +++ b/src/main/java/org/openepics/names/rest/beans/element/StructureElement.java @@ -54,7 +54,7 @@ public class StructureElement extends StructureElementCommand implements Seriali private Boolean deleted; @Schema(description = "Date and time when the structure entry was created.") private Date when; - @Schema(description = "(User) Name of who created the structure entry.") + @Schema(description = "Name (user) of who created the structure entry.") private String who; /** diff --git a/src/main/java/org/openepics/names/rest/beans/element/StructureElementCommand.java b/src/main/java/org/openepics/names/rest/beans/element/StructureElementCommand.java index d64eaee01f1eea6f6ddf25be336e373e0768a05e..13b7ad80e5df7c913d89e7d18c59b2a6a0e682c4 100644 --- a/src/main/java/org/openepics/names/rest/beans/element/StructureElementCommand.java +++ b/src/main/java/org/openepics/names/rest/beans/element/StructureElementCommand.java @@ -41,11 +41,11 @@ public class StructureElementCommand implements Serializable { */ private static final long serialVersionUID = 788979309267676232L; - @Schema(description = "Identity of the structure entry. Value is set server-side.") + @Schema(description = "Identity (uuid) of the structure entry. Value is created server-side.") private UUID uuid; @Schema(description = "Type of the structure entry.") private Type type; - @Schema(description = "Identity for the structure entry parent (if the structure entry has a parent).") + @Schema(description = "Identity (uuid) for the structure entry parent (if the structure entry has a parent).") private UUID parent; @Schema(description = "Name of the structure entry.") private String name; diff --git a/src/main/java/org/openepics/names/rest/controller/NamesController.java b/src/main/java/org/openepics/names/rest/controller/NamesController.java index 5ee006d6d783925a1bf6d979383dabef25177e81..5358b5d9dfbe8d09dc095478e3a6b7ce82bef03e 100644 --- a/src/main/java/org/openepics/names/rest/controller/NamesController.java +++ b/src/main/java/org/openepics/names/rest/controller/NamesController.java @@ -18,6 +18,7 @@ package org.openepics.names.rest.controller; +import java.io.IOException; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -32,13 +33,19 @@ import org.openepics.names.rest.beans.response.ResponseBooleanList; import org.openepics.names.rest.beans.response.ResponsePageNameElements; import org.openepics.names.service.NamesService; import org.openepics.names.service.exception.ServiceException; +import org.openepics.names.util.ExcelUtil; import org.openepics.names.util.LogUtil; import org.openepics.names.util.TextUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import com.google.common.collect.Lists; @@ -85,6 +92,41 @@ public class NamesController implements INames { } } + @Override + public ResponseEntity<Resource> createNames(MultipartFile file) { + // validate authority + // naming user & admin + // validate + // do + + try { + if (ExcelUtil.hasExcelFormat(file)) { + List<NameElementCommand> nameElementCommands = ExcelUtil.excelToNameElementCommands(file.getInputStream()); + namesService.validateNamesCreate(nameElementCommands); + List<NameElement> nameElements = namesService.createNames(nameElementCommands); + InputStreamResource isr = new InputStreamResource(ExcelUtil.nameElementsToExcel(nameElements)); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=NameElement.xlsx") + .contentType(MediaType.parseMediaType(ExcelUtil.MIME_TYPE_EXCEL)) + .body(isr); + } else { + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, null, null, null); + } + } catch (ServiceException e) { + LogUtil.logServiceException(LOGGER, Level.SEVERE, e); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } catch (IOException e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, e.getMessage(), null, e); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } + } + // ---------------------------------------------------------------------------------------------------- @Override @@ -106,6 +148,17 @@ public class NamesController implements INames { } } + @Override + public ResponseEntity<Resource> readNamesDownload(Boolean deleted, FieldName[] queryFields, String[] queryValues, + FieldName orderBy, Boolean isAsc, Integer page, Integer pageSize) { + ResponsePageNameElements nameElements = readNames(deleted, queryFields, queryValues, orderBy, isAsc, page, pageSize); + InputStreamResource isr = new InputStreamResource(ExcelUtil.nameElementsToExcel(nameElements)); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=NameElement.xlsx") + .contentType(MediaType.parseMediaType(ExcelUtil.MIME_TYPE_EXCEL)) + .body(isr); + } + @Override public ResponsePageNameElements readNames( String name, @@ -351,6 +404,41 @@ public class NamesController implements INames { } } + @Override + public ResponseEntity<Resource> updateNames(MultipartFile file) { + // validate authority + // naming user & admin + // validate + // do + + try { + if (ExcelUtil.hasExcelFormat(file)) { + List<NameElementCommand> nameElementCommands = ExcelUtil.excelToNameElementCommands(file.getInputStream()); + namesService.validateNamesUpdate(nameElementCommands); + List<NameElement> nameElements = namesService.updateNames(nameElementCommands); + InputStreamResource isr = new InputStreamResource(ExcelUtil.nameElementsToExcel(nameElements)); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=NameElement.xlsx") + .contentType(MediaType.parseMediaType(ExcelUtil.MIME_TYPE_EXCEL)) + .body(isr); + } else { + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, null, null, null); + } + } catch (ServiceException e) { + LogUtil.logServiceException(LOGGER, Level.SEVERE, e); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } catch (IOException e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, e.getMessage(), null, e); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } + } + // ---------------------------------------------------------------------------------------------------- @Override @@ -374,4 +462,39 @@ public class NamesController implements INames { } } -} \ No newline at end of file + @Override + public ResponseEntity<Resource> deleteNames(MultipartFile file) { + // validate authority + // naming user & admin + // validate + // do + + try { + if (ExcelUtil.hasExcelFormat(file)) { + List<NameElementCommand> nameElementCommands = ExcelUtil.excelToNameElementCommands(file.getInputStream()); + namesService.validateNamesDelete(nameElementCommands); + List<NameElement> nameElements = namesService.deleteNames(nameElementCommands); + InputStreamResource isr = new InputStreamResource(ExcelUtil.nameElementsToExcel(nameElements)); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=NameElement.xlsx") + .contentType(MediaType.parseMediaType(ExcelUtil.MIME_TYPE_EXCEL)) + .body(isr); + } else { + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, null, null, null); + } + } catch (ServiceException e) { + LogUtil.logServiceException(LOGGER, Level.SEVERE, e); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } catch (IOException e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, e.getMessage(), null, e); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } + } + +} diff --git a/src/main/java/org/openepics/names/rest/controller/StructuresController.java b/src/main/java/org/openepics/names/rest/controller/StructuresController.java index 9b708cb5a3b027c3c61ceb0591ae42bb08f2a77e..047ec6ca33d9a220caf7c9a4950dfa8b66f287ef 100644 --- a/src/main/java/org/openepics/names/rest/controller/StructuresController.java +++ b/src/main/java/org/openepics/names/rest/controller/StructuresController.java @@ -18,6 +18,7 @@ package org.openepics.names.rest.controller; +import java.io.IOException; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -34,13 +35,19 @@ import org.openepics.names.rest.beans.response.ResponseBooleanList; import org.openepics.names.rest.beans.response.ResponsePageStructureElements; import org.openepics.names.service.StructuresService; import org.openepics.names.service.exception.ServiceException; +import org.openepics.names.util.ExcelUtil; import org.openepics.names.util.LogUtil; import org.openepics.names.util.TextUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import com.google.common.collect.Lists; @@ -94,6 +101,41 @@ public class StructuresController implements IStructures { } } + @Override + public ResponseEntity<Resource> createStructures(MultipartFile file) { + // validate authority + // naming user & admin + // validate + // do + + try { + if (ExcelUtil.hasExcelFormat(file)) { + List<StructureElementCommand> structureElementCommands = ExcelUtil.excelToStructureElementCommands(file.getInputStream()); + structuresService.validateStructuresCreate(structureElementCommands); + List<StructureElement> structureElements = structuresService.createStructures(structureElementCommands); + InputStreamResource isr = new InputStreamResource(ExcelUtil.structureElementsToExcel(structureElements)); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=StructureElement.xlsx") + .contentType(MediaType.parseMediaType(ExcelUtil.MIME_TYPE_EXCEL)) + .body(isr); + } else { + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, null, null, null); + } + } catch (ServiceException e) { + LogUtil.logServiceException(LOGGER, Level.SEVERE, e); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } catch (IOException e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, e.getMessage(), null, e); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } + } + // ---------------------------------------------------------------------------------------------------- @Override @@ -115,6 +157,18 @@ public class StructuresController implements IStructures { } } + @Override + public ResponseEntity<Resource> readStructuresDownload(Type type, Status[] statuses, Boolean deleted, + FieldStructure[] queryFields, String[] queryValues, FieldStructure orderBy, Boolean isAsc, Integer page, + Integer pageSize) { + ResponsePageStructureElements structureElements = readStructures(type, statuses, deleted, queryFields, queryValues, orderBy, isAsc, page, pageSize); + InputStreamResource isr = new InputStreamResource(ExcelUtil.structureElementsToExcel(structureElements)); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=StructureElement.xlsx") + .contentType(MediaType.parseMediaType(ExcelUtil.MIME_TYPE_EXCEL)) + .body(isr); + } + @Override public ResponsePageStructureElements readStructuresChildren( Type type, String uuid, @@ -434,6 +488,35 @@ public class StructuresController implements IStructures { } } + @Override + public ResponseEntity<Resource> updateStructures(MultipartFile file) { + try { + if (ExcelUtil.hasExcelFormat(file)) { + List<StructureElementCommand> structureElementCommands = ExcelUtil.excelToStructureElementCommands(file.getInputStream()); + structuresService.validateStructuresUpdate(structureElementCommands); + List<StructureElement> structureElements = structuresService.updateStructures(structureElementCommands); + InputStreamResource isr = new InputStreamResource(ExcelUtil.structureElementsToExcel(structureElements)); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=StructureElement.xlsx") + .contentType(MediaType.parseMediaType(ExcelUtil.MIME_TYPE_EXCEL)) + .body(isr); + } else { + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, null, null, null); + } + } catch (ServiceException e) { + LogUtil.logServiceException(LOGGER, Level.SEVERE, e); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } catch (IOException e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, e.getMessage(), null, e); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } + } // ---------------------------------------------------------------------------------------------------- @@ -453,6 +536,36 @@ public class StructuresController implements IStructures { } } + @Override + public ResponseEntity<Resource> deleteStructures(MultipartFile file) { + try { + if (ExcelUtil.hasExcelFormat(file)) { + List<StructureElementCommand> structureElementCommands = ExcelUtil.excelToStructureElementCommands(file.getInputStream()); + structuresService.validateStructuresDelete(structureElementCommands); + List<StructureElement> structureElements = structuresService.deleteStructures(structureElementCommands); + InputStreamResource isr = new InputStreamResource(ExcelUtil.structureElementsToExcel(structureElements)); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=StructureElement.xlsx") + .contentType(MediaType.parseMediaType(ExcelUtil.MIME_TYPE_EXCEL)) + .body(isr); + } else { + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, null, null, null); + } + } catch (ServiceException e) { + LogUtil.logServiceException(LOGGER, Level.SEVERE, e); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } catch (IOException e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, e.getMessage(), null, e); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } + } + // ---------------------------------------------------------------------------------------------------- @Override @@ -471,6 +584,36 @@ public class StructuresController implements IStructures { } } + @Override + public ResponseEntity<Resource> approveStructures(MultipartFile file) { + try { + if (ExcelUtil.hasExcelFormat(file)) { + List<StructureElementCommand> structureElementCommands = ExcelUtil.excelToStructureElementCommands(file.getInputStream()); + structuresService.validateStructuresApprove(structureElementCommands); + List<StructureElement> structureElements = structuresService.approveStructures(structureElementCommands); + InputStreamResource isr = new InputStreamResource(ExcelUtil.structureElementsToExcel(structureElements)); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=StructureElement.xlsx") + .contentType(MediaType.parseMediaType(ExcelUtil.MIME_TYPE_EXCEL)) + .body(isr); + } else { + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, null, null, null); + } + } catch (ServiceException e) { + LogUtil.logServiceException(LOGGER, Level.SEVERE, e); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } catch (IOException e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, e.getMessage(), null, e); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } + } + @Override public List<StructureElement> cancelStructures(List<StructureElementCommand> structureElements) { try { @@ -487,6 +630,36 @@ public class StructuresController implements IStructures { } } + @Override + public ResponseEntity<Resource> cancelStructures(MultipartFile file) { + try { + if (ExcelUtil.hasExcelFormat(file)) { + List<StructureElementCommand> structureElementCommands = ExcelUtil.excelToStructureElementCommands(file.getInputStream()); + structuresService.validateStructuresCancel(structureElementCommands); + List<StructureElement> structureElements = structuresService.cancelStructures(structureElementCommands); + InputStreamResource isr = new InputStreamResource(ExcelUtil.structureElementsToExcel(structureElements)); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=StructureElement.xlsx") + .contentType(MediaType.parseMediaType(ExcelUtil.MIME_TYPE_EXCEL)) + .body(isr); + } else { + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, null, null, null); + } + } catch (ServiceException e) { + LogUtil.logServiceException(LOGGER, Level.SEVERE, e); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } catch (IOException e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, e.getMessage(), null, e); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } + } + @Override public List<StructureElement> rejectStructures(List<StructureElementCommand> structureElements) { try { @@ -503,4 +676,34 @@ public class StructuresController implements IStructures { } } + @Override + public ResponseEntity<Resource> rejectStructures(MultipartFile file) { + try { + if (ExcelUtil.hasExcelFormat(file)) { + List<StructureElementCommand> structureElementCommands = ExcelUtil.excelToStructureElementCommands(file.getInputStream()); + structuresService.validateStructuresReject(structureElementCommands); + List<StructureElement> structureElements = structuresService.rejectStructures(structureElementCommands); + InputStreamResource isr = new InputStreamResource(ExcelUtil.structureElementsToExcel(structureElements)); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=StructureElement.xlsx") + .contentType(MediaType.parseMediaType(ExcelUtil.MIME_TYPE_EXCEL)) + .body(isr); + } else { + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, null, null, null); + } + } catch (ServiceException e) { + LogUtil.logServiceException(LOGGER, Level.SEVERE, e); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } catch (IOException e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw new ServiceException(TextUtil.FILE_COULD_NOT_BE_PARSED, e.getMessage(), null, e); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LogUtil.logStackTraceElements(LOGGER, Level.SEVERE, e); + throw e; + } + } + } diff --git a/src/main/java/org/openepics/names/util/ExcelUtil.java b/src/main/java/org/openepics/names/util/ExcelUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..b98389c8b08584d2cb0ca79ef877a52c1b202dba --- /dev/null +++ b/src/main/java/org/openepics/names/util/ExcelUtil.java @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2022 European Spallation Source ERIC. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package org.openepics.names.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.DateFormat; +import java.text.MessageFormat; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.Comment; +import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.DataFormat; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.RichTextString; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.openepics.names.rest.beans.Type; +import org.openepics.names.rest.beans.element.NameElement; +import org.openepics.names.rest.beans.element.NameElementCommand; +import org.openepics.names.rest.beans.element.StructureElement; +import org.openepics.names.rest.beans.element.StructureElementCommand; +import org.openepics.names.rest.beans.response.ResponsePageNameElements; +import org.openepics.names.rest.beans.response.ResponsePageStructureElements; +import org.openepics.names.service.exception.ServiceException; +import org.springframework.web.multipart.MultipartFile; + +import com.google.common.collect.Lists; + +/** + * Utility class to assist in handling of Excel files. + * + * @author Lars Johansson + */ +public class ExcelUtil { + + private static final Logger LOGGER = Logger.getLogger(ExcelUtil.class.getName()); + + public static final String MIME_TYPE_EXCEL = "application/vnd.ms-excel"; + public static final String MIME_TYPE_OPENXML_SPREADSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + + // width (in units of 1/256th of a character width) + // column width 2.75 inch (to fit header text and uuid) + public static final int SHEET_COLUMN_WIDTH = 28 * 256 + 64; + + public static final int NAMEELEMENTCOMMAND_LENGTH = 6; + public static final String[][] NAMEELEMENT_HEADER_COMMENT = { + {"Uuid", "Identity (uuid) of the name entry. Value is created server-side."}, + {"Parentsystemstructure", "Identity (uuid) for the system structure parent."}, + {"Parentdevicestructure", "Identity (uuid) for the device structure parent (if the name entry refers to device structure)."}, + {"Index", "Index (instance) of the name entry (if the name entry refers to device structure)."}, + {"Description", "Description of the name entry."}, + {"Comment", "Comment of the name entry command."}, + // above NameElementCommand + {"Systemstructure", "Mnemonic path for for the system structure."}, + {"Devicestructure", "Mnemonic path for for the device structure."}, + {"Name", "Name (verbose) of the name entry."}, + {"Status", "Status of the name entry."}, + {"Latest", "If the name entry is latest (with status APPROVED) in its line of entries."}, + {"Deleted", "If the name entry is deleted."}, + {"When", "Date and time when the name entry was created."}, + {"Who", "Name (user) of who created the name entry."} }; + + public static final int STRUCTUREELEMENTCOMMAND_LENGTH = 7; + public static final String[][] STRUCTUREELEMENT_HEADER_COMMENT = { + {"Uuid", "Identity (uuid) of the structure entry. Value is created server-side."}, + {"Type", "Type of the structure entry."}, + {"Parent", "Identity (uuid) for the structure entry parent (if the structure entry has a parent)."}, + {"Name", "Name (verbose) of the structure entry."}, + {"Mnemonic", "Mnemonic of the structure entry."}, + {"Description", "Description of the structure entry."}, + {"Comment", "Comment of the structure entry command."}, + // above StructureElementCommand + {"Mnemonicpath", "Mnemonic path of the structure entry."}, + {"Level", "Level of the structure entry."}, + {"Status", "Status of the structure entry."}, + {"Latest", "If the structure entry is latest (with status APPROVED) in its line of entries."}, + {"Deleted", "If the structure entry is deleted."}, + {"When", "Date and time when the structure entry was created."}, + {"Who", "Name (user) of who created the structure entry."} }; + + private static final String SHEET = "Entries"; + + private static final String ENTRIES_EXCEL_TO_NAME_ELEMENT_COMMANDS = "excelToNameElementCommands, # entries: {0}"; + private static final String ENTRIES_EXCEL_TO_STRUCTURE_ELEMENT_COMMANDS = "excelToStructureElementCommands, # entries: {0}"; + private static final String ENTRIES_NAME_ELEMENTS_TO_EXCEL = "nameElementsToExcel, # entries: {0}"; + private static final String ENTRIES_STRUCTURE_ELEMENTS_TO_EXCEL = "structureElementsToExcel, # entries: {0}"; + + private static final String FAILED_TO_EXPORT_VALUES_TO_FILE = "Failed to export values to file"; + private static final String FILE_COULD_NOT_BE_PARSED_FOR_VALUE_AT_ROW_CELL = "File could not be parsed for value at row: {0} cell: {1}"; + private static final String INVALID_SHEET_EXPECTED_SHEET = "Invalid sheet. Expected sheet: {0}"; + private static final String INVALID_VALUE_EXPECTED_VALUE = "Invalid value: {0} Expected value: {1}"; + private static final String INVALID_VALUE_UNEXPECTED_VALUE = "Invalid value: {0} Unexpected value."; + + /** + * This class is not to be instantiated. + */ + private ExcelUtil() { + throw new IllegalStateException("Utility class"); + } + + /** + * Utility method to check if file has Excel format. + * + * @param file file + * @return boolean if file has Excel format + */ + public static boolean hasExcelFormat(MultipartFile file) { + return MIME_TYPE_OPENXML_SPREADSHEET.equals(file.getContentType()); + } + + /** + * Utility method to convert an Excel file to a list of name elements. + * + * @param is input stream + * @return list of name elements + */ + public static List<NameElementCommand> excelToNameElementCommands(InputStream is) { + // rules for conversion + // Excel as NameElementCommand accepted + // Excel as NameElement not accepted + // see validateHeaderNameElementCommand + + int rowIndex = 0; + int columnIndex = 0; + + try { + Workbook workbook = new XSSFWorkbook(is); + validateSheet(workbook); + + Sheet sheet = workbook.getSheet(SHEET); + Iterator<Row> rows = sheet.iterator(); + List<NameElementCommand> list = Lists.newArrayList(); + + // iterate over rows and columns (cells) + while (rows.hasNext()) { + Row row = rows.next(); + Iterator<Cell> cells = row.iterator(); + NameElementCommand nameElementCommand = new NameElementCommand(); + + while (cells.hasNext()) { + Cell cell = cells.next(); + rowIndex = cell.getRowIndex(); + columnIndex = cell.getColumnIndex(); + + // ensure header row with syntax + if (rowIndex == 0) { + validateHeaderNameElementCommand(cell); + continue; + } + + String value = cell.getStringCellValue(); + switch (columnIndex) { + case 0: + nameElementCommand.setUuid(!StringUtils.isEmpty(value) ? UUID.fromString(value) : null); + break; + case 1: + nameElementCommand.setParentsystemstructure(!StringUtils.isEmpty(value) ? UUID.fromString(value) : null); + break; + case 2: + nameElementCommand.setParentdevicestructure(!StringUtils.isEmpty(value) ? UUID.fromString(value) : null); + break; + case 3: + nameElementCommand.setIndex(value); + break; + case 4: + nameElementCommand.setDescription(value); + break; + case 5: + nameElementCommand.setComment(value); + break; + default: + throw new IllegalArgumentException( + MessageFormat.format(INVALID_VALUE_UNEXPECTED_VALUE, value)); + } + } + // not add header row + if (rowIndex > 0) { + list.add(nameElementCommand); + } + } + + workbook.close(); + LOGGER.log(Level.INFO, ENTRIES_EXCEL_TO_NAME_ELEMENT_COMMANDS, list.size()); + return list; + } catch (IllegalArgumentException | IOException e) { + throw new ServiceException( + MessageFormat.format(FILE_COULD_NOT_BE_PARSED_FOR_VALUE_AT_ROW_CELL, rowIndex, columnIndex), + e.getMessage(), null, e); + } + } + + /** + * Utility method to convert an Excel file to a list of structure elements. + * + * @param is input stream + * @return list of structure elements + */ + public static List<StructureElementCommand> excelToStructureElementCommands(InputStream is) { + // rules for conversion + // Excel as StructureElementCommand accepted + // Excel as StructureElement not accepted + // see validateHeaderStructureElementCommand + + int rowIndex = 0; + int columnIndex = 0; + + try { + Workbook workbook = new XSSFWorkbook(is); + validateSheet(workbook); + + Sheet sheet = workbook.getSheet(SHEET); + Iterator<Row> rows = sheet.iterator(); + List<StructureElementCommand> list = Lists.newArrayList(); + + // iterate over rows and columns (cells) + while (rows.hasNext()) { + Row row = rows.next(); + Iterator<Cell> cells = row.iterator(); + StructureElementCommand structureElementCommand = new StructureElementCommand(); + + while (cells.hasNext()) { + Cell cell = cells.next(); + rowIndex = cell.getRowIndex(); + columnIndex = cell.getColumnIndex(); + + // ensure header row with syntax + if (rowIndex == 0) { + validateHeaderStructureElementCommand(cell); + continue; + } + + String value = cell.getStringCellValue(); + switch (columnIndex) { + case 0: + structureElementCommand.setUuid(!StringUtils.isEmpty(value) ? UUID.fromString(value) : null); + break; + case 1: + structureElementCommand.setType(Type.valueOf(value)); + break; + case 2: + structureElementCommand.setParent(!StringUtils.isEmpty(value) ? UUID.fromString(value) : null); + break; + case 3: + structureElementCommand.setName(value); + break; + case 4: + structureElementCommand.setMnemonic(value); + break; + case 5: + structureElementCommand.setDescription(value); + break; + case 6: + structureElementCommand.setComment(value); + break; + default: + throw new IllegalArgumentException( + MessageFormat.format(INVALID_VALUE_UNEXPECTED_VALUE, value)); + } + + } + // not add header row + if (rowIndex > 0) { + list.add(structureElementCommand); + } + } + + workbook.close(); + LOGGER.log(Level.INFO, ENTRIES_EXCEL_TO_STRUCTURE_ELEMENT_COMMANDS, list.size()); + return list; + } catch (IllegalArgumentException | IOException e) { + throw new ServiceException( + MessageFormat.format(FILE_COULD_NOT_BE_PARSED_FOR_VALUE_AT_ROW_CELL, rowIndex, columnIndex), + e.getMessage(), null, e); + } + } + + /** + * Validate that workbook has sheet with expected name. + * + * @param workbook workbook + */ + private static void validateSheet(Workbook workbook) { + if (workbook.getSheet(SHEET) == null) { + throw new IllegalArgumentException(MessageFormat.format(INVALID_SHEET_EXPECTED_SHEET, SHEET)); + } + } + + /** + * Validate that cell is a proper header cell for a name element. + * + * @param cell cell + */ + private static void validateHeaderNameElementCommand(Cell cell) { + // validate that columns are NameElementCommand + // nothing less or more + // not have (cell.getColumnIndex() >= NAMEELEMENTCOMMAND_LENGTH) to allow NameElement + + if (cell.getColumnIndex() < NAMEELEMENTCOMMAND_LENGTH + && !NAMEELEMENT_HEADER_COMMENT[cell.getColumnIndex()][0].equals(cell.getStringCellValue())) { + throw new IllegalArgumentException( + MessageFormat.format( + INVALID_VALUE_EXPECTED_VALUE, + cell.getStringCellValue(), + NAMEELEMENT_HEADER_COMMENT[cell.getColumnIndex()][0])); + } else if (cell.getColumnIndex() >= NAMEELEMENTCOMMAND_LENGTH) { + throw new IllegalArgumentException( + MessageFormat.format(INVALID_VALUE_UNEXPECTED_VALUE, cell.getStringCellValue())); + } + } + + /** + * Validate that cell is a proper header cell for a structure element. + * + * @param cell cell + */ + private static void validateHeaderStructureElementCommand(Cell cell) { + // validate that columns are StructureElementCommand + // nothing less or more + // not have (cell.getColumnIndex() >= STRUCTUREELEMENTCOMMAND_LENGTH) to allow StructureElement + + if (cell.getColumnIndex() < STRUCTUREELEMENTCOMMAND_LENGTH + && !STRUCTUREELEMENT_HEADER_COMMENT[cell.getColumnIndex()][0].equals(cell.getStringCellValue())) { + throw new IllegalArgumentException( + MessageFormat.format( + INVALID_VALUE_EXPECTED_VALUE, + cell.getStringCellValue(), + STRUCTUREELEMENT_HEADER_COMMENT[cell.getColumnIndex()][0])); + } else if (cell.getColumnIndex() >= STRUCTUREELEMENTCOMMAND_LENGTH) { + throw new IllegalArgumentException( + MessageFormat.format(INVALID_VALUE_UNEXPECTED_VALUE, cell.getStringCellValue())); + } + } + + /** + * Utility method to convert a list of name elements to an Excel file. + * + * @param nameElements name elements + * @return Excel file + */ + public static ByteArrayInputStream nameElementsToExcel(ResponsePageNameElements nameElements) { + return nameElementsToExcel(nameElements.getList()); + } + + /** + * Utility method to convert a list of name elements to an Excel file. + * + * @param nameElements name elements + * @return Excel file + */ + public static ByteArrayInputStream nameElementsToExcel(List<NameElement> nameElements) { + LOGGER.log(Level.INFO, ENTRIES_NAME_ELEMENTS_TO_EXCEL, nameElements.size()); + try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream();) { + Sheet sheet = workbook.createSheet(SHEET); + + // prepare header + Drawing<?> drawing = sheet.createDrawingPatriarch(); + CreationHelper factory = workbook.getCreationHelper(); + ClientAnchor anchor = factory.createClientAnchor(); + DataFormat format = workbook.createDataFormat(); + short textFormat = format.getFormat("text"); + Font headerFont = workbook.createFont(); + headerFont.setBold(true); + + // header + Row headerRow = sheet.createRow(0); + for (int columnIndex = 0; columnIndex < NAMEELEMENT_HEADER_COMMENT.length; columnIndex++) { + Cell cell = headerRow.createCell(columnIndex); + + // value + cell.setCellValue(NAMEELEMENT_HEADER_COMMENT[columnIndex][0]); + + // width + sheet.setColumnWidth(columnIndex, SHEET_COLUMN_WIDTH); + + // style + CellStyle cellStyle = cell.getCellStyle(); + cellStyle.setDataFormat(textFormat); + cellStyle.setFont(headerFont); + cell.setCellStyle(cellStyle); + + // comment + anchor.setRow1(headerRow.getRowNum()); + anchor.setRow2(headerRow.getRowNum()+3); + anchor.setCol1(cell.getColumnIndex()); + anchor.setCol2(cell.getColumnIndex()+1); + Comment cellComment = drawing.createCellComment(anchor); + RichTextString richTextString = factory.createRichTextString(NAMEELEMENT_HEADER_COMMENT[columnIndex][1]); + cellComment.setString(richTextString); + cellComment.setAuthor(TextUtil.NAMING); + cell.setCellComment(cellComment); + } + + // data + int rowIndex = 1; + for (NameElement nameElement : nameElements) { + Row row = sheet.createRow(rowIndex++); + row.createCell(0).setCellValue(nameElement.getUuid() != null ? nameElement.getUuid().toString() : null); + row.createCell(1).setCellValue(nameElement.getParentsystemstructure() != null ? nameElement.getParentsystemstructure().toString() : null); + row.createCell(2).setCellValue(nameElement.getParentdevicestructure() != null ? nameElement.getParentdevicestructure().toString() : null); + row.createCell(3).setCellValue(nameElement.getIndex()); + row.createCell(4).setCellValue(nameElement.getDescription()); + row.createCell(5).setCellValue(nameElement.getComment()); + row.createCell(6).setCellValue(nameElement.getSystemstructure()); + row.createCell(7).setCellValue(nameElement.getDevicestructure()); + row.createCell(8).setCellValue(nameElement.getName()); + row.createCell(9).setCellValue(nameElement.getStatus() != null ? nameElement.getStatus().toString() : null); + row.createCell(10).setCellValue(nameElement.isLatest()); + row.createCell(11).setCellValue(nameElement.isDeleted()); + row.createCell(12).setCellValue(DateFormat.getDateTimeInstance().format(nameElement.getWhen())); + row.createCell(13).setCellValue(nameElement.getWho()); + } + workbook.write(out); + return new ByteArrayInputStream(out.toByteArray()); + + } catch (IOException e) { + throw new ServiceException(FAILED_TO_EXPORT_VALUES_TO_FILE, e.getMessage(), null, e); + } + } + + /** + * Utility method to convert a list of structure elements to an Excel file. + * + * @param structureElements structure elements + * @return Excel file + */ + public static ByteArrayInputStream structureElementsToExcel(ResponsePageStructureElements structureElements) { + return structureElementsToExcel(structureElements.getList()); + } + + /** + * Utility method to convert a list of structure elements to an Excel file. + * + * @param structureElements structure elements + * @return Excel file + */ + public static ByteArrayInputStream structureElementsToExcel(List<StructureElement> structureElements) { + LOGGER.log(Level.INFO, ENTRIES_STRUCTURE_ELEMENTS_TO_EXCEL, structureElements.size()); + try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream();) { + Sheet sheet = workbook.createSheet(SHEET); + + // prepare header + Drawing<?> drawing = sheet.createDrawingPatriarch(); + CreationHelper factory = workbook.getCreationHelper(); + ClientAnchor anchor = factory.createClientAnchor(); + DataFormat format = workbook.createDataFormat(); + short textFormat = format.getFormat("text"); + Font headerFont = workbook.createFont(); + headerFont.setBold(true); + + // header + Row headerRow = sheet.createRow(0); + for (int columnIndex = 0; columnIndex < STRUCTUREELEMENT_HEADER_COMMENT.length; columnIndex++) { + Cell cell = headerRow.createCell(columnIndex); + + // value + cell.setCellValue(STRUCTUREELEMENT_HEADER_COMMENT[columnIndex][0]); + + // width + sheet.setColumnWidth(columnIndex, SHEET_COLUMN_WIDTH); + + // style + CellStyle cellStyle = cell.getCellStyle(); + cellStyle.setDataFormat(textFormat); + cellStyle.setFont(headerFont); + cell.setCellStyle(cellStyle); + + // comment + anchor.setRow1(headerRow.getRowNum()); + anchor.setRow2(headerRow.getRowNum()+3); + anchor.setCol1(cell.getColumnIndex()); + anchor.setCol2(cell.getColumnIndex()+1); + Comment cellComment = drawing.createCellComment(anchor); + RichTextString richTextString = factory.createRichTextString(STRUCTUREELEMENT_HEADER_COMMENT[columnIndex][1]); + cellComment.setString(richTextString); + cellComment.setAuthor(TextUtil.NAMING); + cell.setCellComment(cellComment); + } + + // data + int rowIndex = 1; + for (StructureElement structureElement : structureElements) { + Row row = sheet.createRow(rowIndex++); + row.createCell(0).setCellValue(structureElement.getUuid() != null ? structureElement.getUuid().toString() : null); + row.createCell(1).setCellValue(structureElement.getType() != null ? structureElement.getType().toString() : null); + row.createCell(2).setCellValue(structureElement.getParent() != null ? structureElement.getParent().toString() : null); + row.createCell(3).setCellValue(structureElement.getName()); + row.createCell(4).setCellValue(structureElement.getMnemonic()); + row.createCell(5).setCellValue(structureElement.getDescription()); + row.createCell(6).setCellValue(structureElement.getComment()); + row.createCell(7).setCellValue(structureElement.getMnemonicpath()); + row.createCell(8).setCellValue(structureElement.getLevel()); + row.createCell(9).setCellValue(structureElement.getStatus() != null ? structureElement.getStatus().toString() : null); + row.createCell(10).setCellValue(structureElement.isLatest()); + row.createCell(11).setCellValue(structureElement.isDeleted()); + row.createCell(12).setCellValue(DateFormat.getDateTimeInstance().format(structureElement.getWhen())); + row.createCell(13).setCellValue(structureElement.getWho()); + } + workbook.write(out); + return new ByteArrayInputStream(out.toByteArray()); + + } catch (IOException e) { + throw new ServiceException(FAILED_TO_EXPORT_VALUES_TO_FILE, e.getMessage(), null, e); + } + } + +} diff --git a/src/main/java/org/openepics/names/util/TextUtil.java b/src/main/java/org/openepics/names/util/TextUtil.java index 3701117c2ab036beee3c153f9ef9b5db69e4f7b4..47fd1efc4cd4810bc47760210972e2ad34458219 100644 --- a/src/main/java/org/openepics/names/util/TextUtil.java +++ b/src/main/java/org/openepics/names/util/TextUtil.java @@ -120,6 +120,8 @@ public class TextUtil { public static final String INDEX_IS_NOT_VALID = INDEX + SPACE + IS_NOT_VALID; public static final String MNEMONIC_IS_NOT_VALID = MNEMONIC + SPACE + IS_NOT_VALID; + public static final String FILE_COULD_NOT_BE_PARSED = "File could not be parsed"; + /** * This class is not to be instantiated. */ diff --git a/src/main/resources/application-docker.properties b/src/main/resources/application-docker.properties index 102c01b5f1bfdb7c5be10bbc7475bf2f1f8161be..272502d963f6af947fed1338d8d53989becacaf3 100644 --- a/src/main/resources/application-docker.properties +++ b/src/main/resources/application-docker.properties @@ -22,8 +22,9 @@ openapi.info.contact.url=https://jira.esss.lu.se/projects/NT/issues openapi.info.description=\ Perform Naming tasks Create Read Update Delete + Approve Cancel Reject \n\n \ Here are some useful links \n \ - - [CCDB ecosystem](https://confluence.esss.lu.se/display/SW/CCDB+ecosystem) \n \ - - [Introduction to Naming REST API](/pdfs/naming_rest_api_brief_introduction.pdf) + [Cheat Sheet](/pdfs/naming_rest_api_cheat_sheet.pdf) \n\n \ + - [Naming](https://confluence.esss.lu.se/display/SW/Naming+Tool%2C+Cable+DB%2C+CCDB%2C+IOC+Factory%2C+RBAC) in ICS Software toolchain \n \ + - [Introduction to Naming REST API](/pdfs/naming_rest_api_brief_introduction.pdf) + [Cheat Sheet](/pdfs/naming_rest_api_cheat_sheet.pdf) \n \ + - Excel templates for [names](/templates/NameElementCommand.xlsx) + [structures](/templates/StructureElementCommand.xlsx) \n\n \ Note \n \ - Observe which fields to use for operations client to server. \n \ - Obsolete values are not shown unless history is requested. \n \ diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 102c01b5f1bfdb7c5be10bbc7475bf2f1f8161be..272502d963f6af947fed1338d8d53989becacaf3 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -22,8 +22,9 @@ openapi.info.contact.url=https://jira.esss.lu.se/projects/NT/issues openapi.info.description=\ Perform Naming tasks Create Read Update Delete + Approve Cancel Reject \n\n \ Here are some useful links \n \ - - [CCDB ecosystem](https://confluence.esss.lu.se/display/SW/CCDB+ecosystem) \n \ - - [Introduction to Naming REST API](/pdfs/naming_rest_api_brief_introduction.pdf) + [Cheat Sheet](/pdfs/naming_rest_api_cheat_sheet.pdf) \n\n \ + - [Naming](https://confluence.esss.lu.se/display/SW/Naming+Tool%2C+Cable+DB%2C+CCDB%2C+IOC+Factory%2C+RBAC) in ICS Software toolchain \n \ + - [Introduction to Naming REST API](/pdfs/naming_rest_api_brief_introduction.pdf) + [Cheat Sheet](/pdfs/naming_rest_api_cheat_sheet.pdf) \n \ + - Excel templates for [names](/templates/NameElementCommand.xlsx) + [structures](/templates/StructureElementCommand.xlsx) \n\n \ Note \n \ - Observe which fields to use for operations client to server. \n \ - Obsolete values are not shown unless history is requested. \n \ diff --git a/src/main/resources/static/templates/NameElementCommand.xlsx b/src/main/resources/static/templates/NameElementCommand.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..04db6c54a4d35f2b81fd79e2827ba481bb54b4d5 Binary files /dev/null and b/src/main/resources/static/templates/NameElementCommand.xlsx differ diff --git a/src/main/resources/static/templates/StructureElementCommand.xlsx b/src/main/resources/static/templates/StructureElementCommand.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..95ac1775957aced5957b45ddbb8e1191b4687112 Binary files /dev/null and b/src/main/resources/static/templates/StructureElementCommand.xlsx differ