blob: 4eb141e3a949c5f83f3c7039887173a154c05dae [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2019 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
package org.eclipse.mdm.apicopy.boundary;
import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.REQUESTPARAM_SOURCENAME;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.ejb.EJB;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.eclipse.mdm.apicopy.control.ApiCopyException;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Provides endpoints for importing ATFX files to a specific datasource.
*
*/
@Tag(name = "Import")
@javax.ws.rs.Path("/environments/{" + REQUESTPARAM_SOURCENAME + "}/import")
public class ImportResource {
private static final Logger LOG = LoggerFactory.getLogger(ImportResource.class);
private static final String ATFX_EXTENSION = ".atfx";
@EJB
private ImportService importService;
/**
* Default public constructor for JAX-RS
*/
public ImportResource() {
}
/**
* Used to provide service for unit tests
*
* @param importService
*/
protected ImportResource(ImportService importService) {
this.importService = importService;
}
/**
* Imports an ATFX file. The ATFX file is provided in the body of the POST
* request. Note that, this method is not suitable for importing a ATFX file
* with accompanying binary files. In this case, use
* {@link ImportResource#importMultiPart(FormDataMultiPart, String)} or
* {@link ImportResource#importZip(InputStream, String)}
*
* @param atfxInputStream the content of the ATFX file
* @param targetSourceName name of the target datasource
* @return
*/
@POST
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_JSON)
public Response importXml(InputStream atfxInputStream,
@PathParam(REQUESTPARAM_SOURCENAME) String targetSourceName) {
return importService.importAtfx(tmpDir -> extractDataFromInputStream(tmpDir, atfxInputStream),
targetSourceName);
}
/**
* Imports an ATFX file. The ATFX file and its binary files are provided as
* multipart form data. The field name represents the original file name and the
* field value the contents of the file. The ATFX file is detected by its file
* extension 'atfx' or the media type 'application/xml'. Only one ATFX file can
* be set at a time.
*
* @param multiPart form data as described above
* @param targetSourceName name of the target datasource
* @return
*/
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
public Response importMultiPart(@Parameter(hidden = true) FormDataMultiPart multiPart,
@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
return importService.importAtfx(tmpDir -> extractDataFromMultiPart(tmpDir, multiPart), sourceName);
}
/**
* Imports an ATFX file. The ATFX file and its binary files are provided as a
* zip file. The ATFX file is detected by its file extension 'atfx'. Only one
* ATFX file can be included in the zip file.
*
* @param zipFile the ATFX file and its binary files as zip compressed
* {@link InputStream}
* @param targetSourceName name of the target datasource
* @return
*/
@POST
@Consumes("application/zip")
@Produces(MediaType.APPLICATION_JSON)
public Response importZip(InputStream zipFile, @PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
return importService.importAtfx(tmpDir -> extractDataFromZip(tmpDir, zipFile), sourceName);
}
/**
* Writes the provided {@link InputStream} into the folder <code>tmpDir</code>
* and returns the path to this file.
*
* @param tmpDir temporary folder
* @param atfxInputStream stream to be written into a file in tmpDir
* @return Path referencing the written file.
*/
private Path extractDataFromInputStream(Path tmpDir, InputStream atfxInputStream) {
try {
Path atfxFile = tmpDir.resolve("tmp.atfx");
;
writeToFile(atfxFile, atfxInputStream);
LOG.debug("Received ATFX file {}.", atfxFile);
return atfxFile;
} catch (IOException e) {
throw new ApiCopyException("Could not extract data from request body.", e);
}
}
/**
* Writes the files provided as multi part form data into the folder
* <code>tmpDir</code> and returns the path to the ATFX file.
*
* @param tmpDir temporary folder
* @param multiPart multi part form data with files
* @return Path referencing the extracted ATFX file.
* @throws ApiCopyException if there is no or more than one ATFX file is found
*/
private Path extractDataFromMultiPart(Path tmpDir, FormDataMultiPart multiPart) {
try {
if (multiPart.getBodyParts().isEmpty()) {
throw new ApiCopyException(
"MultiPart message does not contain any parts. Atleast an ATFX file is expected.");
}
Path atfxFile = null;
List<Path> binFiles = new ArrayList<>();
for (Entry<String, List<FormDataBodyPart>> entry : multiPart.getFields().entrySet()) {
if (entry.getValue().size() != 1) {
throw new ApiCopyException("Expecting exactly one part per field.");
}
String fileName = entry.getKey();
Path filePath = tmpDir.resolve(fileName);
FormDataBodyPart data = entry.getValue().get(0);
writeToFile(filePath, data.getEntityAs(InputStream.class));
if (fileName.toLowerCase().endsWith(ATFX_EXTENSION)
|| data.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
if (atfxFile == null) {
atfxFile = filePath;
} else {
throw new ApiCopyException("Expecting exactly one part with Content-Type '"
+ MediaType.APPLICATION_XML + "', but multiple parts were found.");
}
} else {
binFiles.add(filePath);
}
}
if (atfxFile == null) {
throw new ApiCopyException("Expecting exactly one part with Content-Type '" + MediaType.APPLICATION_XML
+ "', but zero parts were found.");
}
LOG.debug("Received ATFX file {} and binary files {}.", atfxFile, binFiles);
return atfxFile;
} catch (IOException e) {
throw new ApiCopyException("Could not extract data from multipart data.", e);
}
}
/**
* Extracts the zip archive provided by {@link InputStream} into the folder
* <code>tmpDir</code> and returns the path to the ATFX file.
*
* @param tmpDir temporary folder
* @param in zip file to be extracted into a file in tmpDir
* @return Path referencing the extracted ATFX file.
* @throws ApiCopyException if there is no or more than one ATFX file is found
*/
private Path extractDataFromZip(Path tmpDir, InputStream in) {
try {
Path atfxFile = null;
List<Path> binFiles = new ArrayList<>();
ZipInputStream zipIn = new ZipInputStream(in);
ZipEntry entry;
while ((entry = zipIn.getNextEntry()) != null) {
String fileName = entry.getName();
Path file = tmpDir.resolve(fileName);
writeToFile(file, zipIn);
if (fileName.endsWith(ATFX_EXTENSION)) {
if (atfxFile == null) {
atfxFile = file;
} else {
throw new ApiCopyException(
"Expecting exactly one file with extension 'atfx', but files were found.");
}
} else {
binFiles.add(file);
}
}
if (atfxFile == null) {
throw new ApiCopyException(
"Expecting exactly one file with extension 'atfx', but zero parts were found.");
}
LOG.debug("Received ATFX file {} and binary files {}.", atfxFile, binFiles);
return atfxFile;
} catch (IOException e) {
throw new ApiCopyException("Could not extract data from zip file.", e);
}
}
/**
* Writes inputstream to file
*
* @param file
* @param inputstream
* @throws IOException
*/
private void writeToFile(Path file, InputStream inputstream) throws IOException {
try (OutputStream out = new FileOutputStream(file.toFile())) {
int read = 0;
byte[] bytes = new byte[1024];
while ((read = inputstream.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
}
}
}