/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.kernel.process; import com.liferay.portal.kernel.concurrent.AbortPolicy; import com.liferay.portal.kernel.concurrent.BaseFutureListener; import com.liferay.portal.kernel.concurrent.DefaultNoticeableFuture; import com.liferay.portal.kernel.concurrent.FutureListener; import com.liferay.portal.kernel.concurrent.NoticeableFuture; import com.liferay.portal.kernel.concurrent.ThreadPoolExecutor; import com.liferay.portal.kernel.concurrent.ThreadPoolHandlerAdapter; import com.liferay.portal.kernel.util.NamedThreadFactory; import com.liferay.portal.kernel.util.ObjectValuePair; import com.liferay.portal.kernel.util.PortalClassLoaderUtil; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicMarkableReference; /** * @author Shuyang Zhou */ public class ProcessUtil { public static final CollectorOutputProcessor COLLECTOR_OUTPUT_PROCESSOR = new CollectorOutputProcessor(); public static final ConsumerOutputProcessor CONSUMER_OUTPUT_PROCESSOR = new ConsumerOutputProcessor(); public static final EchoOutputProcessor ECHO_OUTPUT_PROCESSOR = new EchoOutputProcessor(); public static final LoggingOutputProcessor LOGGING_OUTPUT_PROCESSOR = new LoggingOutputProcessor(); public static <O, E> NoticeableFuture<ObjectValuePair<O, E>> execute( OutputProcessor<O, E> outputProcessor, List<String> arguments) throws ProcessException { if (outputProcessor == null) { throw new NullPointerException("Output processor is null"); } if (arguments == null) { throw new NullPointerException("Arguments is null"); } ProcessBuilder processBuilder = new ProcessBuilder(arguments); try { Process process = processBuilder.start(); ThreadPoolExecutor threadPoolExecutor = _getThreadPoolExecutor(); try { NoticeableFuture<O> stdOutNoticeableFuture = threadPoolExecutor.submit( new ProcessStdOutCallable<O>(outputProcessor, process)); NoticeableFuture<E> stdErrNoticeableFuture = threadPoolExecutor.submit( new ProcessStdErrCallable<E>(outputProcessor, process)); return _wrapNoticeableFuture( stdOutNoticeableFuture, stdErrNoticeableFuture, process); } catch (RejectedExecutionException ree) { process.destroy(); throw new ProcessException( "Cancelled execution because of a concurrent destroy", ree); } } catch (IOException ioe) { throw new ProcessException(ioe); } } public static <O, E> NoticeableFuture<ObjectValuePair<O, E>> execute( OutputProcessor<O, E> outputProcessor, String... arguments) throws ProcessException { return execute(outputProcessor, Arrays.asList(arguments)); } public void destroy() { if (_threadPoolExecutor == null) { return; } synchronized (ProcessUtil.class) { if (_threadPoolExecutor != null) { _threadPoolExecutor.shutdownNow(); _threadPoolExecutor = null; } } } private static ThreadPoolExecutor _getThreadPoolExecutor() { if (_threadPoolExecutor != null) { return _threadPoolExecutor; } synchronized (ProcessUtil.class) { if (_threadPoolExecutor == null) { _threadPoolExecutor = new ThreadPoolExecutor( 0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, true, Integer.MAX_VALUE, new AbortPolicy(), new NamedThreadFactory( ProcessUtil.class.getName(), Thread.MIN_PRIORITY, PortalClassLoaderUtil.getClassLoader()), new ThreadPoolHandlerAdapter()); } } return _threadPoolExecutor; } private static <O, E> NoticeableFuture<ObjectValuePair<O, E>> _wrapNoticeableFuture( final NoticeableFuture<O> stdOutNoticeableFuture, final NoticeableFuture<E> stdErrNoticeableFuture, final Process process) { final DefaultNoticeableFuture<ObjectValuePair<O, E>> defaultNoticeableFuture = new DefaultNoticeableFuture<>(); defaultNoticeableFuture.addFutureListener( new FutureListener<ObjectValuePair<O, E>>() { @Override public void complete(Future<ObjectValuePair<O, E>> future) { if (!future.isCancelled()) { return; } stdOutNoticeableFuture.cancel(true); stdErrNoticeableFuture.cancel(true); process.destroy(); } }); final AtomicMarkableReference<O> stdOutReference = new AtomicMarkableReference<>(null, false); final AtomicMarkableReference<E> stdErrReference = new AtomicMarkableReference<>(null, false); stdOutNoticeableFuture.addFutureListener( new BaseFutureListener<O>() { @Override public void completeWithCancel(Future<O> future) { defaultNoticeableFuture.cancel(true); } @Override public void completeWithException( Future<O> future, Throwable throwable) { defaultNoticeableFuture.setException(throwable); } @Override public void completeWithResult(Future<O> future, O stdOut) { stdOutReference.set(stdOut, true); boolean[] markHolder = new boolean[1]; E stdErr = stdErrReference.get(markHolder); if (markHolder[0]) { defaultNoticeableFuture.set( new ObjectValuePair<O, E>(stdOut, stdErr)); } } }); stdErrNoticeableFuture.addFutureListener( new BaseFutureListener<E>() { @Override public void completeWithCancel(Future<E> future) { defaultNoticeableFuture.cancel(true); } @Override public void completeWithException( Future<E> future, Throwable throwable) { defaultNoticeableFuture.setException(throwable); } @Override public void completeWithResult(Future<E> future, E stdErr) { stdErrReference.set(stdErr, true); boolean[] markHolder = new boolean[1]; O stdOut = stdOutReference.get(markHolder); if (markHolder[0]) { defaultNoticeableFuture.set( new ObjectValuePair<O, E>(stdOut, stdErr)); } } }); return defaultNoticeableFuture; } private static volatile ThreadPoolExecutor _threadPoolExecutor; private static class ProcessStdErrCallable<T> implements Callable<T> { public ProcessStdErrCallable( OutputProcessor<?, T> outputProcessor, Process process) { _outputProcessor = outputProcessor; _process = process; } @Override public T call() throws Exception { return _outputProcessor.processStdErr(_process.getErrorStream()); } private final OutputProcessor<?, T> _outputProcessor; private final Process _process; } private static class ProcessStdOutCallable<T> implements Callable<T> { public ProcessStdOutCallable( OutputProcessor<T, ?> outputProcessor, Process process) { _outputProcessor = outputProcessor; _process = process; } @Override public T call() throws Exception { try { return _outputProcessor.processStdOut( _process.getInputStream()); } finally { try { int exitCode = _process.waitFor(); if (exitCode != 0) { throw new TerminationProcessException(exitCode); } } catch (InterruptedException ie) { _process.destroy(); throw new ProcessException( "Forcibly killed subprocess on interruption", ie); } } } private final OutputProcessor<T, ?> _outputProcessor; private final Process _process; } }