com.adobe.platform.operation.pdfops.CombineFilesOperation Maven / Gradle / Ivy
/*
* Copyright 2019 Adobe
* All Rights Reserved.
*
* NOTICE: Adobe permits you to use, modify, and distribute this file in
* accordance with the terms of the Adobe license agreement accompanying
* it. If you have received this file from a source other than Adobe,
* then your use, modification, or distribution of it requires the prior
* written permission of Adobe.
*/
package com.adobe.platform.operation.pdfops;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import com.adobe.platform.operation.ExecutionContext;
import com.adobe.platform.operation.internal.MediaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.adobe.platform.operation.Operation;
import com.adobe.platform.operation.exception.ServiceApiException;
import com.adobe.platform.operation.exception.ServiceUsageException;
import com.adobe.platform.operation.internal.ExtensionMediaTypeMapping;
import com.adobe.platform.operation.internal.FileRefImpl;
import com.adobe.platform.operation.internal.InternalExecutionContext;
import com.adobe.platform.operation.internal.cpf.dto.response.ContentAnalyzerResponse;
import com.adobe.platform.operation.internal.service.CombinePDFService;
import com.adobe.platform.operation.internal.api.FileDownloadApi;
import com.adobe.platform.operation.internal.exception.OperationException;
import com.adobe.platform.operation.internal.options.CombineOperationInput;
import com.adobe.platform.operation.internal.util.FileUtil;
import com.adobe.platform.operation.internal.util.PathUtil;
import com.adobe.platform.operation.internal.util.StringUtil;
import com.adobe.platform.operation.internal.util.ValidationUtil;
import com.adobe.platform.operation.io.FileRef;
import com.adobe.platform.operation.pdfops.options.PageRanges;
/**
* Combines multiple PDF files into a single PDF file. Allows specifying which pages of the source files to combine.
*
*
* Sample Usage:
*
{@code CombineFilesOperation combineFilesOperation = CombineFilesOperation.createNew();
* combineFilesOperation.addInput(FileRef.createFromLocalFile("~/Documents/combinePdfInput1.pdf",
* CombineFilesOperation.SupportedSourceFormat.PDF.getMediaType()));
* combineFilesOperation.addInput(FileRef.createFromLocalFile("~/Documents/combinePdfInput2.pdf",
* CombineFilesOperation.SupportedSourceFormat.PDF.getMediaType()));
* Credentials credentials = Credentials.serviceAccountCredentialsBuilder().fromFile("pdftools-api-credentials.json").build();
* FileRef result = combineFilesOperation.execute(ExecutionContext.create(credentials));
* result.saveAs("output/CombineFilesOutput.pdf");
* }
*/
public class CombineFilesOperation implements Operation {
private static final Logger LOGGER = LoggerFactory.getLogger(CombineFilesOperation.class);
/**
* Supported media types for this operation
*/
private static final Set SUPPORTED_SOURCE_MEDIA_TYPE =
new HashSet<>(Arrays.asList(ExtensionMediaTypeMapping.PDF.getMediaType()));
/**
* Field representing the extension of the operation result
*/
private static final String TARGET_FILE_EXTENSION = ExtensionMediaTypeMapping.PDF.getExtension();
/**
* Field to check if the operation instance was invoked more than once
*/
private boolean isInvoked = false;
private List filesToCombine;
private CombineFilesOperation() {
this.filesToCombine = new ArrayList<>();
}
/**
* Constructs a {@code CombineFilesOperation} instance.
*
* @return a new {@code CombineFilesOperation} instance
*/
public static CombineFilesOperation createNew() {
return new CombineFilesOperation();
}
/**
* Specifies a PDF file (media type "application/pdf") to be combined with other files. All pages of this file will be
* added after the pages of any previously specified files.
*
* For adding particular pages of a PDF file, use {@link #addInput(FileRef, PageRanges)}.
*
* @param sourceFileRef a PDF file to be combined
*/
public void addInput(FileRef sourceFileRef) {
Objects.requireNonNull(sourceFileRef, "No input was set for operation");
PageRanges pageRanges = new PageRanges();
pageRanges.addAll();
filesToCombine.add(CombineOperationInput.createNew((FileRefImpl) sourceFileRef, pageRanges));
}
/**
* Specifies particular pages of a PDF file (media type "application/pdf") to be combined with other files. The pages will be added after
* the pages of any previously specified files.
*
* For adding all the pages of the file, consider {@link #addInput(FileRef)}.
*
* @param sourceFileRef a PDF file
* @param pageRanges page ranges of the PDF file to be combined
*/
public void addInput(FileRef sourceFileRef, PageRanges pageRanges) {
Objects.requireNonNull(sourceFileRef, "No input was set for operation");
Objects.requireNonNull(pageRanges, "No page options was provided for input file");
filesToCombine.add(CombineOperationInput.createNew((FileRefImpl) sourceFileRef, pageRanges));
}
/**
* Executes this operation synchronously using the supplied context and returns a new FileRef instance for the resulting file.
*
* The resulting file may be stored in the system temporary directory (per java.io.tmpdir System property).
* See {@link FileRef} for how temporary resources are cleaned up.
*
* @param context the context in which to execute the operation
* @return the resulting PDF file
* @throws ServiceApiException if an API call results in an error response
* @throws IOException if there is an error in reading either the input source or the resulting file
* @throws ServiceUsageException if service usage limits have been reached or credentials quota has been exhausted.
*/
@Override
public FileRef execute(ExecutionContext context) throws ServiceApiException, IOException, ServiceUsageException {
validateInvocationCount();
InternalExecutionContext internalExecutionContext = (InternalExecutionContext) context;
this.validate(internalExecutionContext);
try {
LOGGER.info("All validations successfully done. Beginning Combine operation execution");
long startTimeMs = System.currentTimeMillis();
String location = CombinePDFService.combinePdf(internalExecutionContext, filesToCombine, this.getClass().getSimpleName());
String targetFileName = FileUtil.getRandomFileName(TARGET_FILE_EXTENSION);
String temporaryDestinationPath = PathUtil.getTemporaryDestinationPath(targetFileName, TARGET_FILE_EXTENSION);
FileDownloadApi.downloadAndSave(internalExecutionContext, location, temporaryDestinationPath, ContentAnalyzerResponse.class);
LOGGER.info("Operation successfully completed. Stored Combined PDF at {}", temporaryDestinationPath);
LOGGER.debug("Operation Success Info - Request ID: {}, Latency(ms): {}",
StringUtil.getRequestIdFromLocation(location), System.currentTimeMillis() - startTimeMs);
isInvoked = true;
return FileRef.createFromLocalFile(temporaryDestinationPath);
} catch (OperationException oe) {
throw new ServiceApiException(oe.getErrorMessage(), oe.getRequestTrackingId(), oe.getStatusCode());
}
}
private void validateInvocationCount() {
if (isInvoked) {
LOGGER.error("Operation instance must only be invoked once");
throw new IllegalStateException("Operation instance must not be reused, can only be invoked once");
}
}
private void validate(InternalExecutionContext context) {
ValidationUtil.validateExecutionContext(context);
if (filesToCombine.isEmpty()) {
throw new IllegalArgumentException("No input was provided for combining files");
}
filesToCombine.forEach(file -> {
ValidationUtil.validateFileWithPageOptions(file, SUPPORTED_SOURCE_MEDIA_TYPE);
});
if (filesToCombine.stream().map(CombineOperationInput::getSourceFileRef).distinct().count() > 20) {
throw new IllegalArgumentException("Only 20 unique input files can be combined in one combine operation instance");
}
}
/**
* Supported source file formats for {@link CombineFilesOperation}.
*/
public enum SupportedSourceFormat implements MediaType {
/**
* Represents "application/pdf" media type
*/
PDF;
/**
* Returns the corresponding media type for this format, intended to be used for {@code mediaType} parameter in
* {@link FileRef} methods.
*
* @return the corresponding media type
*/
public String getMediaType() {
return ExtensionMediaTypeMapping.valueOf(name()).getMediaType().toLowerCase();
}
}
}