Skip to content

Commit 24bb96d

Browse files
authored
Merge pull request #23 from kit-data-manager/development
PR for v1.0.1
2 parents b1796c1 + 6b972bd commit 24bb96d

File tree

8 files changed

+194
-85
lines changed

8 files changed

+194
-85
lines changed

CHANGELOG.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,16 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7-
## [Unreleased]
7+
##[Unreleased]
8+
9+
## [1.0.1]
10+
### New Features
11+
- Simple alert is shown if mapping fails to provide some feedback
12+
13+
### Fixed
14+
- No more empty downloads in GUI
15+
- User input and result data for mapping execution gets now reliably removed in case of success and error
16+
- Fixed pythonExecutable config variable name and default value
817

918
## [1.0.0] - date 2023-06-09
1019
### New Features
@@ -34,7 +43,8 @@ and mapping of metadata documents delivered by RabbitMQ
3443
- Mapping of metadata documents with Gemma
3544
- Ingest to elasticsearch
3645

37-
[Unreleased]: https://github.com/kit-data-manager/indexing-service/compare/v1.0.0...HEAD
46+
[Unreleased]: https://github.com/kit-data-manager/indexing-service/compare/v1.0.1...HEAD
47+
[1.0.1]: https://github.com/kit-data-manager/indexing-service/compare/v1.0.0...v1.0.1
3848
[1.0.0]: https://github.com/kit-data-manager/indexing-service/compare/v0.0.4...v1.0.0
3949
[0.0.4]: https://github.com/kit-data-manager/indexing-service/compare/v0.0.3...v0.0.4
4050
[0.0.3]: https://github.com/kit-data-manager/indexing-service/compare/v0.0.2...v0.0.3

config/application.default.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ management.endpoints.web.exposure.include=*
2727
# Database
2828
##################################################
2929
spring.datasource.driver-class-name=org.h2.Driver
30-
spring.datasource.url=jdbc:h2:file:e:/tmp/mapping-service/database
30+
spring.datasource.url=jdbc:h2:file:/tmp/mapping-service/database
3131
spring.datasource.username=user
3232
spring.datasource.password=password
3333
spring.jpa.hibernate.ddl-auto=update
@@ -36,7 +36,7 @@ spring.jpa.hibernate.ddl-auto=update
3636
# Mapping-Service specific settings
3737
##################################################
3838
# Absolute path to the local python interpreter.
39-
mapping-service.pythonLocation=${pythonLocation:'file:///usr/bin/python3'}
39+
mapping-service.pythonExecutable=file:///usr/bin/python3
4040
# Absolute path to the folder where all plugins are located.
4141
mapping-service.pluginLocation=file:///${user.dir}/plugins
4242
# Absolute path to the local gemma mappings folder.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2022 Karlsruhe Institute of Technology.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package edu.kit.datamanager.mappingservice.exception;
17+
18+
import org.springframework.http.HttpStatus;
19+
import org.springframework.web.bind.annotation.ResponseStatus;
20+
21+
/**
22+
* Invalid json format of data.
23+
*/
24+
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
25+
public class MappingExecutionException extends RuntimeException {
26+
27+
/**
28+
* Default constructor.
29+
*/
30+
public MappingExecutionException() {
31+
super();
32+
}
33+
34+
/**
35+
* Constructor with given message and cause.
36+
*
37+
* @param message Message.
38+
* @param cause Cause.
39+
*/
40+
public MappingExecutionException(String message, Throwable cause) {
41+
super(message, cause);
42+
}
43+
44+
/**
45+
* Constructor with given message.
46+
*
47+
* @param message Message.
48+
*/
49+
public MappingExecutionException(String message) {
50+
super(message);
51+
}
52+
53+
/**
54+
* Constructor with given message and cause.
55+
*
56+
* @param cause Cause.
57+
*/
58+
public MappingExecutionException(Throwable cause) {
59+
super(cause);
60+
}
61+
}

