| /******************************************************************************** |
| * Copyright (c) 2015-2018 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.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); |
| } |
| } |
| } |
| } |