/*
* RHQ Management Platform
* Copyright (C) 2005-2010 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 as published by
* the Free Software Foundation version 2 of the License.
*
* 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.plugins.filetemplate;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.bundle.filetemplate.recipe.RecipeContext;
import org.rhq.bundle.filetemplate.recipe.RecipeParser;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionSimple;
import org.rhq.core.domain.configuration.definition.PropertySimpleType;
import org.rhq.core.util.ZipUtil;
import org.rhq.core.util.file.FileUtil;
import org.rhq.core.util.stream.StreamUtil;
import org.rhq.core.util.updater.DeploymentProperties;
import org.rhq.enterprise.server.bundle.BundleDistributionInfo;
import org.rhq.enterprise.server.bundle.RecipeParseResults;
import org.rhq.enterprise.server.plugin.pc.ControlFacet;
import org.rhq.enterprise.server.plugin.pc.ControlResults;
import org.rhq.enterprise.server.plugin.pc.ServerPluginComponent;
import org.rhq.enterprise.server.plugin.pc.ServerPluginContext;
import org.rhq.enterprise.server.plugin.pc.bundle.BundleServerPluginFacet;
import org.rhq.enterprise.server.plugin.pc.bundle.UnknownRecipeException;
/**
* A bundle server-side plugin component that the server uses to process file template bundles.
*
* @author John Mazzitelli
*/
public class BundleServerPluginComponent implements ServerPluginComponent, BundleServerPluginFacet, ControlFacet {
private final Log log = LogFactory.getLog(BundleServerPluginComponent.class);
private ServerPluginContext context;
public void initialize(ServerPluginContext context) throws Exception {
this.context = context;
log.debug("The filetemplate bundle plugin has been initialized!!! : " + this);
}
public void start() {
log.debug("The filetemplate bundle plugin has started!!! : " + this);
}
public void stop() {
log.debug("The filetemplate bundle plugin has stopped!!! : " + this);
}
public void shutdown() {
log.debug("The filetemplate bundle plugin has been shut down!!! : " + this);
}
public RecipeParseResults parseRecipe(String recipe) throws UnknownRecipeException, Exception {
RecipeParser parser = new RecipeParser();
RecipeContext recipeContext = new RecipeContext(recipe);
try {
parser.parseRecipe(recipeContext);
} catch (Exception e) {
if (recipeContext.isUnknownRecipe()) {
throw new UnknownRecipeException("Not a valid file template recipe");
}
throw e;
}
DeploymentProperties bundleMetadata = recipeContext.getDeploymentProperties();
Set<String> bundleFileNames = new HashSet<String>();
Map<String, String> deployFiles = recipeContext.getDeployFiles();
bundleFileNames.addAll(deployFiles.keySet());
Set<String> scriptFiles = recipeContext.getScriptFiles();
bundleFileNames.addAll(scriptFiles);
Set<String> files = recipeContext.getFiles().keySet();
bundleFileNames.addAll(files);
ConfigurationDefinition configDef = null;
if (recipeContext.getReplacementVariables() != null) {
configDef = new ConfigurationDefinition("replacementVariables", null);
for (String replacementVar : recipeContext.getReplacementVariables()) {
PropertyDefinitionSimple prop = new PropertyDefinitionSimple(replacementVar,
"Needed by bundle recipe.", false, PropertySimpleType.STRING);
prop.setDisplayName(replacementVar);
prop.setDefaultValue(recipeContext.getReplacementVariableDefaultValues().get(replacementVar));
configDef.put(prop);
}
}
RecipeParseResults results = new RecipeParseResults(bundleMetadata, configDef, bundleFileNames);
return results;
}
public BundleDistributionInfo processBundleDistributionFile(File distributionFile) throws UnknownRecipeException,
Exception {
if (null == distributionFile) {
throw new IllegalArgumentException("distributionFile == null");
}
BundleDistributionInfo info = null;
String recipe = null;
RecipeParseResults recipeParseResults = null;
Map<String, File> bundleFiles = null;
// try and parse the recipe, if successful then process the distributionFile completely
RecipeVisitor recipeVisitor = new RecipeVisitor(this, "deploy.txt");
ZipUtil.walkZipFile(distributionFile, recipeVisitor);
recipe = recipeVisitor.getRecipe();
recipeParseResults = recipeVisitor.getResults();
if (null == recipeParseResults) {
// we also want to support the ability to provide just a file template recipe as the distro file
// so see if we can parse it, but note that we don't even bother if its a really big file since
// that's probably not a recipe file and we don't want to risk loading in a huge file in memory
if (distributionFile.length() < 50000L) {
byte[] content = StreamUtil.slurp(new FileInputStream(distributionFile));
recipe = new String(content);
content = null;
recipeParseResults = parseRecipe(recipe); // if it isn't a recipe either, this will throw UnknownRecipeException
} else {
throw new UnknownRecipeException("Not a File Template Bundle");
}
} else {
// if we parsed the recipe, then this is a distribution zip we can deal with, get the bundle file Map
BundleFileVisitor bundleFileVisitor = new BundleFileVisitor(recipeParseResults.getBundleMetadata()
.getBundleName(), recipeParseResults.getBundleFileNames());
ZipUtil.walkZipFile(distributionFile, bundleFileVisitor);
bundleFiles = bundleFileVisitor.getBundleFiles();
}
info = new BundleDistributionInfo(recipe, recipeParseResults, bundleFiles);
return info;
}
public ControlResults invoke(String name, Configuration parameters) {
ControlResults controlResults = new ControlResults();
if (name.equals("testControl")) {
System.out.println("Invoked 'testControl': " + this);
} else {
controlResults.setError("Unknown operation name: " + name);
}
return controlResults;
}
@Override
public String toString() {
if (this.context == null) {
return "<no context>";
}
StringBuilder str = new StringBuilder();
str.append("plugin-key=").append(this.context.getPluginEnvironment().getPluginKey()).append(",");
str.append("plugin-url=").append(this.context.getPluginEnvironment().getPluginUrl()).append(']');
return str.toString();
}
private static class RecipeVisitor implements ZipUtil.ZipEntryVisitor {
private RecipeParseResults results = null;
private String recipeName = null;
private BundleServerPluginFacet facet = null;
private String recipe = null;
public RecipeVisitor(BundleServerPluginFacet facet, String recipeName) {
this.facet = facet;
this.recipeName = recipeName;
}
public boolean visit(ZipEntry entry, ZipInputStream stream) throws Exception {
if (this.recipeName.equalsIgnoreCase(entry.getName())) {
// this should be safe downcast, recipes are not that big
int contentSize = (int) entry.getSize();
ByteArrayOutputStream out = new ByteArrayOutputStream((contentSize > 0) ? contentSize : 32768);
StreamUtil.copy(stream, out, false);
this.recipe = new String(out.toByteArray());
out = null; // no need for this anymore, help out GC
this.results = this.facet.parseRecipe(this.recipe);
return false; // whether we parsed it or not, we found the file we are looking for so stop walking
}
return true;
}
public RecipeParseResults getResults() {
return results;
}
public String getRecipe() {
return recipe;
}
}
private static class BundleFileVisitor implements ZipUtil.ZipEntryVisitor {
private Set<String> bundleFileNames;
private Map<String, File> bundleFiles;
private File tmpDir;
public BundleFileVisitor(String bundleName, Set<String> bundleFileNames) throws IOException {
this.bundleFileNames = bundleFileNames;
this.bundleFiles = new HashMap<String, File>(bundleFileNames.size());
this.tmpDir = FileUtil.createTempDirectory("file-template-bundle", ".dir", null);
}
public boolean visit(ZipEntry entry, ZipInputStream stream) throws Exception {
if (bundleFileNames.contains(entry.getName())) {
File bundleFile = new File(tmpDir, entry.getName());
bundleFile.getParentFile().mkdirs();
FileOutputStream fos = null;
try {
fos = new FileOutputStream(bundleFile);
StreamUtil.copy(stream, fos, false);
} finally {
if (null != fos) {
try {
fos.close();
} catch (Exception e) {
//
}
}
}
this.bundleFiles.put(entry.getName(), bundleFile);
}
return true;
}
public Map<String, File> getBundleFiles() {
return bundleFiles;
}
}
}