/*
* Jopr Management Platform
* Copyright (C) 2005-2009 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* 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 General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.plugins.jbossas5.script;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.content.PackageDetailsKey;
import org.rhq.core.domain.content.PackageType;
import org.rhq.core.domain.content.transfer.ContentResponseResult;
import org.rhq.core.domain.content.transfer.DeployIndividualPackageResponse;
import org.rhq.core.domain.content.transfer.DeployPackageStep;
import org.rhq.core.domain.content.transfer.DeployPackagesResponse;
import org.rhq.core.domain.content.transfer.RemovePackagesResponse;
import org.rhq.core.domain.content.transfer.ResourcePackageDetails;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.pluginapi.content.ContentFacet;
import org.rhq.core.pluginapi.content.ContentServices;
import org.rhq.core.pluginapi.inventory.DeleteResourceFacet;
import org.rhq.core.pluginapi.inventory.ResourceComponent;
import org.rhq.core.pluginapi.inventory.ResourceContext;
import org.rhq.core.pluginapi.operation.OperationFacet;
import org.rhq.core.pluginapi.operation.OperationResult;
import org.rhq.core.pluginapi.util.ProcessExecutionUtility;
import org.rhq.core.system.ProcessExecution;
import org.rhq.core.system.ProcessExecutionResults;
import org.rhq.core.system.SystemInfo;
import org.rhq.core.util.MessageDigestGenerator;
import org.rhq.plugins.jbossas5.ApplicationServerComponent;
import org.rhq.plugins.jbossas5.ApplicationServerPluginConfigurationProperties;
import org.rhq.plugins.jbossas5.connection.ProfileServiceConnection;
import org.rhq.plugins.jbossas5.deploy.RemoteDownloader;
import org.rhq.plugins.jbossas5.deploy.ScriptDeployer;
/**
* A JON service that provides the ability to execute a script.
*
* @author Ian Springer
* @author Lukas Krejci
*/
public class ScriptComponent implements ResourceComponent<ApplicationServerComponent<?>>, OperationFacet,
DeleteResourceFacet, ContentFacet {
public static final String TYPE_NAME = "Script";
public static final String PACKAGE_TYPE = "script";
public static final String PATH_CONFIG_PROP = "path";
public static final String ENVIRONMENT_VARIABLES_CONFIG_PROP = "environmentVariables";
public static final String EXECUTE_OPERATION = "execute";
public static final String COMMAND_LINE_ARGUMENTS_PARAM_PROP = "commandLineArguments";
private static final String EXIT_CODE_RESULT_PROP = "exitCode";
private static final String OUTPUT_RESULT_PROP = "output";
private static final String PACKAGE_VERSION = "none";
private static final String PACKAGE_ARCHITECTURE = "noarch";
private final Log log = LogFactory.getLog(this.getClass());
private ResourceContext<ApplicationServerComponent<?>> resourceContext;
public void start(ResourceContext<ApplicationServerComponent<?>> resourceContext) {
this.resourceContext = resourceContext;
}
public void stop() {
this.resourceContext = null;
}
public AvailabilityType getAvailability() {
File scriptFile = getScriptFile();
return (scriptFile.exists()) ? AvailabilityType.UP : AvailabilityType.DOWN;
}
public OperationResult invokeOperation(String name, Configuration params) throws Exception {
if (name.equals(EXECUTE_OPERATION)) {
OperationResult operationResult = new OperationResult();
File scriptFile = getScriptFile();
SystemInfo systemInfo = this.resourceContext.getSystemInformation();
ProcessExecution processExecution = ProcessExecutionUtility.createProcessExecution(scriptFile);
processExecution.setWaitForCompletion(1000L * 60 * 60); // 1 hour
processExecution.setCaptureOutput(true);
// TODO: Make the script's cwd configurable, but default it to the
// directory containing the script.
processExecution.setWorkingDirectory(scriptFile.getParent());
setEnvironmentVariables(processExecution);
setCommandLineArguments(params, processExecution);
if (log.isDebugEnabled()) {
log.debug(processExecution);
}
ProcessExecutionResults processExecutionResults = systemInfo.executeProcess(processExecution);
if (processExecutionResults.getError() != null) {
throw new Exception(processExecutionResults.getError());
}
Integer exitCode = processExecutionResults.getExitCode();
String output = processExecutionResults.getCapturedOutput(); // NOTE:
// this
// is
// stdout
// +
// stderr
Configuration complexResults = operationResult.getComplexResults();
complexResults.put(new PropertySimple(EXIT_CODE_RESULT_PROP, exitCode));
complexResults.put(new PropertySimple(OUTPUT_RESULT_PROP, output));
if (exitCode != null && exitCode != 0) {
operationResult.setErrorMessage("Exit code was '" + exitCode + "', see operation results for details");
}
return operationResult;
} else {
throw new IllegalArgumentException("Unsupported operation: " + name);
}
}
public void deleteResource() throws Exception {
String path = resourceContext.getPluginConfiguration().getSimpleValue(PATH_CONFIG_PROP, null);
File f = new File(path);
if (!f.delete()) {
throw new IOException("Failed to delete the file on the configured path: " + path);
}
}
public List<DeployPackageStep> generateInstallationSteps(ResourcePackageDetails packageDetails) {
return null;
}
public DeployPackagesResponse deployPackages(Set<ResourcePackageDetails> packages, ContentServices contentServices) {
DeployPackagesResponse response = new DeployPackagesResponse();
if (packages.size() != 1) {
log.warn("Request to deploy a script containing multiple files, which is obivously illegal: " + packages);
response.setOverallRequestResult(ContentResponseResult.FAILURE);
response.setOverallRequestErrorMessage("Only one script can be updated at a time.");
return response;
}
try {
String jbossHomeDir = resourceContext.getParentResourceComponent().getResourceContext()
.getPluginConfiguration().getSimpleValue(ApplicationServerPluginConfigurationProperties.HOME_DIR, null);
SystemInfo systemInfo = resourceContext.getSystemInformation();
ProfileServiceConnection profileServiceConnection = resourceContext.getParentResourceComponent()
.getConnection();
ScriptDeployer deployer = new ScriptDeployer(jbossHomeDir, systemInfo, new RemoteDownloader(
resourceContext, true, profileServiceConnection));
ResourcePackageDetails packageDetails = packages.iterator().next();
DeployIndividualPackageResponse scriptUpdateResult = deployer.update(packageDetails,
resourceContext.getResourceType());
response.setOverallRequestResult(scriptUpdateResult.getResult());
response.addPackageResponse(scriptUpdateResult);
} catch (Exception e) {
response.setOverallRequestErrorMessage(e.getMessage());
response.setOverallRequestResult(ContentResponseResult.FAILURE);
}
return response;
}
public RemovePackagesResponse removePackages(Set<ResourcePackageDetails> packages) {
throw new UnsupportedOperationException("Cannot remove a package backing a script.");
}
public Set<ResourcePackageDetails> discoverDeployedPackages(PackageType type) {
Set<ResourcePackageDetails> results = new HashSet<ResourcePackageDetails>();
if (PACKAGE_TYPE.equals(type.getName())) {
String jbossHomeDir = resourceContext.getParentResourceComponent().getResourceContext()
.getPluginConfiguration().getSimpleValue(ApplicationServerPluginConfigurationProperties.HOME_DIR, null);
File binDirectory = new File(jbossHomeDir, "bin");
File scriptFile = new File(binDirectory, resourceContext.getResourceKey());
String sha256 = PACKAGE_VERSION;
try {
sha256 = new MessageDigestGenerator(MessageDigestGenerator.SHA_256).calcDigestString(scriptFile);
} catch (IOException e) {
log.warn("Failed to compute the SHA256 digest of the script: " + scriptFile.getAbsolutePath(), e);
}
PackageDetailsKey key = new PackageDetailsKey(scriptFile.getName(), this.getVersion(sha256), PACKAGE_TYPE,
PACKAGE_ARCHITECTURE);
ResourcePackageDetails details = new ResourcePackageDetails(key);
details.setDisplayName(scriptFile.getName());
details.setFileName(scriptFile.getAbsolutePath());
details.setFileSize(scriptFile.length());
details.setLocation(scriptFile.getAbsolutePath());
details.setFileCreatedDate(scriptFile.lastModified());
details.setInstallationTimestamp(System.currentTimeMillis());
details.setSHA256(sha256);
results.add(details);
}
return results;
}
public InputStream retrievePackageBits(ResourcePackageDetails packageDetails) {
String jbossHomeDir = resourceContext.getParentResourceComponent().getResourceContext()
.getPluginConfiguration().getSimpleValue(ApplicationServerPluginConfigurationProperties.HOME_DIR, null);
File binDir = new File(jbossHomeDir, "bin");
String scriptName = packageDetails.getKey().getName();
File script = new File(binDir, scriptName);
try {
return new FileInputStream(script);
} catch (FileNotFoundException e) {
log.warn("Failed to retrieve package bits for script " + packageDetails, e);
return null;
}
}
private void setCommandLineArguments(Configuration params, ProcessExecution processExecution) {
List<String> processExecutionArguments = processExecution.getArguments();
if (null == processExecutionArguments) {
processExecutionArguments = new ArrayList<String>();
processExecution.setArguments(processExecutionArguments);
}
String cmdLineArgsString = params.getSimpleValue(COMMAND_LINE_ARGUMENTS_PARAM_PROP, null);
List<String> cmdLineArgs = createCommandLineArgumentList(cmdLineArgsString);
if (null != cmdLineArgs) {
processExecutionArguments.addAll(cmdLineArgs);
}
}
private void setEnvironmentVariables(ProcessExecution processExecution) {
Configuration pluginConfig = this.resourceContext.getPluginConfiguration();
Map<String, String> processExecutionEnvironmentVariables = processExecution.getEnvironmentVariables();
if (null == processExecutionEnvironmentVariables) {
processExecutionEnvironmentVariables = new LinkedHashMap<String, String>();
processExecution.setEnvironmentVariables(processExecutionEnvironmentVariables);
}
String envVars = pluginConfig.getSimpleValue(ENVIRONMENT_VARIABLES_CONFIG_PROP, null);
Map<String, String> envVarsMap = createEnvironmentVariableMap(envVars);
if (null != envVarsMap) {
processExecutionEnvironmentVariables.putAll(envVarsMap);
}
}
@NotNull
private List<String> createCommandLineArgumentList(String cmdLineArgsString) {
if (cmdLineArgsString == null) {
return new ArrayList<String>();
}
StringTokenizer tokenizer = new StringTokenizer(cmdLineArgsString, "\n");
List<String> cmdLineArgs = new ArrayList<String>(tokenizer.countTokens());
while (tokenizer.hasMoreTokens()) {
String cmdLineArg = tokenizer.nextToken().trim();
cmdLineArg = replacePropertyPatterns(cmdLineArg);
cmdLineArgs.add(cmdLineArg);
}
return cmdLineArgs;
}
private Map<String, String> createEnvironmentVariableMap(String envVarsString) {
if (envVarsString == null) {
return null;
}
StringTokenizer tokenizer = new StringTokenizer(envVarsString, "\n");
Map<String, String> envVars = new LinkedHashMap<String, String>(tokenizer.countTokens());
while (tokenizer.hasMoreTokens()) {
String var = tokenizer.nextToken().trim();
int equalsIndex = var.indexOf('=');
if (equalsIndex == -1) {
throw new IllegalStateException("Malformed environment entry: " + var);
}
String varName = var.substring(0, equalsIndex);
String varValue = var.substring(equalsIndex + 1);
varValue = replacePropertyPatterns(varValue);
envVars.put(varName, varValue);
}
return envVars;
}
private String replacePropertyPatterns(String envVars) {
Pattern pattern = Pattern.compile("(%([^%]*)%)");
Matcher matcher = pattern.matcher(envVars);
Configuration parentPluginConfig = this.resourceContext.getParentResourceComponent().getResourceContext()
.getPluginConfiguration();
StringBuffer buffer = new StringBuffer();
while (matcher.find()) {
String propName = matcher.group(2);
PropertySimple prop = parentPluginConfig.getSimple(propName);
String propPattern = matcher.group(1);
String replacement = (prop != null) ? prop.getStringValue() : propPattern;
matcher.appendReplacement(buffer, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(buffer);
return buffer.toString();
}
private File getScriptFile() {
Configuration pluginConfig = this.resourceContext.getPluginConfiguration();
String scriptFilePath = pluginConfig.getSimple(PATH_CONFIG_PROP).getStringValue();
return new File(scriptFilePath);
}
private String getVersion(String sha256) {
return "[sha256=" + sha256 + "]";
}
}