/**
* 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.petra.doulos.processor;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONObject;
/**
* @author Brian Wing Shun Chan
* @author Peter Shin
*/
public abstract class BaseShellDoulosRequestProcessor
extends BaseDoulosRequestProcessor {
public BaseShellDoulosRequestProcessor() {
ConcurrentLinkedHashMap.Builder<String, ShellStatus> builder =
new ConcurrentLinkedHashMap.Builder<String, ShellStatus>();
builder.maximumWeightedCapacity(getShellStatusesSize());
_shellStatuses = builder.build();
_thread.start();
}
@Override
public void destroy() {
_destroy = true;
while (!_destroyed) {
try {
if (_log.isInfoEnabled()) {
_log.info("Waiting for background thread to destroy");
}
Thread.sleep(getThreadDestroyInterval());
}
catch (InterruptedException ie) {
_log.error(ie, ie);
}
}
if (_log.isInfoEnabled()) {
_log.info("Background thread is destroyed");
}
super.destroy();
}
@Override
public void process(
String method, String pathInfo, Map<String, String[]> parameterMap,
JSONObject payloadJSONObject, JSONObject responseJSONObject)
throws Exception {
if (!isValid(payloadJSONObject)) {
if (_log.isInfoEnabled()) {
_log.info("Skip invalid payload");
}
responseJSONObject.put(
"queue", new ArrayList<String>(_shellStatuses.keySet()));
return;
}
ShellStatus shellStatus = queue(payloadJSONObject);
populateResponseJSONObject(responseJSONObject, shellStatus);
responseJSONObject.put("queueSize", _queue.size());
if (_log.isInfoEnabled()) {
_log.info(
"Status " + shellStatus.status + " for " + shellStatus.key);
}
}
protected void addShellStatus(String key, ShellStatus shellStatus) {
_shellStatuses.put(key, shellStatus);
_queue.add(shellStatus);
}
protected abstract ShellStatus createShellStatus(
JSONObject payloadJSONObject);
protected void execute(ShellStatus shellStatus) throws Exception {
shellStatus.status = "executing";
List<String> shellCommandsList = getShellCommands(shellStatus);
shellCommandsList.add(0, "/bin/bash");
shellCommandsList.add(1, "-x");
shellCommandsList.add(2, "-c");
String[] shellCommands = shellCommandsList.toArray(
new String[shellCommandsList.size()]);
shellStatus.shellCommands = StringUtils.join(shellCommands, "\n");
ProcessBuilder processBuilder = new ProcessBuilder(shellCommands);
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
StringBuilder sb = new StringBuilder();
String line = null;
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
bufferedReader.close();
try {
if (_log.isDebugEnabled()) {
_log.debug("Wait for process to finish");
}
process.waitFor();
shellStatus.exitValue = String.valueOf(process.exitValue());
shellStatus.output = sb.toString();
shellStatus.status = "finished";
}
catch (Exception e) {
Writer writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
e.printStackTrace(printWriter);
shellStatus.exception = writer.toString();
shellStatus.status = "exception";
}
}
protected long getExpiredTime() {
return _EXPIRED_TIME;
}
protected abstract String getKey(JSONObject payloadJSONObject);
protected abstract List<String> getShellCommands(ShellStatus shellStatus);
protected long getShellStatusesSize() {
return _SHELL_STATUSES_SIZE;
}
protected int getThreadDestroyInterval() {
return _THREAD_DESTROY_INTERVAL;
}
protected int getThreadExecuteInterval() {
return _THREAD_EXECUTE_INTERVAL;
}
protected abstract boolean isRemoveFromQueue(JSONObject payloadJSONObject);
protected abstract boolean isValid(JSONObject payloadJSONObject);
protected void populateResponseJSONObject(
JSONObject responseJSONObject, ShellStatus shellStatus) {
responseJSONObject.put("exception", shellStatus.exception);
responseJSONObject.put("exitValue", shellStatus.exitValue);
responseJSONObject.put("output", shellStatus.output);
responseJSONObject.put("shellCommands", shellStatus.shellCommands);
responseJSONObject.put("status", shellStatus.status);
}
protected ShellStatus queue(JSONObject payloadJSONObject) {
ShellStatus shellStatus = null;
String key = getKey(payloadJSONObject);
synchronized (this) {
shellStatus = _shellStatuses.get(key);
if (isRemoveFromQueue(payloadJSONObject)) {
if (shellStatus != null) {
removeShellStatus(key, shellStatus);
}
shellStatus = createShellStatus(payloadJSONObject);
shellStatus.status = "removed";
return shellStatus;
}
if (shellStatus != null) {
long expiredTime = getExpiredTime();
if ((expiredTime > 0) &&
(shellStatus.time < getExpiredTime())) {
removeShellStatus(key, shellStatus);
shellStatus = null;
}
}
if (shellStatus == null) {
if (_log.isInfoEnabled()) {
_log.info("Adding " + key + " to queue");
}
shellStatus = createShellStatus(payloadJSONObject);
addShellStatus(key, shellStatus);
}
}
return shellStatus;
}
protected void removeShellStatus(String key, ShellStatus shellStatus) {
_shellStatuses.remove(key);
_queue.remove(shellStatus);
}
protected class ShellStatus {
public ShellStatus(String key) {
this.key = key;
}
public String exception = "";
public String exitValue = "";
public String key = "";
public String output = "";
public String shellCommands = "";
public String status = "queued";
public long time = System.currentTimeMillis();
}
private static final int _EXPIRED_TIME = 0;
private static final long _SHELL_STATUSES_SIZE = 1000;
private static final int _THREAD_DESTROY_INTERVAL = 10 * 1000;
private static final int _THREAD_EXECUTE_INTERVAL = 3 * 1000;
private static final Log _log = LogFactory.getLog(
BaseShellDoulosRequestProcessor.class);
private boolean _destroy;
private boolean _destroyed;
private final Queue<ShellStatus> _queue = new LinkedBlockingQueue<>();
private final Map<String, ShellStatus> _shellStatuses;
private final Thread _thread = new Thread() {
@Override
public void run() {
while (true) {
if (_destroy) {
break;
}
ShellStatus shellStatus = _queue.poll();
if (shellStatus == null) {
try {
Thread.sleep(getThreadExecuteInterval());
}
catch (InterruptedException ie) {
_log.error(
"Terminating background thread due to unexpected " +
"interruption",
ie);
break;
}
continue;
}
System.out.println("Queue size " + _queue.size());
try {
if (_log.isInfoEnabled()) {
_log.info("Executing " + shellStatus.key);
}
execute(shellStatus);
}
catch (Exception e) {
_log.error(e, e);
}
}
_destroyed = true;
}
};
}