src/main/java/edu/kit/datamanager/mappingservice/rest/IMappingExecutionController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public interface IMappingExecutionController {
5353

5454
@RequestMapping(value = {"/{mappingID}"}, method = {RequestMethod.POST}, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
5555
@ResponseBody
56-
ResponseEntity mapDocument(
56+
void mapDocument(
5757
@Parameter(description = "The document to be mapped.", required = true) @RequestPart(name = "document") final MultipartFile document,
5858
@Parameter(description = "The mappingID of the already defined mapping.", required = true) @PathVariable(value = "mappingID") String mappingID,
5959
final HttpServletRequest request,

src/main/java/edu/kit/datamanager/mappingservice/rest/impl/MappingExecutionController.java

Lines changed: 102 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
import edu.kit.datamanager.mappingservice.dao.IMappingRecordDao;
1919
import edu.kit.datamanager.mappingservice.domain.MappingRecord;
20+
import edu.kit.datamanager.mappingservice.exception.MappingException;
21+
import edu.kit.datamanager.mappingservice.exception.MappingExecutionException;
22+
import edu.kit.datamanager.mappingservice.exception.MappingNotFoundException;
2023
import edu.kit.datamanager.mappingservice.impl.MappingService;
2124
import edu.kit.datamanager.mappingservice.plugins.MappingPluginException;
2225
import edu.kit.datamanager.mappingservice.plugins.MappingPluginState;
@@ -38,8 +41,11 @@
3841
import javax.servlet.http.HttpServletResponse;
3942
import java.io.File;
4043
import java.io.IOException;
44+
import java.net.URI;
4145
import java.nio.file.Files;
4246
import java.nio.file.Path;
47+
import java.nio.file.Paths;
48+
import java.nio.file.StandardCopyOption;
4349
import java.util.Optional;
4450

4551
/**
@@ -49,86 +55,109 @@
4955
*/
5056
@Controller
5157
@RequestMapping(value = "/api/v1/mappingExecution")
52-
public class MappingExecutionController implements IMappingExecutionController {
58+
public class MappingExecutionController implements IMappingExecutionController{
5359

54-
private static final Logger LOG = LoggerFactory.getLogger(MappingExecutionController.class);
60+
private static final Logger LOG = LoggerFactory.getLogger(MappingExecutionController.class);
5561

56-
private final MappingService mappingService;
62+
private final MappingService mappingService;
5763

58-
private final IMappingRecordDao mappingRecordDao;
64+
private final IMappingRecordDao mappingRecordDao;
5965

60-
public MappingExecutionController(MappingService mappingService, IMappingRecordDao mappingRecordDao) {
61-
this.mappingService = mappingService;
62-
this.mappingRecordDao = mappingRecordDao;
63-
}
66+
public MappingExecutionController(MappingService mappingService, IMappingRecordDao mappingRecordDao){
67+
this.mappingService = mappingService;
68+
this.mappingRecordDao = mappingRecordDao;
69+
}
6470

65-
@Override
66-
public ResponseEntity mapDocument(MultipartFile document, String mappingID, HttpServletRequest request, HttpServletResponse response, UriComponentsBuilder uriBuilder) {
67-
LOG.trace("Performing mapDocument(File#{}, {})", document.getOriginalFilename(), mappingID);
68-
69-
Optional<Path> resultPath;
70-
if (!document.isEmpty() && !mappingID.isBlank()) {
71-
LOG.trace("Obtaining mapping for id {}.", mappingID);
72-
Optional<MappingRecord> record = mappingRecordDao.findByMappingId(mappingID);
73-
if (record.isEmpty()) {
74-
String message = String.format("No mapping found for mapping id %s.", mappingID);
75-
LOG.error(message + " Returning HTTP 404.");
76-
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(message);
77-
}
78-
79-
LOG.trace("Receiving mapping input file.");
80-
String extension = "." + FilenameUtils.getExtension(document.getOriginalFilename());
81-
LOG.trace("Found file extension: {}", extension);
82-
Path inputPath = FileUtil.createTempFile("inputMultipart", extension);
83-
LOG.trace("Writing user upload to: {}", inputPath);
84-
File inputFile = inputPath.toFile();
85-
try {
86-
document.transferTo(inputFile);
87-
LOG.trace("Successfully received user upload.");
88-
} catch (IOException e) {
89-
LOG.error("Failed to receive upload from user.", e);
90-
return ResponseEntity.internalServerError().body("Unable to write user upload to disk.");
91-
}
92-
93-
try {
94-
LOG.trace("Performing mapping process of file {} via mapping service", inputPath.toString());
95-
96-
resultPath = mappingService.executeMapping(inputFile.toURI(), mappingID);
97-
if (resultPath.isPresent()) {
98-
LOG.trace("Mapping process finished. Output written to {}.", resultPath.toString());
99-
} else {
100-
throw new MappingPluginException(MappingPluginState.UNKNOWN_ERROR, "Mapping process finished, but no result was returned.");
101-
}
102-
} catch (MappingPluginException e) {
103-
LOG.error("Failed to execute mapping.", e);
104-
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to execute mapping with id " + mappingID + " on provided input document.");
105-
}
106-
LOG.trace("Removing user upload at {}.", inputFile);
107-
FileUtil.removeFile(inputPath);
108-
LOG.trace("User upload successfully removed.");
109-
} else {
110-
String message = "Either mapping id or input document are missing. Unable to perform mapping.";
111-
LOG.error(message);
112-
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(message);
113-
}
71+
@Override
72+
public void mapDocument(MultipartFile document, String mappingID, HttpServletRequest request, HttpServletResponse response, UriComponentsBuilder uriBuilder){
73+
LOG.trace("Performing mapDocument(File#{}, {})", document.getOriginalFilename(), mappingID);
74+
75+
Optional<Path> resultPath;
76+
if(!document.isEmpty() && !mappingID.isBlank()){
77+
LOG.trace("Obtaining mapping for id {}.", mappingID);
78+
Optional<MappingRecord> record = mappingRecordDao.findByMappingId(mappingID);
79+
if(record.isEmpty()){
80+
String message = String.format("No mapping found for mapping id %s.", mappingID);
81+
LOG.error(message + " Returning HTTP 404.");
82+
throw new MappingNotFoundException(message);
83+
//return ResponseEntity.status(HttpStatus.NOT_FOUND).body(message);
84+
}
11485

115-
Path result = resultPath.get();
116-
if (!Files.exists(result) || !Files.isRegularFile(result) || !Files.isReadable(result)) {
117-
String message = "The mapping result expected at path " + result + " is not accessible. This indicates an error of the mapper implementation.";
118-
LOG.trace(message);
119-
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(message);
86+
LOG.trace("Receiving mapping input file.");
87+
String extension = "." + FilenameUtils.getExtension(document.getOriginalFilename());
88+
LOG.trace("Found file extension: {}", extension);
89+
Path inputPath = FileUtil.createTempFile("inputMultipart", extension);
90+
LOG.trace("Writing user upload to: {}", inputPath);
91+
File inputFile = inputPath.toFile();
92+
try{
93+
document.transferTo(inputFile);
94+
LOG.trace("Successfully received user upload.");
95+
} catch(IOException e){
96+
LOG.error("Failed to receive upload from user.", e);
97+
throw new MappingExecutionException("Unable to write user upload to disk.");
98+
}
99+
100+
try{
101+
LOG.trace("Performing mapping process of file {} via mapping service", inputPath.toString());
102+
103+
resultPath = mappingService.executeMapping(inputFile.toURI(), mappingID);
104+
if(resultPath.isPresent()){
105+
LOG.trace("Mapping process finished. Output written to {}.", resultPath.toString());
106+
} else{
107+
throw new MappingPluginException(MappingPluginState.UNKNOWN_ERROR, "Mapping process finished, but no result was returned.");
120108
}
109+
} catch(MappingPluginException e){
110+
LOG.error("Failed to execute mapping.", e);
111+
throw new MappingExecutionException("Failed to execute mapping with id " + mappingID + " on provided input document.");
112+
} finally{
113+
LOG.trace("Removing user upload at {}.", inputFile);
114+
FileUtil.removeFile(inputPath);
115+
LOG.trace("User upload successfully removed.");
116+
}
117+
} else{
118+
String message = "Either mapping id or input document are missing. Unable to perform mapping.";
119+
LOG.error(message);
120+
throw new MappingException(message);
121+
}
122+
123+
Path result = resultPath.get();
124+
if(!Files.exists(result) || !Files.isRegularFile(result) || !Files.isReadable(result)){
125+
String message = "The mapping result expected at path " + result + " is not accessible. This indicates an error of the mapper implementation.";
126+
LOG.error(message);
127+
throw new MappingExecutionException(message);
128+
}
129+
130+
LOG.trace("Determining mime type for mapping result.");
131+
result = FileUtil.fixFileExtension(result);
132+
133+
String mimeType = FileUtil.getMimeType(result);
134+
LOG.trace("Determining file extension for mapping result.");
135+
String extension = FileUtil.getExtensionForMimeType(mimeType);
136+
137+
LOG.trace("Using mime type {} and extension {}.", mimeType, extension);
138+
139+
response.setStatus(HttpStatus.OK.value());
140+
response.setHeader("Content-Type", mimeType);
141+
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(result.toFile().length()));
142+
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
143+
response.setHeader("Pragma", "no-cache");
144+
response.setHeader("Expires", "0");
145+
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;" + "filename=result" + extension);
146+
try{
147+
Files.copy(result, response.getOutputStream());
121148

122-
LOG.trace("Determining mime type for mapping result.");
123-
String mimeType = FileUtil.getMimeType(result);
124-
LOG.trace("Determining file extension for mapping result.");
125-
String extension = FileUtil.getExtensionForMimeType(mimeType);
126-
127-
LOG.trace("Using mime type {} and extension {}.", mimeType, extension);
128-
return ResponseEntity.ok().
129-
header(HttpHeaders.CONTENT_LENGTH, String.valueOf(result.toFile().length())).
130-
header(HttpHeaders.CONTENT_TYPE, mimeType).
131-
header(HttpHeaders.CONTENT_DISPOSITION, String.valueOf("attachment;" + "result" + extension)).
132-
body(new FileSystemResource(result.toFile()));
149+
} catch(IOException ex){
150+
String message = "Failed to write mapping result file to stream.";
151+
LOG.error(message, ex);
152+
throw new MappingExecutionException(message);
153+
} finally{
154+
LOG.trace("Result file successfully transferred to client. Removing file {} from disk.", result);
155+
try{
156+
Files.delete(result);
157+
LOG.trace("Result file successfully removed.");
158+
} catch(IOException ignored){
159+
LOG.warn("Failed to remove result file. Please remove manually.");
160+
}
133161
}
162+
}
134163
}

src/main/resources/static/JS/mapDocument.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,18 @@ function map() {
2929

3030
const http = new XMLHttpRequest();
3131
http.open("POST", execUrl + id)
32-
http.send(formData)
32+
http.responseType = 'blob';
33+
3334
http.onload = () => {
34-
let content_dispo = http.getResponseHeader("content-disposition");
35-
let filename = content_dispo.substr(content_dispo.lastIndexOf(";") + 1);
36-
download(http.responseText, filename, http.getResponseHeader("content-type"));
35+
if(http.status === 200){
36+
let content_dispo = http.getResponseHeader("content-disposition");
37+
let filename = content_dispo.substr(content_dispo.lastIndexOf("=") + 1);
38+
let content_type =http.getResponseHeader("content-type");
39+
console.log("Downloading result to " + filename + " with content type " + content_type);
40+
download(http.response, filename, content_type);
41+
}else{
42+
alert("A remote mapping error occured. Please check server logs for details.")
43+
}
3744
document.getElementById("progress").hidden = true
3845
document.getElementById("downloadButton").hidden = true
3946
document.getElementById("submit").disabled = false
@@ -82,4 +89,6 @@ function map() {
8289
document.getElementById("errorMessage").textContent = "ERROR " + http.status + " (" + http.statusText + ") Please try later again."
8390
document.getElementById("errorMessage").hidden = false
8491
}
92+
93+
http.send(formData)
8594
}

src/test/java/edu/kit/datamanager/mappingservice/rest/impl/MappingExecutionControllerTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ void mapValidDocument() throws Exception {
139139
this.mockMvc.perform(MockMvcRequestBuilders.multipart(MAPPING_URL).file(mappingFile)).
140140
andDo(print()).
141141
andExpect(status().isOk()).
142-
andExpect(header().string("content-disposition", "attachment;result.txt")).andReturn();
142+
andExpect(header().string("content-disposition", "attachment;filename=result.txt")).andReturn();
143143
}
144144

145145
@Test
@@ -181,7 +181,7 @@ void mapWithInvalidID() throws Exception {
181181
this.mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/mappingExecution/xsfdfg").file(mappingFile)).
182182
andDo(print()).
183183
andExpect(status().isNotFound()).
184-
andExpect(content().string("No mapping found for mapping id xsfdfg.")).
184+
//andExpect(content().string("No mapping found for mapping id xsfdfg.")).
185185
andReturn();
186186
}
187187

src/test/resources/test-config/application-test.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# General Spring Boot Settings (do not change!)
1717
spring.main.allow-bean-definition-overriding=true
1818
spring.main.allow-circular-references=true
19-
server.port=8095
19+
#server.port=8095
2020
# Data transfer settings, e.g. transfer compression and multipart message size.
2121
# The properties max-file-size and max-request-size define the maximum size of files
2222
# transferred to and from the repository. Setting them to -1 removes all limits.

0 commit comments

Comments
 (0)