/*
* Jopr Management Platform
* Copyright (C) 2005-2008 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.jboss.on.plugins.tomcat;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.StringTokenizer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.mc4j.ems.connection.EmsConnection;
import org.mc4j.ems.connection.bean.attribute.EmsAttribute;
import org.jboss.on.plugins.tomcat.helper.CreateResourceHelper;
import org.jboss.on.plugins.tomcat.helper.FileContentDelegate;
import org.jboss.on.plugins.tomcat.helper.TomcatApplicationDeployer;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.ConfigurationUpdateStatus;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.content.PackageDetailsKey;
import org.rhq.core.domain.content.transfer.ResourcePackageDetails;
import org.rhq.core.domain.resource.CreateResourceStatus;
import org.rhq.core.pluginapi.configuration.ConfigurationUpdateReport;
import org.rhq.core.pluginapi.content.ContentContext;
import org.rhq.core.pluginapi.content.ContentServices;
import org.rhq.core.pluginapi.inventory.ApplicationServerComponent;
import org.rhq.core.pluginapi.inventory.CreateChildResourceFacet;
import org.rhq.core.pluginapi.inventory.CreateResourceReport;
import org.rhq.core.util.exception.ThrowableUtil;
import org.rhq.plugins.jmx.MBeanResourceComponent;
/**
* Handle generic information about a virtual host in tomcat
*
* @author Jay Shaughnessy
* @author Heiko W. Rupp
*
*/
public class TomcatVHostComponent extends MBeanResourceComponent<TomcatServerComponent<?>> implements
ApplicationServerComponent, CreateChildResourceFacet {
public static final String CONFIG_ALIASES = "aliases";
public static final String CONFIG_APP_BASE = "appBase";
public static final String CONFIG_UNPACK_WARS = "unpackWARs";
public static final String CONTENT_CONFIG_EXPLODE_ON_DEPLOY = "explodeOnDeploy";
public static final String PLUGIN_CONFIG_NAME = "name";
/**
* Roles and Groups are handled as comma delimited lists and offered up as a String array of object names by the MBean
*/
@Override
public Configuration loadResourceConfiguration() {
Configuration configuration = super.loadResourceConfiguration();
try {
resetConfig(CONFIG_ALIASES, configuration);
} catch (Exception e) {
log.error("Failed to reset role property value", e);
}
return configuration;
}
// Reset the StringArray provided by the MBean with a more user friendly longString
private void resetConfig(String property, Configuration configuration) {
EmsAttribute attribute = getEmsBean().getAttribute(property);
Object valueObject = attribute.refresh();
String[] vals = (String[]) valueObject;
if (vals.length > 0) {
String delim = "";
StringBuilder sb = new StringBuilder();
for (String val : vals) {
sb.append(delim);
sb.append(val);
delim = "\n";
}
configuration.put(new PropertySimple(property, sb.toString()));
} else {
configuration.put(new PropertySimple(property, null));
}
}
@Override
public void updateResourceConfiguration(ConfigurationUpdateReport report) {
Configuration reportConfiguration = report.getConfiguration();
// reserve the new alias settings
PropertySimple newAliases = reportConfiguration.getSimple(CONFIG_ALIASES);
// get the current alias settings
resetConfig(CONFIG_ALIASES, reportConfiguration);
PropertySimple currentAliases = reportConfiguration.getSimple(CONFIG_ALIASES);
// remove the aliases config from the report so they are ignored by the mbean config processing
reportConfiguration.remove(CONFIG_ALIASES);
// perform standard processing on remaining config
super.updateResourceConfiguration(report);
// add back the aliases config so the report is complete
reportConfiguration.put(newAliases);
// if the mbean update failed, return now
if (ConfigurationUpdateStatus.SUCCESS != report.getStatus()) {
return;
}
// try updating the alias settings
try {
consolidateSettings(newAliases, currentAliases, "addAlias", "removeAlias", "alias");
} catch (Exception e) {
newAliases.setErrorMessage(ThrowableUtil.getStackAsString(e));
report.setErrorMessage("Failed setting resource configuration - see property error messages for details");
log.info("Failure setting Tomcat VHost aliases configuration value", e);
}
// If all went well, persist the changes to the Tomcat server.xml
try {
storeConfig();
} catch (Exception e) {
report
.setErrorMessage("Failed to persist configuration change. Changes will not survive Tomcat restart unless a successful Store Configuration operation is performed.");
}
}
/** Persist local changes to the server.xml */
void storeConfig() throws Exception {
this.getResourceContext().getParentResourceComponent().storeConfig();
}
private void consolidateSettings(PropertySimple newVals, PropertySimple currentVals, String addOp, String removeOp,
String arg) throws Exception {
// add new values not in the current settings
String currentValsLongString = currentVals.getStringValue();
String newValsLongString = newVals.getStringValue();
StringTokenizer tokenizer = null;
Configuration opConfig = new Configuration();
if (null != newValsLongString) {
tokenizer = new StringTokenizer(newValsLongString, "\n");
while (tokenizer.hasMoreTokens()) {
String newVal = tokenizer.nextToken().trim();
if ((null == currentValsLongString) || !currentValsLongString.contains(newVal)) {
opConfig.put(new PropertySimple(arg, newVal));
try {
invokeOperation(addOp, opConfig);
} catch (Exception e) {
throw new IllegalArgumentException("Could not add " + arg + "=" + newVal
+ ". Please check spelling/existence.");
}
}
}
}
if (null != currentValsLongString) {
tokenizer = new StringTokenizer(currentValsLongString, "\n");
while (tokenizer.hasMoreTokens()) {
String currentVal = tokenizer.nextToken().trim();
if ((null == newValsLongString) || !newValsLongString.contains(currentVal)) {
opConfig.put(new PropertySimple(arg, currentVal));
try {
invokeOperation(removeOp, opConfig);
} catch (Exception e) {
throw new IllegalArgumentException("Could not remove " + arg + "=" + currentVal
+ ". Please check spelling/existence.");
}
}
}
}
}
public File getCatalinaBase() {
return getResourceContext().getParentResourceComponent().getCatalinaBase();
}
public String getName() {
return getResourceContext().getPluginConfiguration().getSimpleValue(PLUGIN_CONFIG_NAME, "localhost");
}
public File getConfigurationPath() {
String appBase = (String) getEmsBean().getAttribute(CONFIG_APP_BASE).getValue();
if (null == appBase) {
appBase = "webapps";
}
return new File(getCatalinaBase(), appBase);
}
private boolean isUnpackWars() {
Boolean unpackWars = (Boolean) getEmsBean().getAttribute(CONFIG_UNPACK_WARS).getValue();
return ((null != unpackWars) && unpackWars.booleanValue());
}
public CreateResourceReport createResource(CreateResourceReport report) {
String resourceTypeName = report.getResourceType().getName();
try {
if (TomcatWarComponent.RESOURCE_TYPE_NAME.equals(resourceTypeName)) {
warCreate(report);
} else {
throw new UnsupportedOperationException("Unsupported Resource type: " + resourceTypeName);
}
} catch (Exception e) {
CreateResourceHelper.setErrorOnReport(report, e);
}
return report;
}
private void warCreate(CreateResourceReport report) throws Exception {
ResourcePackageDetails details = report.getPackageDetails();
PackageDetailsKey key = details.getKey();
String archiveName = key.getName();
// Validate file name
if (!archiveName.toLowerCase().endsWith(".war")) {
CreateResourceHelper.setErrorOnReport(report, "Deployed file must have a .war extension");
return;
}
// validate explode option
Configuration deployTimeConfiguration = details.getDeploymentTimeConfiguration();
if (null == deployTimeConfiguration) {
CreateResourceHelper.setErrorOnReport(report,
"Explode On Deploy property is required. Deploy configuration missing.");
return;
}
PropertySimple explodeOnDeployProp = deployTimeConfiguration.getSimple(CONTENT_CONFIG_EXPLODE_ON_DEPLOY);
if ((null == explodeOnDeployProp) || (null == explodeOnDeployProp.getBooleanValue())) {
CreateResourceHelper.setErrorOnReport(report, "Explode On Deploy property is required.");
return;
}
// if the host is configured to unpack wars then always explode, this prevents us from having an unnecessary .war
// in the deploy directory.
boolean explodeOnDeploy = (isUnpackWars() || explodeOnDeployProp.getBooleanValue());
// Perform the deployment
File deployDir = getConfigurationPath();
String contextRoot = archiveName.substring(0, archiveName.length() - 4);
if (explodeOnDeploy) {
// trim off the .war suffix because we want to deploy into a root directory named after the app name
archiveName = contextRoot;
}
File path = new File(deployDir, archiveName);
if (path.exists()) {
CreateResourceHelper.setErrorOnReport(report, "A web application named " + path.getName()
+ " is already deployed with path " + path + ".");
return;
}
File tempDir = getResourceContext().getTemporaryDirectory();
File tempFile = new File(tempDir.getAbsolutePath(), "tomcat-war.bin");
ContentContext contentContext = getResourceContext().getContentContext();
ContentServices contentServices = contentContext.getContentServices();
OutputStream osForTempDir = null;
try {
osForTempDir = new BufferedOutputStream(new FileOutputStream(tempFile));
contentServices.downloadPackageBitsForChildResource(contentContext, TomcatWarComponent.RESOURCE_TYPE_NAME,
key, osForTempDir);
} finally {
if (null != osForTempDir) {
try {
osForTempDir.close();
} catch (IOException e) {
log.error("Error closing temporary output stream", e);
}
}
}
// check for content
boolean valid = isWebApplication(tempFile);
if (!valid) {
CreateResourceHelper.setErrorOnReport(report, "Expected a " + TomcatWarComponent.RESOURCE_TYPE_NAME
+ " file, but its format/content did not match");
return;
}
FileContentDelegate fileContent = new FileContentDelegate(deployDir);
fileContent.createContent(path, tempFile, explodeOnDeploy);
// Resource key is a canonical objectName similar to:
// Catalina:j2eeType=WebModule,name=//<vHost>/<path>,J2EEApplication=none,J2EEServer=none
String objectName = "Catalina:j2eeType=WebModule,J2EEApplication=none,J2EEServer=none,name=//" + getName()
+ "/" + contextRoot;
report.setResourceName(archiveName);
report.setResourceKey(CreateResourceHelper.getCanonicalName(objectName));
report.setStatus(CreateResourceStatus.SUCCESS);
}
public boolean isWebApplication(File file) {
String testFile = "WEB-INF/web.xml";
boolean result = false;
if (file.isDirectory()) {
result = new File(file, testFile).exists();
} else {
JarFile jfile = null;
try {
jfile = new JarFile(file);
JarEntry entry = jfile.getJarEntry(testFile);
result = (null != entry);
} catch (Exception e) {
log.info(e.getMessage());
result = false;
} finally {
if (jfile != null)
try {
jfile.close();
} catch (IOException e) {
log.info("Exception when trying to close the war file: " + e.getMessage());
}
}
}
return result;
}
public TomcatApplicationDeployer getDeployer() {
TomcatApplicationDeployer deployer = null;
EmsConnection connection = null;
try {
connection = getEmsConnection();
if (null != connection) {
deployer = new TomcatApplicationDeployer(connection, getName());
}
} catch (Throwable e) {
log.error("Unable to access Deployer MBean required for creation and deletion of managed resources - this should never happen. Cause: "
+ e);
}
return deployer;
}
void undeployWar(String contextRoot) throws TomcatApplicationDeployer.DeployerException {
// As it stands Tomcat will respond to the placement or removal of the physical Web App itself. We
// call removeServiced prior to the file delete to let TC know to stop servicing the app, hopefully
// for a cleaner removal.
// There is no additional MBean interaction required, the deploy is done in a file-based way.
TomcatApplicationDeployer deployer = getDeployer();
if (null == deployer) {
throw new IllegalStateException("Unable to undeploy " + contextRoot + ", because Deployer MBean could "
+ "not be accessed - this should never happen.");
}
deployer.undeploy(contextRoot);
}
}