All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.fitko.fitconnect.api.domain.model.attachment.Attachment Maven / Gradle / Ivy

Go to download

Library that provides client access to the FIT-Connect api-endpoints for sending, subscribing and routing

There is a newer version: 2.3.5
Show newest version
package dev.fitko.fitconnect.api.domain.model.attachment;

import dev.fitko.fitconnect.api.config.chunking.AttachmentChunkingConfig;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Purpose;
import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType;
import dev.fitko.fitconnect.api.exceptions.client.FitConnectAttachmentException;
import dev.fitko.fitconnect.api.exceptions.client.FitConnectSenderException;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.UUID;

import static dev.fitko.fitconnect.api.config.chunking.AttachmentChunkingConfig.TEMP_BUFFERED_FILE_PREFIX;

/**
 * This class represents an attachment with data payload and some metadata. The data can come from two sources:
 * 
    *
  • a file path
  • *
  • in memory data stored in a byte array
  • *
* This is because we cannot buffer all data in memory for large attachments payloads. *

* For attachment payloads that won't fit into memory (java byte arrays have a max. size of 2^32-1 byte == 2 GB.) use: *
    *
  • {@link #fromLargeAttachment(Path, String)}
  • *
  • {@link #fromLargeAttachment(InputStream, String)}
  • *
* Use all other creator methods for data that can be stored in memory. *

* Large attachments with a file path will be chunked automatically. * For further chunking options see {@link AttachmentChunkingConfig} */ public final class Attachment { private final Path dataFile; private final byte[] inMemoryData; private UUID attachmentId; private final String fileName; private final String description; private final String mimeType; private final Purpose purpose; /** * Creates an attachment and reads the content from a given path into memory. * * @param filePath path of the attachment file * @param mimeType mime-type of the attachment * @throws FitConnectSenderException if the file path could not be read */ public static Attachment fromPath(final Path filePath, final String mimeType) throws FitConnectSenderException { return fromPath(filePath, mimeType, null, null); } /** * Creates an attachment and reads the content from a given path into memory. * * @param filePath path of the attachment file * @param mimeType mime-type of the attachment * @param fileName name of the attachment file * @param description description of the attachment file * @throws FitConnectSenderException if the file path could not be read */ public static Attachment fromPath(final Path filePath, final String mimeType, final String fileName, final String description) throws FitConnectSenderException { try { return new Attachment(Files.readAllBytes(filePath), mimeType, fileName, description, Purpose.ATTACHMENT); } catch (IOException e) { throw new FitConnectSenderException("Reading attachment from path '" + filePath + "' failed", e); } } /** * Creates an attachment and reads the content from an input-stream into memory. * * @param inputStream stream of the attachment data * @param mimeType mime type of the provided attachment data * @throws FitConnectSenderException if the input-stream could not be read */ public static Attachment fromInputStream(final InputStream inputStream, final String mimeType) throws FitConnectSenderException { return fromInputStream(inputStream, mimeType, null, null); } /** * Creates an attachment and reads the content from an input-stream into memory. * * @param inputStream stream of the attachment data * @param mimeType mime type of the provided attachment data * @param fileName name of the attachment file * @param description description of the attachment file * @throws FitConnectSenderException if the input-stream could not be read */ public static Attachment fromInputStream(final InputStream inputStream, final String mimeType, final String fileName, final String description) throws FitConnectSenderException { try { return new Attachment(inputStream.readAllBytes(), mimeType, fileName, description, Purpose.ATTACHMENT); } catch (IOException e) { throw new FitConnectSenderException(e.getMessage(), e); } } /** * Creates an attachment and reads the content from a byte-array into memory. * * @param content data of the attachment as byte[] * @param mimeType mime type of the provided attachment data */ public static Attachment fromByteArray(final byte[] content, final String mimeType) { return fromByteArray(content, mimeType, null, null); } /** * Creates an attachment and reads the content from a byte-array into memory. * * @param content data of the attachment as byte[] * @param fileName name of the attachment file * @param mimeType mime type of the provided attachment data * @param description description of the attachment file */ public static Attachment fromByteArray(final byte[] content, final String mimeType, final String fileName, final String description) { return new Attachment(content, mimeType, fileName, description, Purpose.ATTACHMENT); } /** * Creates an attachment and reads the content from a given string into memory. The content will be read with UTF-8 encoding. *

Note: If you don't use an SDK to retrieve the submission you may have to decode the string with UTF-8

* * @param content data of the attachment as byte[] * @param mimeType mime type of the provided attachment data * @deprecated This method is no longer acceptable since it can lead to character encoding issues when used incorrectly, e.g. when an entire file is passed as string. *

Use {@link Attachment#fromByteArray(byte[], String)}} instead. */ @Deprecated(since = "3.0.0", forRemoval = true) public static Attachment fromString(final String content, final String mimeType) { return fromString(content, mimeType, null, null); } /** * Creates an attachment and reads the content from a given string into memory. The content will be read with UTF-8 encoding. *

Note: If you don't use an SDK to retrieve the submission you may have to decode the string with UTF-8

* * @param content data of the attachment as string * @param fileName name of the attachment file * @param mimeType mime type of the provided attachment data * @param description description of the attachment file * @deprecated This method is no longer acceptable since it can lead to character encoding issues when used incorrectly, e.g. when an entire file is passed as string. *

Use {@link Attachment#fromByteArray(byte[], String)}} instead. */ @Deprecated(since = "3.0.0", forRemoval = true) public static Attachment fromString(final String content, final String mimeType, final String fileName, final String description) { return new Attachment(content.getBytes(StandardCharsets.UTF_8), mimeType, fileName, description, Purpose.ATTACHMENT); } /** * Creates an attachment for a file that does not fit into memory and will be stored as a file/path reference. * This type of attachment will be chunked automatically. * * @param filePath path of the attachment file * @param mimeType mime-type of the attachment * @throws FitConnectSenderException if the file path could not be read * @see AttachmentChunkingConfig */ public static Attachment fromLargeAttachment(final Path filePath, final String mimeType) throws FitConnectSenderException { return fromLargeAttachment(filePath, mimeType, null, null); } /** * Creates an attachment for a file that does not fit into memory and will be stored as a file/path reference. * This type of attachment will be chunked automatically. * * @param filePath path of the attachment file * @param mimeType mime-type of the attachment * @param fileName name of the attachment file * @param description description of the attachment file * @throws FitConnectSenderException if the file path could not be read * @see AttachmentChunkingConfig */ public static Attachment fromLargeAttachment(final Path filePath, final String mimeType, final String fileName, final String description) throws FitConnectSenderException { return new Attachment(filePath, mimeType, fileName, description, Purpose.ATTACHMENT); } /** * Creates an attachment for an input-stream that does not fit into memory and will be stored as a file/path reference. * This type of attachment will be chunked automatically. * * @param inputStream stream of the attachment file * @param mimeType mime-type of the attachment * @throws FitConnectSenderException if the stream could not be read or buffered in filesystem * @see AttachmentChunkingConfig */ public static Attachment fromLargeAttachment(final InputStream inputStream, final String mimeType) throws FitConnectSenderException { return fromLargeAttachment(inputStream, mimeType, null, null); } /** * Creates an attachment for an input-stream that does not fit into memory and will be stored as a file/path reference. * This type of attachment will be chunked automatically. * * @param inputStream stream of the attachment file * @param mimeType mime-type of the attachment * @param fileName name of the attachment file * @param description description of the attachment file * @throws FitConnectSenderException if the stream could not be read or buffered in filesystem * @see AttachmentChunkingConfig */ public static Attachment fromLargeAttachment(final InputStream inputStream, final String mimeType, final String fileName, final String description) throws FitConnectSenderException { final Path tempFile = bufferStreamToFileSystem(inputStream); return fromLargeAttachment(tempFile, mimeType, fileName, description); } /** * Creates an attachment for submission data that is too large to be transferred in the submission metadata. * This type of attachment will be chunked automatically. *

* HINT: Only one attachment with {@link Purpose#DATA} is allowed per submission! * * @param inputStream stream of the submission data * @param mimeType mime-type of the submission data (json/xml) * @throws FitConnectAttachmentException if the stream could not be read or buffered in filesystem */ public static Attachment fromSubmissionData(InputStream inputStream, MimeType mimeType) { final Path tempFile = bufferStreamToFileSystem(inputStream); var filename = "data." + mimeType.getExtension(); return new Attachment(null, null, tempFile, mimeType.value(), filename, "submission data as attachment", Purpose.DATA); } /** * Gets the attachment content for in-memory data and large attachment files as byte[]. * Be aware that large attachments might not fit into memory. Use {@link #getDataAsInputStream()} instead. * * @return byte array of the attachment content * @see #isInMemoryAttachment() * @see #isLargeAttachment() */ public byte[] getDataAsBytes() { if (isInMemoryAttachment()) { return inMemoryData; } try (FileInputStream fileInputStream = new FileInputStream(dataFile.toFile())) { return fileInputStream.readAllBytes(); } catch (final IOException e) { throw new FitConnectSenderException(e.getMessage(), e); } } /** * Gets the attachment content as input-stream. * * @return input-stream of the attachment content * @throws FitConnectAttachmentException if creating an input-stream from large attachment files fails */ public InputStream getDataAsInputStream() { if (isInMemoryAttachment()) { return new ByteArrayInputStream(inMemoryData); } try { return Files.newInputStream(dataFile); } catch (IOException e) { throw new FitConnectAttachmentException(e.getMessage(), e); } } /** * Gets the in-memory attachment content as string. *

* Be aware that the attachments data might not fit onto memory and use {@link #getDataAsInputStream()} instead. * * @param encoding charset the string should be encoded with * @return string of the attachments content. */ public String getDataAsString(final Charset encoding) { return new String(getDataAsBytes(), encoding); } /** * Gets the in-memory attachment content as string with a default UTF-8 encoding. *

* Be aware that the attachments data might not fit onto memory and use {@link #getDataAsInputStream()} instead. * * @return utf-8 encoded string of the attachments content. */ public String getDataAsString() { return getDataAsString(StandardCharsets.UTF_8); } /** * Gets the file path for large attachment data that is stored in file system. * * @return Path to the large attachment file */ public Path getLargeAttachmentFilePath() { return dataFile; } /** * Get the attachment id. * * @return attachment id as UUID */ public UUID getAttachmentId() { return attachmentId; } /** * Gets the filename of the attachment. This filed is optional so it might be null. * * @return filename as string, null if not present */ public String getFileName() { return fileName; } /** * Gets the description of the attachment. This filed is optional so it might be null. * * @return description as string, null if not present */ public String getDescription() { return description; } /** * Gets the mimetype of the attachments content. * * @return mimetype as string. */ public String getMimeType() { return mimeType; } /** * Gets the purpose of the attachment. * * @return {@link Purpose} . */ public Purpose getPurpose() { return purpose; } /** * Checks if the attachment payload is stored in memory or as a file path for large attachments. * * @return true | false */ public boolean isInMemoryAttachment() { return inMemoryData != null && dataFile == null; } /** * Checks if the attachment payload is stored as file and not in memory. * * @return true | false */ public boolean isLargeAttachment() { return !isInMemoryAttachment(); } private Attachment(final byte[] inMemoryData, final String mimeType, final String fileName, final String description, Purpose purpose) { this(null, inMemoryData, null, mimeType, fileName, description, purpose); } private Attachment(final Path dataFile, final String mimeType, final String fileName, final String description, Purpose purpose) { this(null, null, dataFile, mimeType, fileName, description, purpose); } public Attachment(UUID attachmentId, final byte[] inMemoryData, final Path dataFile, final String mimeType, final String fileName, final String description, Purpose purpose) { this.inMemoryData = inMemoryData; this.dataFile = dataFile; this.attachmentId = attachmentId; this.fileName = fileName != null ? getBaseNameFromPath(fileName) : UUID.randomUUID().toString(); // prevent maliciously injected filePaths this.mimeType = mimeType; this.description = description; this.purpose = purpose; } /** * Write an input-stream to a temp file path to be used as attachment payload. * * @param inputStream the stream that is written to the filesystem * @return a {@link Path} to the * @throws FitConnectAttachmentException if attachment stream could not be written to temp file * @see AttachmentChunkingConfig#TEMP_BUFFERED_FILE_PREFIX */ private static Path bufferStreamToFileSystem(InputStream inputStream) { try { final Path tempFile = Files.createTempFile(TEMP_BUFFERED_FILE_PREFIX, ".tmp"); try (OutputStream os = new FileOutputStream(tempFile.toFile())) { inputStream.transferTo(os); } return tempFile; } catch (IOException e) { throw new FitConnectAttachmentException(e.getMessage(), e); } } private static String getBaseNameFromPath(final String fileName) { try { return Path.of(fileName).getFileName().toString(); } catch (final InvalidPathException e) { throw new FitConnectSenderException("Reading filename '" + fileName + "' failed ", e); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy