org.artofsolving.jodconverter.office.OfficeProcess Maven / Gradle / Ivy
The newest version!
//
// JODConverter - Java OpenDocument Converter
// Copyright 2004-2012 Mirko Nasato and contributors
//
// JODConverter is Open Source software, you can redistribute it and/or
// modify it under either (at your option) of the following licenses
//
// 1. The GNU Lesser General Public License v3 (or later)
// -> http://www.gnu.org/licenses/lgpl-3.0.txt
// 2. The Apache License, Version 2.0
// -> http://www.apache.org/licenses/LICENSE-2.0.txt
//
package org.artofsolving.jodconverter.office;
import static org.artofsolving.jodconverter.process.ProcessManager.PID_NOT_FOUND;
import static org.artofsolving.jodconverter.process.ProcessManager.PID_UNKNOWN;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.apache.commons.io.FileUtils;
import org.artofsolving.jodconverter.process.ProcessManager;
import org.artofsolving.jodconverter.process.ProcessQuery;
import org.artofsolving.jodconverter.util.PlatformUtils;
class OfficeProcess {
private final File officeHome;
private final UnoUrl unoUrl;
private final String[] runAsArgs;
private final File templateProfileDir;
private final File instanceProfileDir;
private final ProcessManager processManager;
private Process process;
private long pid = PID_UNKNOWN;
private final Logger logger = Logger.getLogger(getClass().getName());
public OfficeProcess(File officeHome, UnoUrl unoUrl, String[] runAsArgs, File templateProfileDir, File workDir, ProcessManager processManager) {
this.officeHome = officeHome;
this.unoUrl = unoUrl;
this.runAsArgs = runAsArgs;
this.templateProfileDir = templateProfileDir;
this.instanceProfileDir = getInstanceProfileDir(workDir, unoUrl);
this.processManager = processManager;
}
public void start() throws IOException {
start(false);
}
public void start(boolean restart) throws IOException {
ProcessQuery processQuery = new ProcessQuery("soffice.bin", unoUrl.getAcceptString());
long existingPid = processManager.findPid(processQuery);
if (!(existingPid == PID_NOT_FOUND || existingPid == PID_UNKNOWN)) {
throw new IllegalStateException(String.format("a process with acceptString '%s' is already running; pid %d",
unoUrl.getAcceptString(), existingPid));
}
if (!restart) {
prepareInstanceProfileDir();
}
List command = new ArrayList();
File executable = OfficeUtils.getOfficeExecutable(officeHome);
if (runAsArgs != null) {
command.addAll(Arrays.asList(runAsArgs));
}
command.add(executable.getAbsolutePath());
command.add("-accept=" + unoUrl.getAcceptString() + ";urp;");
command.add("-env:UserInstallation=" + OfficeUtils.toUrl(instanceProfileDir));
command.add("-headless");
command.add("-nocrashreport");
command.add("-nodefault");
command.add("-nofirststartwizard");
command.add("-nolockcheck");
command.add("-nologo");
command.add("-norestore");
ProcessBuilder processBuilder = new ProcessBuilder(command);
if (PlatformUtils.isWindows()) {
addBasisAndUrePaths(processBuilder);
}
logger.info(String.format("starting process with acceptString '%s' and profileDir '%s'", unoUrl, instanceProfileDir));
process = processBuilder.start();
pid = processManager.findPid(processQuery);
if (pid == PID_NOT_FOUND) {
throw new IllegalStateException(String.format("process with acceptString '%s' started but its pid could not be found",
unoUrl.getAcceptString()));
}
logger.info("started process" + (pid != PID_UNKNOWN ? "; pid = " + pid : ""));
}
private File getInstanceProfileDir(File workDir, UnoUrl unoUrl) {
String dirName = ".jodconverter_" + unoUrl.getAcceptString().replace(',', '_').replace('=', '-');
return new File(workDir, dirName);
}
private void prepareInstanceProfileDir() throws OfficeException {
if (instanceProfileDir.exists()) {
logger.warning(String.format("profile dir '%s' already exists; deleting", instanceProfileDir));
deleteProfileDir();
}
if (templateProfileDir != null) {
try {
FileUtils.copyDirectory(templateProfileDir, instanceProfileDir);
} catch (IOException ioException) {
throw new OfficeException("failed to create profileDir", ioException);
}
}
}
public void deleteProfileDir() {
if (instanceProfileDir != null) {
try {
FileUtils.deleteDirectory(instanceProfileDir);
} catch (IOException ioException) {
File oldProfileDir = new File(instanceProfileDir.getParentFile(), instanceProfileDir.getName() + ".old." + System.currentTimeMillis());
if (instanceProfileDir.renameTo(oldProfileDir)) {
logger.warning("could not delete profileDir: " + ioException.getMessage() + "; renamed it to " + oldProfileDir);
} else {
logger.severe("could not delete profileDir: " + ioException.getMessage());
}
}
}
}
private void addBasisAndUrePaths(ProcessBuilder processBuilder) throws IOException {
// see http://wiki.services.openoffice.org/wiki/ODF_Toolkit/Efforts/Three-Layer_OOo
File basisLink = new File(officeHome, "basis-link");
if (!basisLink.isFile()) {
logger.fine("no %OFFICE_HOME%/basis-link found; assuming it's OOo 2.x and we don't need to append URE and Basic paths");
return;
}
String basisLinkText = FileUtils.readFileToString(basisLink).trim();
File basisHome = new File(officeHome, basisLinkText);
File basisProgram = new File(basisHome, "program");
File ureLink = new File(basisHome, "ure-link");
String ureLinkText = FileUtils.readFileToString(ureLink).trim();
File ureHome = new File(basisHome, ureLinkText);
File ureBin = new File(ureHome, "bin");
Map environment = processBuilder.environment();
// Windows environment variables are case insensitive but Java maps are not :-/
// so let's make sure we modify the existing key
String pathKey = "PATH";
for (String key : environment.keySet()) {
if ("PATH".equalsIgnoreCase(key)) {
pathKey = key;
}
}
String path = environment.get(pathKey) + ";" + ureBin.getAbsolutePath() + ";" + basisProgram.getAbsolutePath();
logger.fine(String.format("setting %s to \"%s\"", pathKey, path));
environment.put(pathKey, path);
}
public boolean isRunning() {
if (process == null) {
return false;
}
return getExitCode() == null;
}
private class ExitCodeRetryable extends Retryable {
private int exitCode;
protected void attempt() throws TemporaryException, Exception {
try {
exitCode = process.exitValue();
} catch (IllegalThreadStateException illegalThreadStateException) {
throw new TemporaryException(illegalThreadStateException);
}
}
public int getExitCode() {
return exitCode;
}
}
public Integer getExitCode() {
try {
return process.exitValue();
} catch (IllegalThreadStateException exception) {
return null;
}
}
public int getExitCode(long retryInterval, long retryTimeout) throws RetryTimeoutException {
try {
ExitCodeRetryable retryable = new ExitCodeRetryable();
retryable.execute(retryInterval, retryTimeout);
return retryable.getExitCode();
} catch (RetryTimeoutException retryTimeoutException) {
throw retryTimeoutException;
} catch (Exception exception) {
throw new OfficeException("could not get process exit code", exception);
}
}
public int forciblyTerminate(long retryInterval, long retryTimeout) throws IOException, RetryTimeoutException {
logger.info(String.format("trying to forcibly terminate process: '" + unoUrl + "'" + (pid != PID_UNKNOWN ? " (pid " + pid + ")" : "")));
processManager.kill(process, pid);
return getExitCode(retryInterval, retryTimeout);
}
}