<commit_before>/*
 * AMW - Automated Middleware allows you to manage the configurations of
 * your Java EE applications on an unlimited number of different environments
 * with various versions, including the automated deployment of those apps.
 * Copyright (C) 2013-2016 by Puzzle ITC
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package ch.puzzle.itc.mobiliar.business.generator.control;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Inject;

import ch.puzzle.itc.mobiliar.common.exception.ScriptExecutionException;
import ch.puzzle.itc.mobiliar.common.exception.ScriptExecutionException.REASON;
import ch.puzzle.itc.mobiliar.common.util.ConfigurationService;
import ch.puzzle.itc.mobiliar.common.util.ConfigurationService.ConfigKey;
import ch.puzzle.itc.mobiliar.common.util.SystemCallTemplate;

/**
 * A class to handle the execution of native system calls (e.g. the execution of
 * deployment scripts and similar).
 * 
 * The provided methods handle the whole feedback-chain in case of errors when
 * executing scripts.
 * 
 */
public class RunSystemCallService
{

	private String lineSeparator = System.lineSeparator();

	@Inject
	protected Logger log;

	/**
	 * Looks up a given folder of configurations, looks up the script files
	 * to be executed, makes them executable and tries to invoke them as
	 * native system processes. If multiple scripts exist and one of them
	 * fails, the method interrupts after the first failing.
	 * 
	 * @param folder
	 *               - the relative path of the configuration-folder for
	 *               which the execution scripts shall be invoked.
	 * @param scriptOutputLogFilename
	 * @throws ScriptExecutionException
	 *                - if the execution of one of the scripts was not
	 *                successful. If the script was executed but had errors
	 *                (exited with an error code), the message of this
	 *                exception should contain the given information provided
	 *                by the process itself.
	 */
	public void getAndExecuteScriptFromGeneratedConfig(String folder, String scriptOutputLogFilename)
			throws ScriptExecutionException {

		List<File> scriptPath = getScriptFiles(folder);

		for (File file : scriptPath) {
			String filePath = makeScriptExecutableAndGetAbsolutePath(file);
			if (filePath != null) {
				executeScript(filePath, scriptOutputLogFilename);
			} else {
				String message = "File permissions of "
						+ file.getName()
						+ " could not be set";
				log.log(Level.WARNING, message);
				throw new ScriptExecutionException(message,
						REASON.PERMISSION);
			}
		}
	}

	/**
	 * (Tries to) execute the script with the given script path.
	 * 
	 * @param scriptPath
	 *               - the abolute file path of the script to be executed
	 * @param scriptOutputLogFilename
	 * @throws ScriptExecutionException
	 *                - an exception if anything fails in the execution
	 *                process.
	 */
	private void executeScript(String scriptPath, String scriptOutputLogFilename)
			throws ScriptExecutionException {
		if (scriptPath != null) {
			runSystemCall(scriptPath, scriptOutputLogFilename);
			log.info(scriptPath + " was excuted successfully.");
		} else {
			throw new ScriptExecutionException(
					"Undefined script path", REASON.NOTAVAILABLE);
		}
	}

