org.jodconverter.office.AbstractOfficeManagerPool Maven / Gradle / Ivy
Show all versions of jodconverter-core Show documentation
/*
* Copyright 2004 - 2012 Mirko Nasato and contributors
* 2016 - 2018 Simon Braconnier and contributors
*
* This file is part of JODConverter - Java OpenDocument Converter.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jodconverter.office;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jodconverter.task.OfficeTask;
/**
* A OfficeManagerPool is responsible to maintain a pool of {@link OfficeProcessManagerPoolEntry}
* that will be used to execute {@link OfficeTask}. The pool will use the first {@link
* OfficeProcessManagerPoolEntry} to execute a given task when the {@link #execute(OfficeTask)}
* function is called.
*/
abstract class AbstractOfficeManagerPool implements OfficeManager, TemporaryFileMaker {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractOfficeManagerPool.class);
private static final int POOL_STOPPED = 0;
private static final int POOL_STARTED = 1;
private static final int POOL_SHUTDOWN = 2;
private final AtomicInteger poolState = new AtomicInteger(POOL_STOPPED);
protected final OfficeManagerPoolConfig config;
private final BlockingQueue pool;
private final AtomicLong tempFileCounter;
private OfficeManager[] entries;
private File tempDir;
private static File makeTempDir(final File workingDir) {
final File tempDir = new File(workingDir, "jodconverter_" + UUID.randomUUID().toString());
tempDir.mkdir();
if (!tempDir.isDirectory()) {
throw new IllegalStateException(String.format("Cannot create temp directory: %s", tempDir));
}
return tempDir;
}
/** Constructs a new instance of the class with the specified settings. */
protected AbstractOfficeManagerPool(final int poolSize, final OfficeManagerPoolConfig config) {
super();
this.config = config;
// Create the pool
pool = new ArrayBlockingQueue<>(poolSize);
// Initialize the temp file counter
tempFileCounter = new AtomicLong(0);
}
/**
* Creates the pool entries when the pool is started.
*
* @return an array of pool entries.
*/
protected abstract OfficeManager[] createPoolEntries();
@Override
public void execute(final OfficeTask task) throws OfficeException {
if (!isRunning()) {
throw new IllegalStateException("This office manager is not running.");
}
// Try to acquire a manager entry, waiting the configured timeout for a
// manager to become available. If we succeed, the acquired manager will
// then execute the given task. Once the task is done, return the manager
// to the pool.
OfficeManager entry = null;
try {
entry = acquireManager();
entry.execute(task);
} finally {
if (entry != null) {
releaseManager(entry);
}
}
}
@Override
public boolean isRunning() {
return poolState.get() == POOL_STARTED;
}
@Override
public void start() throws OfficeException {
synchronized (this) {
if (poolState.get() == POOL_SHUTDOWN) {
throw new IllegalStateException("This office manager has been shutdown.");
}
if (poolState.get() == POOL_STARTED) {
throw new IllegalStateException("This office manager is already running.");
}
// Create the poll entries...
entries = createPoolEntries();
// then start them.
doStart();
// Create the temporary dir is the pool has successfully started
tempDir = makeTempDir(config.getWorkingDir());
poolState.set(POOL_STARTED);
}
}
@Override
public void stop() throws OfficeException {
synchronized (this) {
if (poolState.get() == POOL_SHUTDOWN) {
// Already shutdown, just exit
return;
}
poolState.set(POOL_SHUTDOWN);
try {
doStop();
} finally {
deleteTempDir();
}
}
}
@Override
public File makeTemporaryFile() {
return new File(tempDir, "tempfile_" + tempFileCounter.getAndIncrement());
}
@Override
public File makeTemporaryFile(final String extension) {
return new File(tempDir, "tempfile_" + tempFileCounter.getAndIncrement() + "." + extension);
}
/**
* Acquires a manager, waiting the configured timeout for an entry to become available.
*
* @return A manager that was available.
* @throws OfficeException If we are unable to acquire a manager.
*/
private OfficeManager acquireManager() throws OfficeException {
try {
final OfficeManager manager = pool.poll(config.getTaskQueueTimeout(), TimeUnit.MILLISECONDS);
if (manager == null) {
throw new OfficeException(
"No office manager available after " + config.getTaskQueueTimeout() + " millisec.");
}
return manager;
} catch (InterruptedException interruptedEx) { // NOSONAR
throw new OfficeException(
"Thread has been interrupted while waiting for a manager to become available.",
interruptedEx);
}
}
/**
* Make the given manager available to executes tasks.
*
* @param manager A manager to return to the pool.
* @throws OfficeException If we are unable to release the manager.
*/
private void releaseManager(final OfficeManager manager) throws OfficeException {
try {
pool.put(manager);
} catch (InterruptedException interruptedEx) { // NOSONAR
// Not supposed to happened
throw new OfficeException("interrupted", interruptedEx);
}
}
/**
* Allow base class to perform operation when the pool starts.
*
* @throws OfficeException If an error occurs.
*/
protected void doStart() throws OfficeException {
// Start all PooledOfficeManager and make them available to execute tasks.
for (final OfficeManager manager : entries) {
manager.start();
releaseManager(manager);
}
}
private void doStop() throws OfficeException {
LOGGER.info("Stopping the office manager pool...");
pool.clear();
OfficeException firstException = null;
for (final OfficeManager manager : entries) {
try {
manager.stop();
} catch (OfficeException ex) {
if (firstException == null) {
firstException = ex;
}
}
}
if (firstException != null) {
throw firstException;
}
LOGGER.info("Office manager stopped");
}
private void deleteTempDir() {
if (tempDir != null) {
LOGGER.debug("Deleting temporary directory '{}'", tempDir);
try {
FileUtils.deleteDirectory(tempDir);
} catch (IOException ioEx) { // NOSONAR
LOGGER.error("Could not temporary profileDir: {}", ioEx.getMessage());
}
}
}
/**
* A builder for constructing an {@link AbstractOfficeManagerPool}.
*
* @see AbstractOfficeManagerPool
*/
@SuppressWarnings("unchecked")
public abstract static class AbstractOfficeManagerPoolBuilder<
B extends AbstractOfficeManagerPoolBuilder> {
protected boolean install;
protected File workingDir;
protected long taskExecutionTimeout =
OfficeManagerPoolEntryConfig.DEFAULT_TASK_EXECUTION_TIMEOUT;
protected long taskQueueTimeout = OfficeManagerPoolConfig.DEFAULT_TASK_QUEUE_TIMEOUT;
// Protected ctor so only subclasses can initialize an instance of this builder.
protected AbstractOfficeManagerPoolBuilder() {
super();
}
/**
* Specifies whether the office manager that will be created by this builder will then set the
* unique instance of the {@link InstalledOfficeManagerHolder} class. Note that if the {@code
* InstalledOfficeManagerHolder} class already holds an {@code OfficeManager} instance, the
* owner of this existing manager is responsible to stopped it.
*
* Default: false
*
* @return This builder instance.
*/
public B install() {
this.install = true;
return (B) this;
}
/**
* Specifies the directory where temporary files and directories are created.
*
*
Default: The system temporary directory as specified by the
* java.io.tmpdir
system property.
*
* @param workingDir The new working directory to set.
* @return This builder instance.
*/
public B workingDir(final File workingDir) {
this.workingDir = workingDir;
return (B) this;
}
/**
* Specifies the directory where temporary files and directories are created.
*
*
Default: The system temporary directory as specified by the
* java.io.tmpdir
system property.
*
* @param workingDir The new working directory to set.
* @return This builder instance.
*/
public B workingDir(final String workingDir) {
return StringUtils.isBlank(workingDir) ? (B) this : workingDir(new File(workingDir));
}
/**
* Specifies the maximum time allowed to process a task. If the processing time of a task is
* longer than this timeout, this task will be aborted and the next task is processed.
*
*
Default: 120000 (2 minutes)
*
* @param taskExecutionTimeout The task execution timeout, in milliseconds.
* @return This builder instance.
*/
public B taskExecutionTimeout(final long taskExecutionTimeout) {
Validate.inclusiveBetween(
0,
Long.MAX_VALUE,
taskExecutionTimeout,
String.format(
"The taskExecutionTimeout %s must greater than or equal to 0", taskExecutionTimeout));
this.taskExecutionTimeout = taskExecutionTimeout;
return (B) this;
}
/**
* Specifies the maximum living time of a task in the conversion queue. The task will be removed
* from the queue if the waiting time is longer than this timeout.
*
*
Default: 30000 (30 seconds)
*
* @param taskQueueTimeout The task queue timeout, in milliseconds.
* @return This builder instance.
*/
public B taskQueueTimeout(final long taskQueueTimeout) {
Validate.inclusiveBetween(
0,
Long.MAX_VALUE,
taskQueueTimeout,
String.format(
"The taskQueueTimeout %s must greater than or equal to 0", taskQueueTimeout));
this.taskQueueTimeout = taskQueueTimeout;
return (B) this;
}
/**
* Creates the manager that is specified by this builder.
*
* @return The manager that is specified by this builder.
*/
protected abstract AbstractOfficeManagerPool build();
}
}