Skip to content

Commit

Permalink
Merge pull request #13 from cuipengfei/decode-parameters
Browse files Browse the repository at this point in the history
add tests for parameter decode
  • Loading branch information
sbraconnier authored Jan 14, 2024
2 parents f23349c + 902896c commit 6916d0e
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 204 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.jodconverter.core.DocumentConverter;
import org.jodconverter.core.document.DefaultDocumentFormatRegistry;
import org.jodconverter.core.document.DocumentFormat;
Expand All @@ -19,6 +14,7 @@
import org.jodconverter.local.LocalConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
Expand All @@ -31,6 +27,11 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
* Controller that will process conversion requests. The mapping is the same as LibreOffice Online
* (/lool/convert-to) so we can use the jodconverter-remote module to send request to this
Expand All @@ -41,214 +42,132 @@
@RequestMapping("/lool/convert-to")
public class ConverterController {

private static final Logger LOGGER = LoggerFactory.getLogger(ConverterController.class);

private static final String FILTER_DATA = "FilterData";
private static final String FILTER_DATA_PREFIX_PARAM = "fd";
private static final String LOAD_PROPERTIES_PREFIX_PARAM = "l";
private static final String LOAD_FILTER_DATA_PREFIX_PARAM =
LOAD_PROPERTIES_PREFIX_PARAM + FILTER_DATA_PREFIX_PARAM;
private static final String STORE_PROPERTIES_PREFIX_PARAM = "s";
private static final String STORE_FILTER_DATA_PREFIX_PARAM =
STORE_PROPERTIES_PREFIX_PARAM + FILTER_DATA_PREFIX_PARAM;

private final OfficeManager officeManager;

/**
* Creates a new controller.
*
* @param officeManager The manager used to execute conversions.
*/
public ConverterController(final OfficeManager officeManager) {
super();

this.officeManager = officeManager;
}

@Operation(
summary =
"Converts the incoming document to the specified format (provided as request param)"
+ " and returns the converted document.")
@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "Document converted successfully."),
@ApiResponse(
responseCode = "400",
description = "The input document or output format is missing."),
@ApiResponse(responseCode = "500", description = "An unexpected error occurred.")
})
@PostMapping(
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
/* default */ Object convertToUsingParam(
@Parameter(description = "The input document to convert.", required = true)
@RequestParam("data")
final MultipartFile inputFile,
@Parameter(
description = "The document format to convert the input document to.",
required = true)
@RequestParam(name = "format")
final String convertToFormat,
@Parameter(description = "The custom options to apply to the conversion.")
@RequestParam(required = false)
final Map<String, String> parameters) {

LOGGER.debug("convertUsingRequestParam > Converting file to {}", convertToFormat);
return convert(inputFile, convertToFormat, parameters);
}

@Operation(
summary =
"Converts the incoming document to the specified format (provided as path param)"
+ " and returns the converted document.")
@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "Document converted successfully."),
@ApiResponse(
responseCode = "400",
description = "The input document or output format is missing."),
@ApiResponse(responseCode = "500", description = "An unexpected error occurred.")
})
@PostMapping(
value = "/{format}",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
/* default */ Object convertToUsingPath(
@Parameter(description = "The input document to convert.", required = true)
@RequestParam("data")
final MultipartFile inputFile,
@Parameter(
description = "The document format to convert the input document to.",
required = true)
@PathVariable(name = "format")
final String convertToFormat,
@Parameter(description = "The custom options to apply to the conversion.")
@RequestParam(required = false)
final Map<String, String> parameters) {

LOGGER.debug("convertUsingPathVariable > Converting file to {}", convertToFormat);
return convert(inputFile, convertToFormat, parameters);
}

private void addProperty(
final String prefix,
final Map.Entry<String, String> param,
final Map<String, Object> properties) {

final String name = param.getKey().substring(prefix.length());
final String value = param.getValue();

if ("true".equalsIgnoreCase(value)) {
properties.put(name, Boolean.TRUE);
} else if ("false".equalsIgnoreCase(value)) {
properties.put(name, Boolean.FALSE);
} else {
try {
final int ival = Integer.parseInt(value);
properties.put(name, ival);
} catch (NumberFormatException nfe) {
properties.put(name, value);
}
}
}

private boolean addProperty(
final String key,
final String prefix,
final Map.Entry<String, String> param,
final Map<String, Object> properties) {
if (key.startsWith(prefix)) {
addProperty(prefix, param, properties);
return true;
}
return false;
}

private void decodeParameters(
final Map<String, String> parameters,
final Map<String, Object> loadProperties,
final Map<String, Object> storeProperties) {
private static final Logger LOGGER = LoggerFactory.getLogger(ConverterController.class);

if (parameters == null || parameters.isEmpty()) {
return;
}
private final OfficeManager officeManager;

final Map<String, Object> loadFilterDataProperties = new HashMap<>();
final Map<String, Object> storeFilterDataProperties = new HashMap<>();
for (final Map.Entry<String, String> param : parameters.entrySet()) {
final String key = param.getKey().toLowerCase(Locale.ROOT);
if (addProperty(key, LOAD_FILTER_DATA_PREFIX_PARAM, param, loadFilterDataProperties)) {
break;
}
if (addProperty(key, LOAD_PROPERTIES_PREFIX_PARAM, param, loadProperties)) {
break;
}
if (addProperty(key, STORE_FILTER_DATA_PREFIX_PARAM, param, storeFilterDataProperties)) {
break;
}
if (addProperty(key, STORE_PROPERTIES_PREFIX_PARAM, param, storeProperties)) {
break;
}
}
@Autowired
private ParameterDecoder parameterDecoder;

if (!loadFilterDataProperties.isEmpty()) {
loadProperties.put(FILTER_DATA, loadFilterDataProperties);
}
/**
* Creates a new controller.
*
* @param officeManager The manager used to execute conversions.
*/
public ConverterController(final OfficeManager officeManager) {
super();

if (!storeFilterDataProperties.isEmpty()) {
storeProperties.put(FILTER_DATA, storeFilterDataProperties);
this.officeManager = officeManager;
}
}

private ResponseEntity<Object> convert(
final MultipartFile inputFile,
final String outputFormat,
final Map<String, String> parameters) {

if (inputFile.isEmpty()) {
return ResponseEntity.badRequest().build();
@Operation(
summary =
"Converts the incoming document to the specified format (provided as request param)"
+ " and returns the converted document.")
@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "Document converted successfully."),
@ApiResponse(
responseCode = "400",
description = "The input document or output format is missing."),
@ApiResponse(responseCode = "500", description = "An unexpected error occurred.")
})
@PostMapping(
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
/* default */ Object convertToUsingParam(
@Parameter(description = "The input document to convert.", required = true)
@RequestParam("data") final MultipartFile inputFile,
@Parameter(
description = "The document format to convert the input document to.",
required = true)
@RequestParam(name = "format") final String convertToFormat,
@Parameter(description = "The custom options to apply to the conversion.")
@RequestParam(required = false) final Map<String, String> parameters) {

LOGGER.debug("convertUsingRequestParam > Converting file to {}", convertToFormat);
return convert(inputFile, convertToFormat, parameters);
}

if (StringUtils.isBlank(outputFormat)) {
return ResponseEntity.badRequest().build();
@Operation(
summary =
"Converts the incoming document to the specified format (provided as path param)"
+ " and returns the converted document.")
@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "Document converted successfully."),
@ApiResponse(
responseCode = "400",
description = "The input document or output format is missing."),
@ApiResponse(responseCode = "500", description = "An unexpected error occurred.")
})
@PostMapping(
value = "/{format}",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
/* default */ Object convertToUsingPath(
@Parameter(description = "The input document to convert.", required = true)
@RequestParam("data") final MultipartFile inputFile,
@Parameter(
description = "The document format to convert the input document to.",
required = true)
@PathVariable(name = "format") final String convertToFormat,
@Parameter(description = "The custom options to apply to the conversion.")
@RequestParam(required = false) final Map<String, String> parameters) {

LOGGER.debug("convertUsingPathVariable > Converting file to {}", convertToFormat);
return convert(inputFile, convertToFormat, parameters);
}

// Here, we could have a dedicated service that would convert document
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {

final DocumentFormat targetFormat =
DefaultDocumentFormatRegistry.getFormatByExtension(outputFormat);
Assert.notNull(targetFormat, "targetFormat must not be null");

// Decode the parameters to load and store properties.
final Map<String, Object> loadProperties =
new HashMap<>(LocalConverter.DEFAULT_LOAD_PROPERTIES);
final Map<String, Object> storeProperties = new HashMap<>();
decodeParameters(parameters, loadProperties, storeProperties);

// Create a converter with the properties.
final DocumentConverter converter =
LocalConverter.builder()
.officeManager(officeManager)
.loadProperties(loadProperties)
.storeProperties(storeProperties)
.build();

// Convert...
converter.convert(inputFile.getInputStream()).to(baos).as(targetFormat).execute();

final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(targetFormat.getMediaType()));
headers.add(
"Content-Disposition",
"attachment; filename="
+ FileUtils.getBaseName(inputFile.getOriginalFilename())
+ "."
+ targetFormat.getExtension());
return ResponseEntity.ok().headers(headers).body(baos.toByteArray());

} catch (OfficeException | IOException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex);
private ResponseEntity<Object> convert(
final MultipartFile inputFile,
final String outputFormat,
final Map<String, String> parameters) {

if (inputFile.isEmpty()) {
return ResponseEntity.badRequest().build();
}

if (StringUtils.isBlank(outputFormat)) {
return ResponseEntity.badRequest().build();
}

// Here, we could have a dedicated service that would convert document
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {

final DocumentFormat targetFormat =
DefaultDocumentFormatRegistry.getFormatByExtension(outputFormat);
Assert.notNull(targetFormat, "targetFormat must not be null");

// Decode the parameters to load and store properties.
final Map<String, Object> loadProperties =
new HashMap<>(LocalConverter.DEFAULT_LOAD_PROPERTIES);
final Map<String, Object> storeProperties = new HashMap<>();
parameterDecoder.decodeParameters(parameters, loadProperties, storeProperties);

// Create a converter with the properties.
final DocumentConverter converter =
LocalConverter.builder()
.officeManager(officeManager)
.loadProperties(loadProperties)
.storeProperties(storeProperties)
.build();

// Convert...
converter.convert(inputFile.getInputStream()).to(baos).as(targetFormat).execute();

final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(targetFormat.getMediaType()));
headers.add(
"Content-Disposition",
"attachment; filename="
+ FileUtils.getBaseName(inputFile.getOriginalFilename())
+ "."
+ targetFormat.getExtension());
return ResponseEntity.ok().headers(headers).body(baos.toByteArray());

} catch (OfficeException | IOException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex);
}
}
}
}
Loading

0 comments on commit 6916d0e

Please sign in to comment.