	/**
	 * This method executes the given command as a native process and traces
	 * the system output. All data that shall be logged as error messages
	 * should be wrapped with " @ ... } ". (e.g. " @ there was a failure
	 * somewhere} ") The message then is recorded and attached to the thrown
	 * {@link ScriptExecutionException} if the process exits with an error
	 * code (!=0)
	 * 
	 * @param command
	 *               - the command to be executed natively.lso includes the
	 *               error stack trace (System.err)
	 * @param scriptOutputLogFilename
	 * @throws ScriptExecutionException
	 *                - if the script was not able to execute properly or
	 *                exited with an error code. Includes the recorded error
	 *                messages as part of the message.
	 */
	private void runSystemCall(String command, String scriptOutputLogFilename)
			throws ScriptExecutionException {
		BufferedWriter logOutput = null;
		FileWriter logOutputFileWriter = null;
		try {
			log.info("Execute command: \"" + command + "\"");
			Process p = new ProcessBuilder(command).redirectErrorStream(true).start();
			InputStream in = p.getInputStream();
			BufferedInputStream buf = new BufferedInputStream(in);
			InputStreamReader inread = new InputStreamReader(buf);
			BufferedReader bufferedreader = new BufferedReader(inread);
			StringBuilder sb = new StringBuilder();
			logOutputFileWriter = new FileWriter(scriptOutputLogFilename, true);
			logOutput  = new BufferedWriter(logOutputFileWriter);
			logOutput.write("Executing Script: " + command);
			logOutput.write(lineSeparator);
			String line;
			while ((line = bufferedreader.readLine()) != null) {
				if(ConfigurationService.getPropertyAsBoolean(ConfigKey.LOG_RUNSCRIPT_OUTPUT_TO_SERVER_LOG)) {
					log.info("Command output: " + line);
				}
				if (line.startsWith("@{") && line.endsWith("}")) {
					sb.append(line.substring(2,<commit_msg>RunScript logging: add target log file name<commit_after>/*
 * AMW - Automated Middleware allows you to manage the configurations of
 * your Java EE applications on an unlimited number of different environments
 * with various versions, including the automated deployment of those apps.
 * Copyright (C) 2013-2016 by Puzzle ITC
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package ch.puzzle.itc.mobiliar.business.generator.control;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Inject;

import ch.puzzle.itc.mobiliar.common.exception.ScriptExecutionException;
import ch.puzzle.itc.mobiliar.common.exception.ScriptExecutionException.REASON;
import ch.puzzle.itc.mobiliar.common.util.ConfigurationService;
import ch.puzzle.itc.mobiliar.common.util.ConfigurationService.ConfigKey;
import ch.puzzle.itc.mobiliar.common.util.SystemCallTemplate;

/**
 * A class to handle the execution of native system calls (e.g. the execution of
 * deployment scripts and similar).
 * 
 * The provided methods handle the whole feedback-chain in case of errors when
 * executing scripts.
 * 
 */
public class RunSystemCallService
{

	private String lineSeparator = System.lineSeparator();

	@Inject
	protected Logger log;

	/**
	 * Looks up a given folder of configurations, looks up the script files
	 * to be executed, makes them executable and tries to invoke them as
	 * native system processes. If multiple scripts exist and one of them
	 * fails, the method interrupts after the first failing.
	 * 
	 * @param folder
	 *               - the relative path of the configuration-folder for
	 *               which the execution scripts shall be invoked.
	 * @param scriptOutputLogFilename
	 * @throws ScriptExecutionException
	 *                - if the execution of one of the scripts was not
	 *                successful. If the script was executed but had errors
	 *                (exited with an error code), the message of this
	 *                exception should contain the given information provided
	 *                by the process itself.
	 */
	public void getAndExecuteScriptFromGeneratedConfig(String folder, String scriptOutputLogFilename)
			throws ScriptExecutionException {

		List<File> scriptPath = getScriptFiles(folder);

		for (File file : scriptPath) {
			String filePath = makeScriptExecutableAndGetAbsolutePath(file);
			if (filePath != null) {
				executeScript(filePath, scriptOutputLogFilename);
			} else {
				String message = "File permissions of "
						+ file.getName()
						+ " could not be set";
				log.log(Level.WARNING, message);
				throw new ScriptExecutionException(message,
						REASON.PERMISSION);
			}
		}
	}

	/**
	 * (Tries to) execute the script with the given script path.
	 * 
	 * @param scriptPath
	 *               - the abolute file path of the script to be executed
	 * @param scriptOutputLogFilename
	 * @throws ScriptExecutionException
	 *                - an exception if anything fails in the execution
	 *                process.
	 */
	private void executeScript(String scriptPath, String scriptOutputLogFilename)
			throws ScriptExecutionException {
		if (scriptPath != null) {
			runSystemCall(scriptPath, scriptOutputLogFilename);
			log.info(scriptPath + " was excuted successfully.");
		} else {
			throw new ScriptExecutionException(
					"Undefined script path", REASON.NOTAVAILABLE);
		}
	}

	/**
	 * This method executes the given command as a native process and traces
	 * the system output. All data that shall be logged as error messages
	 * should be wrapped with " @ ... } ". (e.g. " @ there was a failure
	 * somewhere} ") The message then is recorded and attached to the thrown
	 * {@link ScriptExecutionException} if the process exits with an error
	 * code (!=0)
	 * 
	 * @param command
	 *               - the command to be executed natively.lso includes the
	 *               error stack trace (System.err)
	 * @param scriptOutputLogFilename
	 * @throws ScriptExecutionException
	 *                - if the script was not able to execute properly or
	 *                exited with an error code. Includes the recorded error
	 *                messages as part of the message.
	 */
	private void runSystemCall(String command, String scriptOutputLogFilename)
			throws ScriptExecutionException {
		BufferedWriter logOutput = null;
		FileWriter logOutputFileWriter = null;
		try {
			log.info("Execute command: \"" + command + "\"");
			Process p = new ProcessBuilder(command).redirectErrorStream(true).start();
			InputStream in = p.getInputStream();
			BufferedInputStream buf = new BufferedInputStream(in);
			InputStreamReader inread = new InputStreamReader(buf);
			BufferedReader bufferedreader = new BufferedReader(inread);
			StringBuilder sb = new StringBuilder();
			logOutputFileWriter = new FileWriter(scriptOutputLogFilename, true);
			logOutput  = new BufferedWriter(logOutputFileWriter);
			logOutput.write("Executing Script: " + command);
			logOutput.write(lineSeparator);
			String line;
			while ((line = bufferedreader.readLine()) != null) {
				if(ConfigurationService.getPropertyAsBoolean(ConfigKey.LOG_RUNSCRIPT_OUTPUT_TO_SERVER_LOG)) {
					log.info("RunScriptOutput " + scriptOutputLogFilename + ": " + line);
				}
				if (line.startsWith("@{") && line.endsWith("}")) {
					sb.append(line.substring(2,
{'fraction_non_alphanumeric': 0.07894956231763235, 'fraction_numerical': 0.0018340975406419342, 'mean_word_length': 3.428202288667405, 'pattern_counts': {'":': 1, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7326433', 'n_tokens_mistral': 3383, 'n_tokens_neox': 3014, 'n_words': 1151}