mirror of
https://github.com/Alfresco/alfresco-transform-core.git
synced 2025-08-14 17:58:27 +00:00
ATS-175 : T-Engine code cleanup
This commit is contained in:
@@ -12,6 +12,8 @@
|
||||
package org.alfresco.transformer;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
|
||||
import org.alfresco.transformer.executors.LibreOfficeJavaExecutor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
||||
@@ -27,9 +29,16 @@ public class Application
|
||||
@Value("${container.name}")
|
||||
private String containerName;
|
||||
|
||||
@Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
||||
@Bean
|
||||
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
||||
return registry -> registry.config().commonTags("containerName", containerName);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LibreOfficeJavaExecutor javaExecutor()
|
||||
{
|
||||
return new LibreOfficeJavaExecutor();
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
|
@@ -11,19 +11,23 @@
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
|
||||
import static org.alfresco.transformer.fs.FileManager.createAttachment;
|
||||
import static org.alfresco.transformer.fs.FileManager.createSourceFile;
|
||||
import static org.alfresco.transformer.fs.FileManager.createTargetFile;
|
||||
import static org.alfresco.transformer.fs.FileManager.createTargetFileName;
|
||||
import static org.alfresco.transformer.logging.StandardMessages.ENTERPRISE_LICENCE;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.alfresco.transformer.executors.LibreOfficeJavaExecutor;
|
||||
import org.alfresco.transformer.logging.LogEntry;
|
||||
import org.alfresco.transformer.probes.ProbeTestTransform;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.artofsolving.jodconverter.OfficeDocumentConverter;
|
||||
import org.artofsolving.jodconverter.office.OfficeException;
|
||||
import org.artofsolving.jodconverter.office.OfficeManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -33,8 +37,6 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.sun.star.task.ErrorCodeIOException;
|
||||
|
||||
/**
|
||||
* Controller for the Docker based LibreOffice transformer.
|
||||
*
|
||||
@@ -59,86 +61,48 @@ import com.sun.star.task.ErrorCodeIOException;
|
||||
@Controller
|
||||
public class LibreOfficeController extends AbstractTransformerController
|
||||
{
|
||||
private static final String OFFICE_HOME = "/opt/libreoffice5.4";
|
||||
|
||||
private static final int JODCONVERTER_TRANSFORMATION_ERROR_CODE = 3088;
|
||||
|
||||
private JodConverter jodconverter;
|
||||
private static final Log logger = LogFactory.getLog(LibreOfficeController.class);
|
||||
|
||||
@Autowired
|
||||
public LibreOfficeController() throws Exception
|
||||
private LibreOfficeJavaExecutor javaExecutor;
|
||||
|
||||
@Autowired
|
||||
public LibreOfficeController()
|
||||
{
|
||||
logger = LogFactory.getLog(LibreOfficeController.class);
|
||||
logger.info("-------------------------------------------------------------------------------------------------------------------------------------------------------");
|
||||
logEnterpriseLicenseMessage();
|
||||
Arrays.stream(ENTERPRISE_LICENCE.split("\\n")).forEach(logger::info);
|
||||
logger.info("This transformer uses LibreOffice from The Document Foundation. See the license at https://www.libreoffice.org/download/license/ or in /libreoffice.txt");
|
||||
logger.info("-------------------------------------------------------------------------------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
private static JodConverter createJodConverter(Long taskExecutionTimeout)
|
||||
{
|
||||
String timeout = taskExecutionTimeout == null || taskExecutionTimeout <= 0 ? "120000" : taskExecutionTimeout.toString();
|
||||
|
||||
JodConverterSharedInstance jodconverter = new JodConverterSharedInstance();
|
||||
|
||||
jodconverter.setOfficeHome(OFFICE_HOME); // jodconverter.officeHome
|
||||
jodconverter.setMaxTasksPerProcess("200"); // jodconverter.maxTasksPerProcess
|
||||
jodconverter.setTaskExecutionTimeout(timeout); // jodconverter.maxTaskExecutionTimeout
|
||||
jodconverter.setTaskQueueTimeout("30000"); // jodconverter.taskQueueTimeout
|
||||
jodconverter.setConnectTimeout("28000"); // jodconverter.connectTimeout
|
||||
jodconverter.setPortNumbers("8100"); // jodconverter.portNumbers
|
||||
jodconverter.setTemplateProfileDir(""); // jodconverter.templateProfileDir
|
||||
jodconverter.setEnabled("true"); // jodconverter.enabled
|
||||
jodconverter.afterPropertiesSet();
|
||||
|
||||
return jodconverter;
|
||||
}
|
||||
|
||||
public void setJodConverter(JodConverter jodconverter)
|
||||
{
|
||||
this.jodconverter = jodconverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Jodconverter timeouts are per OfficeManager, so we would need multiple OfficeManagers if we
|
||||
* have different timeouts. Alfresco only has one. So we delay building it until the first request.
|
||||
* This was not done previously.
|
||||
*/
|
||||
private synchronized void setJodConverterOnFirstRequest(Long timeout)
|
||||
{
|
||||
if (jodconverter == null)
|
||||
{
|
||||
setJodConverter(createJodConverter(timeout));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTransformerName()
|
||||
public String getTransformerName()
|
||||
{
|
||||
return "LibreOffice";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String version()
|
||||
public String version()
|
||||
{
|
||||
return "LibreOffice available";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProbeTestTransform getProbeTestTransform()
|
||||
public ProbeTestTransform getProbeTestTransform()
|
||||
{
|
||||
// See the Javadoc on this method and Probes.md for the choice of these values.
|
||||
return new ProbeTestTransform(this, "quick.doc", "quick.pdf",
|
||||
return new ProbeTestTransform(this, logger, "quick.doc", "quick.pdf",
|
||||
11817, 1024, 150, 10240, 60*30+1, 60*15+20)
|
||||
{
|
||||
@Override
|
||||
protected void executeTransformCommand(File sourceFile, File targetFile)
|
||||
{
|
||||
LibreOfficeController.this.executeTransformCommand(sourceFile, targetFile, null);
|
||||
javaExecutor.call(sourceFile, targetFile);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//todo: the "timeout" request parameter is ignored; the timeout is preset at JodConverter creation
|
||||
@PostMapping(value = "/transform", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ResponseEntity<Resource> transform(HttpServletRequest request,
|
||||
@RequestParam("file") MultipartFile sourceMultipartFile,
|
||||
@@ -147,122 +111,25 @@ public class LibreOfficeController extends AbstractTransformerController
|
||||
@RequestParam(value = "testDelay", required = false) Long testDelay)
|
||||
{
|
||||
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
|
||||
getProbeTestTransform().incrementTransformerCount();
|
||||
File sourceFile = createSourceFile(request, sourceMultipartFile);
|
||||
File targetFile = createTargetFile(request, targetFilename);
|
||||
// Both files are deleted by TransformInterceptor.afterCompletion
|
||||
|
||||
executeTransformCommand(sourceFile, targetFile, timeout);
|
||||
javaExecutor.call(sourceFile, targetFile);
|
||||
|
||||
return createAttachment(targetFilename, targetFile, testDelay);
|
||||
final ResponseEntity<Resource> body = createAttachment(targetFilename, targetFile);
|
||||
LogEntry.setTargetSize(targetFile.length());
|
||||
long time = LogEntry.setStatusCodeAndMessage(200, "Success");
|
||||
time += LogEntry.addDelay(testDelay);
|
||||
getProbeTestTransform().recordTransformTime(time);
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processTransform(File sourceFile, File targetFile,
|
||||
public void processTransform(File sourceFile, File targetFile,
|
||||
Map<String, String> transformOptions, Long timeout)
|
||||
{
|
||||
executeTransformCommand(sourceFile, targetFile, timeout);
|
||||
}
|
||||
|
||||
protected void executeTransformCommand(File sourceFile, File targetFile, Long timeout)
|
||||
{
|
||||
timeout = timeout != null && timeout > 0 ? timeout : 0;
|
||||
|
||||
try
|
||||
{
|
||||
convert(sourceFile, targetFile, timeout);
|
||||
}
|
||||
catch (OfficeException e)
|
||||
{
|
||||
throw new TransformException(400, "LibreOffice server conversion failed: \n"+
|
||||
" from file: " + sourceFile + "\n" +
|
||||
" to file: " + targetFile,
|
||||
e);
|
||||
}
|
||||
catch (Throwable throwable)
|
||||
{
|
||||
// Because of the known bug with empty Spreadsheets in JodConverter try to catch exception and produce empty pdf file
|
||||
if (throwable.getCause() instanceof ErrorCodeIOException &&
|
||||
((ErrorCodeIOException) throwable.getCause()).ErrCode == JODCONVERTER_TRANSFORMATION_ERROR_CODE)
|
||||
{
|
||||
logger.warn("Transformation failed: \n" +
|
||||
"from file: " + sourceFile + "\n" +
|
||||
"to file: " + targetFile +
|
||||
"Source file " + sourceFile + " has no content");
|
||||
produceEmptyPdfFile(targetFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw throwable;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetFile.exists() || targetFile.length() == 0L)
|
||||
{
|
||||
throw new TransformException(500, "Transformer failed to create an output file");
|
||||
}
|
||||
}
|
||||
|
||||
void convert(File sourceFile, File targetFile, long timeout)
|
||||
{
|
||||
setJodConverterOnFirstRequest(timeout);
|
||||
OfficeManager officeManager = jodconverter.getOfficeManager();
|
||||
OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
|
||||
converter.convert(sourceFile, targetFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method produces an empty PDF file at the specified File location.
|
||||
* Apache's PDFBox is used to create the PDF file.
|
||||
*/
|
||||
private void produceEmptyPdfFile(File targetFile)
|
||||
{
|
||||
// If improvement PDFBOX-914 is incorporated, we can do this with a straight call to
|
||||
// org.apache.pdfbox.TextToPdf.createPDFFromText(new StringReader(""));
|
||||
// https://issues.apache.org/jira/browse/PDFBOX-914
|
||||
|
||||
PDDocument pdfDoc = null;
|
||||
PDPageContentStream contentStream = null;
|
||||
try
|
||||
{
|
||||
pdfDoc = new PDDocument();
|
||||
PDPage pdfPage = new PDPage();
|
||||
// Even though, we want an empty PDF, some libs (e.g. PDFRenderer) object to PDFs
|
||||
// that have literally nothing in them. So we'll put a content stream in it.
|
||||
contentStream = new PDPageContentStream(pdfDoc, pdfPage);
|
||||
pdfDoc.addPage(pdfPage);
|
||||
|
||||
// Now write the in-memory PDF document into the temporary file.
|
||||
pdfDoc.save(targetFile.getAbsolutePath());
|
||||
|
||||
}
|
||||
catch (IOException iox)
|
||||
{
|
||||
throw new TransformException(500, "Error creating empty PDF file", iox);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (contentStream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
contentStream.close();
|
||||
}
|
||||
catch (IOException ignored)
|
||||
{
|
||||
// Intentionally empty
|
||||
}
|
||||
}
|
||||
if (pdfDoc != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
pdfDoc.close();
|
||||
}
|
||||
catch (IOException ignored)
|
||||
{
|
||||
// Intentionally empty.
|
||||
}
|
||||
}
|
||||
}
|
||||
javaExecutor.call(sourceFile, targetFile);
|
||||
}
|
||||
}
|
||||
|
@@ -9,11 +9,11 @@
|
||||
* agreement is prohibited.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
package org.alfresco.transformer.executors;
|
||||
|
||||
import org.artofsolving.jodconverter.office.OfficeManager;
|
||||
|
||||
///////// THIS FILE IS A COPY OF THE CODE IN alfresco-repository /////////////
|
||||
///////// THIS FILE WAS A COPY OF THE CODE IN alfresco-repository /////////////
|
||||
|
||||
public interface JodConverter
|
||||
{
|
||||
@@ -21,11 +21,11 @@ public interface JodConverter
|
||||
* Gets the JodConverter OfficeManager.
|
||||
* @return
|
||||
*/
|
||||
public abstract OfficeManager getOfficeManager();
|
||||
OfficeManager getOfficeManager();
|
||||
|
||||
/**
|
||||
* This method returns a boolean indicating whether the JodConverter connection to OOo is available.
|
||||
* @return <code>true</code> if available, else <code>false</code>
|
||||
*/
|
||||
public abstract boolean isAvailable();
|
||||
boolean isAvailable();
|
||||
}
|
@@ -23,12 +23,11 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
package org.alfresco.transformer.executors;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FilenameFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
@@ -41,7 +40,7 @@ import org.artofsolving.jodconverter.office.OfficeManager;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
///////// THIS FILE IS A COPY OF THE CODE IN alfresco-repository /////////////
|
||||
///////// THIS FILE WAS A COPY OF THE CODE IN alfresco-repository /////////////
|
||||
|
||||
/**
|
||||
* Makes use of the JodConverter library and an installed
|
||||
@@ -51,10 +50,10 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
*/
|
||||
public class JodConverterSharedInstance implements InitializingBean, DisposableBean, JodConverter
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(JodConverterSharedInstance.class);
|
||||
private static final Log logger = LogFactory.getLog(JodConverterSharedInstance.class);
|
||||
|
||||
private OfficeManager officeManager;
|
||||
boolean isAvailable = false;
|
||||
private boolean isAvailable = false;
|
||||
|
||||
// JodConverter's built-in configuration settings.
|
||||
//
|
||||
@@ -82,7 +81,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
private Boolean deprecatedOooEnabled;
|
||||
private int[] deprecatedOooPortNumbers;
|
||||
|
||||
public void setMaxTasksPerProcess(String maxTasksPerProcess)
|
||||
void setMaxTasksPerProcess(String maxTasksPerProcess)
|
||||
{
|
||||
Long l = parseStringForLong(maxTasksPerProcess.trim());
|
||||
if (l != null)
|
||||
@@ -96,7 +95,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public void setOfficeHome(String officeHome)
|
||||
void setOfficeHome(String officeHome)
|
||||
{
|
||||
this.officeHome = officeHome == null ? "" : officeHome.trim();
|
||||
}
|
||||
@@ -106,7 +105,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
this.deprecatedOooExe = deprecatedOooExe == null ? "" : deprecatedOooExe.trim();
|
||||
}
|
||||
|
||||
public void setPortNumbers(String s)
|
||||
void setPortNumbers(String s)
|
||||
{
|
||||
portNumbers = parsePortNumbers(s, "jodconverter");
|
||||
}
|
||||
@@ -147,12 +146,12 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
return portNumbers;
|
||||
}
|
||||
|
||||
public void setTaskExecutionTimeout(String taskExecutionTimeout)
|
||||
void setTaskExecutionTimeout(String taskExecutionTimeout)
|
||||
{
|
||||
this.taskExecutionTimeout = parseStringForLong(taskExecutionTimeout.trim());
|
||||
}
|
||||
|
||||
public void setTemplateProfileDir(String templateProfileDir)
|
||||
void setTemplateProfileDir(String templateProfileDir)
|
||||
{
|
||||
if (templateProfileDir == null || templateProfileDir.trim().length() == 0)
|
||||
{
|
||||
@@ -169,26 +168,26 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
}
|
||||
}
|
||||
|
||||
public void setTaskQueueTimeout(String taskQueueTimeout)
|
||||
void setTaskQueueTimeout(String taskQueueTimeout)
|
||||
{
|
||||
this.taskQueueTimeout = parseStringForLong(taskQueueTimeout.trim());
|
||||
}
|
||||
|
||||
public void setConnectTimeout(String connectTimeout)
|
||||
void setConnectTimeout(String connectTimeout)
|
||||
{
|
||||
this.connectTimeout = parseStringForLong(connectTimeout.trim());
|
||||
}
|
||||
|
||||
public void setEnabled(String enabled)
|
||||
void setEnabled(final String enabledStr)
|
||||
{
|
||||
this.enabled = parseEnabled(enabled);
|
||||
enabled = parseEnabled(enabledStr);
|
||||
|
||||
// If this is a request from the Enterprise Admin console to disable the JodConverter.
|
||||
if (this.enabled == false && (deprecatedOooEnabled == null || deprecatedOooEnabled == false))
|
||||
if (!enabled && (deprecatedOooEnabled == null || !deprecatedOooEnabled))
|
||||
{
|
||||
// We need to change isAvailable to false so we don't make calls to a previously started OfficeManger.
|
||||
// In the case of Enterprise it is very unlikely that ooo.enabled will have been set to true.
|
||||
this.isAvailable = false;
|
||||
isAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +206,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
// So that Community systems <= Alfresco 6.0.1-ea keep working on upgrade, we may need to use the deprecated
|
||||
// ooo.exe setting rather than the jodconverter.officeHome setting if we don't have the jod setting as
|
||||
// oooDirect was replaced by jodconverter after this release.
|
||||
String getOfficeHome()
|
||||
private String getOfficeHome()
|
||||
{
|
||||
String officeHome = this.officeHome;
|
||||
if ((officeHome == null || officeHome.isEmpty()) && (deprecatedOooExe != null && !deprecatedOooExe.isEmpty()))
|
||||
@@ -243,7 +242,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
// Community set properties via alfresco-global.properties.
|
||||
// Enterprise may do the same but may also reset jodconverter.enabled them via the Admin console.
|
||||
// In the case of Enterprise it is very unlikely that ooo.enabled will be set to true.
|
||||
boolean isEnabled()
|
||||
private boolean isEnabled()
|
||||
{
|
||||
return (deprecatedOooEnabled != null && deprecatedOooEnabled) || (enabled != null && enabled);
|
||||
}
|
||||
@@ -251,7 +250,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
// So that Community systems <= Alfresco 6.0.1-ea keep working on upgrade, we may need to use the deprecated
|
||||
// ooo.port setting rather than the jodconverter.portNumbers if ooo.enabled is true and jodconverter.enabled
|
||||
// is false.
|
||||
int[] getPortNumbers()
|
||||
private int[] getPortNumbers()
|
||||
{
|
||||
return (enabled == null || !enabled) && deprecatedOooEnabled != null && deprecatedOooEnabled
|
||||
? deprecatedOooPortNumbers
|
||||
@@ -260,11 +259,9 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
|
||||
private Long parseStringForLong(String string)
|
||||
{
|
||||
Long result = null;
|
||||
try
|
||||
{
|
||||
long l = Long.parseLong(string);
|
||||
result = new Long(l);
|
||||
return Long.parseLong(string);
|
||||
}
|
||||
catch (NumberFormatException nfe)
|
||||
{
|
||||
@@ -272,9 +269,8 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
{
|
||||
logger.debug("Cannot parse numerical value from " + string);
|
||||
}
|
||||
// else intentionally empty
|
||||
}
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -283,14 +279,14 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
*/
|
||||
public boolean isAvailable()
|
||||
{
|
||||
final boolean result = isAvailable && (officeManager != null || (url != null && !url.isEmpty()));
|
||||
return result;
|
||||
return isAvailable && (officeManager != null || (url != null && !url.isEmpty()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet()
|
||||
{
|
||||
// isAvailable defaults to false afterPropertiesSet. It only becomes true on successful completion of this method.
|
||||
@@ -318,7 +314,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
}
|
||||
|
||||
// Only start the JodConverter instance(s) if the subsystem is enabled.
|
||||
if (isEnabled() == false)
|
||||
if (!isEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -418,7 +414,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
|
||||
private void logAllSofficeFilesUnderOfficeHome()
|
||||
{
|
||||
if (logger.isDebugEnabled() == false)
|
||||
if (!logger.isDebugEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -430,7 +426,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
|
||||
logFileInfo(requestedOfficeHome);
|
||||
|
||||
for (File f : findSofficePrograms(requestedOfficeHome, new ArrayList<File>(), 2))
|
||||
for (File f : findSofficePrograms(requestedOfficeHome, new ArrayList<>(), 2))
|
||||
{
|
||||
logFileInfo(f);
|
||||
}
|
||||
@@ -449,26 +445,11 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
return results;
|
||||
}
|
||||
|
||||
File[] matchingFiles = searchRoot.listFiles(new FilenameFilter()
|
||||
{
|
||||
@Override
|
||||
public boolean accept(File dir, String name)
|
||||
{
|
||||
return name.startsWith("soffice");
|
||||
}
|
||||
});
|
||||
for (File f : matchingFiles)
|
||||
{
|
||||
results.add(f);
|
||||
}
|
||||
File[] matchingFiles = searchRoot.listFiles((dir, name) -> name.startsWith("soffice"));
|
||||
Arrays.stream(matchingFiles)
|
||||
.forEach(results::add);
|
||||
|
||||
for (File dir : searchRoot.listFiles(new FileFilter()
|
||||
{
|
||||
@Override
|
||||
public boolean accept(File f) {
|
||||
return f.isDirectory();
|
||||
}
|
||||
}))
|
||||
for (File dir : searchRoot.listFiles(File::isDirectory))
|
||||
{
|
||||
findSofficePrograms(dir, results, currentRecursionDepth + 1, maxRecursionDepth);
|
||||
}
|
||||
@@ -482,7 +463,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
*/
|
||||
private void logFileInfo(File f)
|
||||
{
|
||||
if (logger.isDebugEnabled() == false)
|
||||
if (!logger.isDebugEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -512,7 +493,9 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.DisposableBean#destroy()
|
||||
*/
|
||||
public void destroy() throws Exception {
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
this.isAvailable = false;
|
||||
if (officeManager != null)
|
||||
{
|
||||
@@ -530,6 +513,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
/* (non-Javadoc)
|
||||
* @see org.alfresco.repo.content.JodConverterWorker#getOfficeManager()
|
||||
*/
|
||||
@Override
|
||||
public OfficeManager getOfficeManager()
|
||||
{
|
||||
return officeManager;
|
@@ -0,0 +1,118 @@
|
||||
package org.alfresco.transformer.executors;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.alfresco.transformer.exceptions.TransformException;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.artofsolving.jodconverter.OfficeDocumentConverter;
|
||||
import org.artofsolving.jodconverter.office.OfficeException;
|
||||
import org.artofsolving.jodconverter.office.OfficeManager;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.sun.star.task.ErrorCodeIOException;
|
||||
|
||||
@Component
|
||||
public class LibreOfficeJavaExecutor implements JavaExecutor
|
||||
{
|
||||
private static final Log logger = LogFactory.getLog(LibreOfficeJavaExecutor.class);
|
||||
|
||||
private static final int JODCONVERTER_TRANSFORMATION_ERROR_CODE = 3088;
|
||||
private static final String OFFICE_HOME = "/opt/libreoffice5.4";
|
||||
|
||||
private JodConverter jodconverter = createJodConverter();
|
||||
|
||||
private static JodConverter createJodConverter()
|
||||
{
|
||||
final String timeout = "120000";
|
||||
|
||||
JodConverterSharedInstance jodconverter = new JodConverterSharedInstance();
|
||||
|
||||
jodconverter.setOfficeHome(OFFICE_HOME); // jodconverter.officeHome
|
||||
jodconverter.setMaxTasksPerProcess("200"); // jodconverter.maxTasksPerProcess
|
||||
jodconverter.setTaskExecutionTimeout(timeout); // jodconverter.maxTaskExecutionTimeout
|
||||
jodconverter.setTaskQueueTimeout("30000"); // jodconverter.taskQueueTimeout
|
||||
jodconverter.setConnectTimeout("28000"); // jodconverter.connectTimeout
|
||||
jodconverter.setPortNumbers("8100"); // jodconverter.portNumbers
|
||||
jodconverter.setTemplateProfileDir(""); // jodconverter.templateProfileDir
|
||||
jodconverter.setEnabled("true"); // jodconverter.enabled
|
||||
jodconverter.afterPropertiesSet();
|
||||
|
||||
return jodconverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void call(File sourceFile, File targetFile, String... args)
|
||||
{
|
||||
try
|
||||
{
|
||||
convert(sourceFile, targetFile);
|
||||
}
|
||||
catch (OfficeException e)
|
||||
{
|
||||
throw new TransformException(400, "LibreOffice server conversion failed: \n" +
|
||||
" from file: " + sourceFile + "\n" +
|
||||
" to file: " + targetFile, e);
|
||||
}
|
||||
catch (Throwable throwable)
|
||||
{
|
||||
// Because of the known bug with empty Spreadsheets in JodConverter try to catch exception and produce empty pdf file
|
||||
if (throwable.getCause() instanceof ErrorCodeIOException &&
|
||||
((ErrorCodeIOException) throwable.getCause()).ErrCode == JODCONVERTER_TRANSFORMATION_ERROR_CODE)
|
||||
{
|
||||
logger.warn("Transformation failed: \n" +
|
||||
"from file: " + sourceFile + "\n" +
|
||||
"to file: " + targetFile +
|
||||
"Source file " + sourceFile + " has no content");
|
||||
produceEmptyPdfFile(targetFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw throwable;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetFile.exists() || targetFile.length() == 0L)
|
||||
{
|
||||
throw new TransformException(500, "Transformer failed to create an output file");
|
||||
}
|
||||
}
|
||||
|
||||
public void convert(File sourceFile, File targetFile)
|
||||
{
|
||||
OfficeManager officeManager = jodconverter.getOfficeManager();
|
||||
OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
|
||||
converter.convert(sourceFile, targetFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method produces an empty PDF file at the specified File location.
|
||||
* Apache's PDFBox is used to create the PDF file.
|
||||
*/
|
||||
private static void produceEmptyPdfFile(File targetFile)
|
||||
{
|
||||
// If improvement PDFBOX-914 is incorporated, we can do this with a straight call to
|
||||
// org.apache.pdfbox.TextToPdf.createPDFFromText(new StringReader(""));
|
||||
// https://issues.apache.org/jira/browse/PDFBOX-914
|
||||
|
||||
PDPage pdfPage = new PDPage();
|
||||
try (PDDocument pdfDoc = new PDDocument();
|
||||
PDPageContentStream contentStream = new PDPageContentStream(pdfDoc, pdfPage))
|
||||
{
|
||||
// Even though, we want an empty PDF, some libs (e.g. PDFRenderer) object to PDFs
|
||||
// that have literally nothing in them. So we'll put a content stream in it.
|
||||
pdfDoc.addPage(pdfPage);
|
||||
|
||||
// Now write the in-memory PDF document into the temporary file.
|
||||
pdfDoc.save(targetFile.getAbsolutePath());
|
||||
}
|
||||
catch (IOException iox)
|
||||
{
|
||||
throw new TransformException(500, "Error creating empty PDF file", iox);
|
||||
}
|
||||
}
|
||||
}
|
@@ -29,26 +29,38 @@ import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.alfresco.transform.client.model.TransformReply;
|
||||
import org.alfresco.transform.client.model.TransformRequest;
|
||||
import org.alfresco.transformer.executors.LibreOfficeJavaExecutor;
|
||||
import org.alfresco.transformer.model.FileRefEntity;
|
||||
import org.alfresco.transformer.model.FileRefResponse;
|
||||
import org.alfresco.util.exec.RuntimeExec;
|
||||
import org.artofsolving.jodconverter.office.OfficeException;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.mockito.Mock;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
@@ -62,15 +74,18 @@ import org.springframework.util.StringUtils;
|
||||
@WebMvcTest(LibreOfficeControllerTest.class)
|
||||
public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
||||
{
|
||||
@Mock
|
||||
private RuntimeExec.ExecutionResult mockExecutionResult;
|
||||
|
||||
@SpyBean
|
||||
private LibreOfficeJavaExecutor javaExecutor;
|
||||
|
||||
@SpyBean
|
||||
private LibreOfficeController controller;
|
||||
|
||||
@Before
|
||||
public void before() throws IOException
|
||||
{
|
||||
controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient);
|
||||
super.controller = controller;
|
||||
|
||||
sourceExtension = "doc";
|
||||
targetExtension = "pdf";
|
||||
sourceMimetype = "application/msword";
|
||||
@@ -78,11 +93,11 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
||||
// The following is based on super.mockTransformCommand(...)
|
||||
// This is because LibreOffice used JodConverter rather than a RuntimeExec
|
||||
|
||||
expectedSourceFileBytes = Files.readAllBytes(getTestFile("quick."+sourceExtension, true).toPath());
|
||||
expectedTargetFileBytes = Files.readAllBytes(getTestFile("quick."+targetExtension, true).toPath());
|
||||
sourceFile = new MockMultipartFile("file", "quick."+sourceExtension, sourceMimetype, expectedSourceFileBytes);
|
||||
expectedSourceFileBytes = Files.readAllBytes(getTestFile("quick." + sourceExtension, true).toPath());
|
||||
expectedTargetFileBytes = Files.readAllBytes(getTestFile("quick." + targetExtension, true).toPath());
|
||||
sourceFile = new MockMultipartFile("file", "quick." + sourceExtension, sourceMimetype, expectedSourceFileBytes);
|
||||
|
||||
doAnswer((Answer) invocation ->
|
||||
doAnswer(invocation ->
|
||||
{
|
||||
File sourceFile = invocation.getArgument(0);
|
||||
File targetFile = invocation.getArgument(1);
|
||||
@@ -91,19 +106,12 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
||||
assertNotNull(sourceFile);
|
||||
assertNotNull(targetFile);
|
||||
|
||||
Long actualTimeout = invocation.getArgument(2);
|
||||
assertNotNull(actualTimeout);
|
||||
if (expectedTimeout != null)
|
||||
{
|
||||
assertEquals("expectedTimeout", expectedTimeout, actualTimeout);
|
||||
}
|
||||
|
||||
// Copy a test file into the target file location if it exists
|
||||
String actualTarget = targetFile.getAbsolutePath();
|
||||
int i = actualTarget.lastIndexOf('_');
|
||||
if (i >= 0)
|
||||
{
|
||||
String testFilename = actualTarget.substring(i+1);
|
||||
String testFilename = actualTarget.substring(i + 1);
|
||||
File testFile = getTestFile(testFilename, false);
|
||||
generateTargetFileFromResourceFile(actualTargetExtension, testFile, targetFile);
|
||||
}
|
||||
@@ -113,20 +121,32 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
||||
assertTrue("Source file is not the same", Arrays.equals(expectedSourceFileBytes, actualSourceFileBytes));
|
||||
|
||||
return null;
|
||||
}).when(controller).convert(any(), any(), anyLong());
|
||||
}).when(javaExecutor).convert(any(), any());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void mockTransformCommand(String sourceExtension, String targetExtension,
|
||||
String sourceMimetype, boolean readTargetFileBytes)
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractTransformerController getController()
|
||||
{
|
||||
return controller;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void badExitCodeTest() throws Exception
|
||||
{
|
||||
doThrow(OfficeException.class).when(controller).convert(any(), any(), anyLong());
|
||||
doThrow(OfficeException.class).when(javaExecutor).convert(any(), any());
|
||||
|
||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
||||
.file(sourceFile)
|
||||
.param("targetExtension", "xxx"))
|
||||
.andExpect(status().is(400))
|
||||
.andExpect(status().reason(containsString("LibreOffice - LibreOffice server conversion failed:")));
|
||||
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||
.file(sourceFile)
|
||||
.param("targetExtension", "xxx"))
|
||||
.andExpect(status().is(400))
|
||||
.andExpect(status().reason(containsString("LibreOffice - LibreOffice server conversion failed:")));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -137,4 +157,58 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
||||
transformRequest.setSourceMediaType("application/msword");
|
||||
transformRequest.setTargetMediaType(MediaType.IMAGE_PNG_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPojoTransform() throws Exception
|
||||
{
|
||||
// Files
|
||||
String sourceFileRef = UUID.randomUUID().toString();
|
||||
File sourceFile = getTestFile("quick." + sourceExtension, true);
|
||||
String targetFileRef = UUID.randomUUID().toString();
|
||||
|
||||
// Transformation Request POJO
|
||||
TransformRequest transformRequest = new TransformRequest();
|
||||
transformRequest.setRequestId("1");
|
||||
transformRequest.setSchema(1);
|
||||
transformRequest.setClientData("Alfresco Digital Business Platform");
|
||||
transformRequest.setTransformRequestOptions(new HashMap<>());
|
||||
transformRequest.setSourceReference(sourceFileRef);
|
||||
transformRequest.setSourceExtension(sourceExtension);
|
||||
transformRequest.setSourceSize(sourceFile.length());
|
||||
transformRequest.setTargetExtension(targetExtension);
|
||||
|
||||
// HTTP Request
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=quick." + sourceExtension);
|
||||
ResponseEntity<Resource> response = new ResponseEntity<>(new FileSystemResource(
|
||||
sourceFile), headers, HttpStatus.OK);
|
||||
|
||||
when(alfrescoSharedFileStoreClient.retrieveFile(sourceFileRef)).thenReturn(response);
|
||||
when(alfrescoSharedFileStoreClient.saveFile(any())).thenReturn(
|
||||
new FileRefResponse(new FileRefEntity(targetFileRef)));
|
||||
when(mockExecutionResult.getExitValue()).thenReturn(0);
|
||||
|
||||
// Update the Transformation Request with any specific params before sending it
|
||||
updateTransformRequestWithSpecificOptions(transformRequest);
|
||||
|
||||
// Serialize and call the transformer
|
||||
String tr = objectMapper.writeValueAsString(transformRequest);
|
||||
String transformationReplyAsString = mockMvc.perform(
|
||||
MockMvcRequestBuilders.post("/transform")
|
||||
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_JSON_VALUE).content(tr))
|
||||
.andExpect(
|
||||
status().is(HttpStatus.CREATED.value()))
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
|
||||
TransformReply transformReply = objectMapper.readValue(transformationReplyAsString,
|
||||
TransformReply.class);
|
||||
|
||||
// Assert the reply
|
||||
assertEquals(transformRequest.getRequestId(), transformReply.getRequestId());
|
||||
assertEquals(transformRequest.getClientData(), transformReply.getClientData());
|
||||
assertEquals(transformRequest.getSchema(), transformReply.getSchema());
|
||||
}
|
||||
}
|
||||
|
@@ -25,7 +25,6 @@
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
|
||||
import org.alfresco.transformer.AbstractHttpRequestTest;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
@@ -47,5 +46,5 @@ public class LibreOfficeHttpRequestTest extends AbstractHttpRequestTest
|
||||
protected String getSourceExtension()
|
||||
{
|
||||
return "doc";
|
||||
};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user