/**
*
*/
package org.rhq.test.shrinkwrap;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import javax.xml.stream.EventFilter;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchivePath;
import org.jboss.shrinkwrap.api.Node;
import org.jboss.shrinkwrap.api.asset.ArchiveAsset;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.asset.ClassLoaderAsset;
import org.jboss.shrinkwrap.api.asset.FileAsset;
import org.jboss.shrinkwrap.api.asset.UrlAsset;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.impl.base.Validate;
import org.jboss.shrinkwrap.impl.base.container.ContainerBase;
import org.jboss.shrinkwrap.impl.base.path.BasicPath;
import org.rhq.core.clientapi.agent.metadata.PluginDependencyGraph;
import org.rhq.core.util.stream.StreamUtil;
/**
* @author Lukas Krejci
*/
public class RhqAgentPluginArchiveImpl extends ContainerBase<RhqAgentPluginArchive> implements RhqAgentPluginArchive {
private static final Log LOG = LogFactory.getLog(RhqAgentPluginArchiveImpl.class);
/**
* Path to the manifests inside of the Archive.
*/
private static final ArchivePath PATH_MANIFEST = new BasicPath("META-INF");
/**
* Path to the resources inside of the Archive.
*/
private static final ArchivePath PATH_RESOURCE = new BasicPath("/");
/**
* Path to the classes inside of the Archive.
*/
private static final ArchivePath PATH_CLASSES = new BasicPath("/");
/**
* Path to the libraries inside the RHQ agent plugin archive
*/
private static final ArchivePath LIBRARY_PATH = new BasicPath("/lib");
private static final ArchivePath REQUIRED_PLUGINS_PATH = new BasicPath(PATH_MANIFEST, "######REQUIRED_PLUGINS");
private static final ArchivePath PLUGIN_DESCRIPTOR_PATH = new BasicPath(PATH_MANIFEST, "rhq-plugin.xml");
private static class PluginDep extends PluginDependencyGraph.PluginDependency {
public String name;
/**
* @param name
*/
public PluginDep(String name) {
super(name);
this.name = name;
}
public PluginDep(String name, boolean useClasses, boolean required) {
super(name, useClasses, required);
this.name = name;
}
}
public RhqAgentPluginArchiveImpl(Archive<?> delegate) {
super(RhqAgentPluginArchive.class, delegate);
}
@Override
public ArchivePath getRequiredPluginsPath() {
return REQUIRED_PLUGINS_PATH;
}
@Override
protected ArchivePath getManifestPath() {
return PATH_MANIFEST;
}
@Override
protected ArchivePath getClassesPath() {
return PATH_CLASSES;
}
@Override
protected ArchivePath getResourcePath() {
return PATH_RESOURCE;
}
@Override
public ArchivePath getLibraryPath() {
return LIBRARY_PATH;
}
@Override
public RhqAgentPluginArchive setPluginDescriptorFromTemplate(String resourceName,
Map<String, String> replacementValues) throws IllegalArgumentException {
Validate.notNull(resourceName, "resourceName should be specified");
Validate.notNull(replacementValues, "replacementValues should be specified");
// get the template text and replace all tokens with their replacement values
String templateXml = new String(StreamUtil.slurp(new ClassLoaderAsset(resourceName).openStream()));
for (Map.Entry<String, String> entry : replacementValues.entrySet()) {
templateXml = templateXml.replace(entry.getKey(), entry.getValue());
}
// Make a new descriptor file with the new template content (with variables replaced) in a tmp directory.
File newDescriptorFile;
try {
newDescriptorFile = File.createTempFile(resourceName.replace(".xml", ""), ".xml");
StreamUtil.copy(new ByteArrayInputStream(templateXml.getBytes()), new FileOutputStream(newDescriptorFile));
} catch (IOException e) {
throw new RuntimeException(e);
}
return setPluginDescriptor(newDescriptorFile);
}
@Override
public RhqAgentPluginArchive setPluginDescriptor(String resourceName) throws IllegalArgumentException {
Validate.notNull(resourceName, "ResourceName should be specified");
return setPluginDescriptor(new ClassLoaderAsset(resourceName));
}
@Override
public RhqAgentPluginArchive setPluginDescriptor(File file) throws IllegalArgumentException {
Validate.notNull(file, "File should be specified");
return setPluginDescriptor(new FileAsset(file));
}
@Override
public RhqAgentPluginArchive setPluginDescriptor(URL url) throws IllegalArgumentException {
Validate.notNull(url, "URL should be specified");
return setPluginDescriptor(new UrlAsset(url));
}
@Override
public RhqAgentPluginArchive setPluginDescriptor(Asset asset) throws IllegalArgumentException {
Validate.notNull(asset, "Asset should be specified");
return add(asset, PLUGIN_DESCRIPTOR_PATH);
}
@Override
public RhqAgentPluginArchive withRequiredPluginsFrom(Collection<? extends Archive<?>> archives)
throws IllegalArgumentException {
delete(REQUIRED_PLUGINS_PATH);
Node descriptor = get(PLUGIN_DESCRIPTOR_PATH);
if (descriptor == null) {
return this;
}
Map<String, Archive<?>> plugins = new HashMap<String, Archive<?>>();
String pluginName = getPluginName(this);
String myPluginName = pluginName;
plugins.put(pluginName, this);
for(Archive<?> ar : archives) {
if (isPlugin(ar)) {
pluginName = getPluginName(ar);
plugins.put(pluginName, ar);
}
}
PluginDependencyGraph pdg = new PluginDependencyGraph();
buildRequiredPlugins(this, plugins, pdg);
for(String pn : pdg.getAllDependencies(myPluginName)) {
Archive<?> p = plugins.get(pn);
add(p, REQUIRED_PLUGINS_PATH, ZipExporter.class);
}
return this;
}
@Override
public RhqAgentPluginArchive withRequiredPluginsFrom(Archive<?>... archives) throws IllegalArgumentException {
return withRequiredPluginsFrom(Arrays.asList(archives));
}
@Override
public List<Archive<?>> getRequiredPlugins() {
Node requiredPluginsRoot = get(REQUIRED_PLUGINS_PATH);
if (requiredPluginsRoot == null) {
return null;
}
List<Archive<?>> ret = new ArrayList<Archive<?>>();
for(Node plugin : requiredPluginsRoot.getChildren()) {
Asset a = plugin.getAsset();
if (a instanceof ArchiveAsset) {
ret.add(((ArchiveAsset) a).getArchive());
}
}
return ret;
}
//use this only in readonly scenarios
@SuppressWarnings("unchecked")
private List<PluginDependencyGraph.PluginDependency> asPdgPd(List<? extends PluginDependencyGraph.PluginDependency> source) {
return (List<PluginDependencyGraph.PluginDependency>) source;
}
private void buildRequiredPlugins(Archive<?> archive, Map<String, Archive<?>> allAvailableArchives, PluginDependencyGraph graph) {
List<PluginDep> requiredPlugins = getRequiredPlugins(archive);
String pluginName = getPluginName(archive);
graph.addPlugin(pluginName, asPdgPd(requiredPlugins));
for(PluginDep pd : requiredPlugins) {
Archive<?> a = allAvailableArchives.get(pd.name);
buildRequiredPlugins(a, allAvailableArchives, graph);
}
}
private static void closeReaderAndStream(XMLEventReader rdr, InputStream str, Archive<?> archive) {
if (rdr != null) {
try {
rdr.close();
} catch (XMLStreamException e) {
LOG.error("Failed to close the XML reader of the plugin descriptor in archive [" + archive + "]", e);
}
}
try {
str.close();
} catch (IOException e) {
LOG.error("Failed to close the input stream of the plugin descriptor in archive [" + archive + "]", e);
}
}
private static List<PluginDep> getRequiredPlugins(Archive<?> archive) {
List<PluginDep> ret = new ArrayList<PluginDep>();
InputStream is = archive.get(PLUGIN_DESCRIPTOR_PATH).getAsset().openStream();
XMLEventReader rdr = null;
try {
XMLInputFactory f = XMLInputFactory.newInstance();
XMLEventReader r = f.createXMLEventReader(is);
rdr = f.createFilteredReader(r, new EventFilter() {
@Override
public boolean accept(XMLEvent event) {
switch (event.getEventType()) {
case XMLEvent.START_ELEMENT:
case XMLEvent.END_DOCUMENT:
return true;
default:
return false;
}
}
});
XMLEvent e;
while ((e = rdr.nextEvent()) != null) {
if (e.getEventType() == XMLEvent.END_DOCUMENT) {
break;
}
StartElement el = e.asStartElement();
if (el.getName().getLocalPart().equals("depends")) {
Attribute pluginAttr = el.getAttributeByName(new QName("plugin"));
if (pluginAttr == null) {
throw new IllegalArgumentException("Couldn't find the 'plugin' attribute on a 'depends' element in the plugin descriptor of plugin '" + archive + "'.");
}
String pluginName = pluginAttr.getValue();
boolean required = true;
boolean useClasses = false;
Attribute requiredAttr = el.getAttributeByName(new QName("required"));
if (requiredAttr != null) {
required = Boolean.parseBoolean(requiredAttr.getValue());
}
Attribute useClassesAttr = el.getAttributeByName(new QName("useClasses"));
if (useClassesAttr != null) {
useClasses = Boolean.parseBoolean(useClassesAttr.getValue());
}
ret.add(new PluginDep(pluginName, useClasses, required));
}
}
return ret;
} catch (XMLStreamException e) {
throw new IllegalArgumentException("Failed to extract the required plugin names out of the RHQ plugin archive [" + archive + "]", e);
} catch (FactoryConfigurationError e) {
throw new IllegalArgumentException("Failed to extract the required plugin name out of the RHQ plugin archive [" + archive + "]", e);
} finally {
closeReaderAndStream(rdr, is, archive);
}
}
private static boolean isPlugin(Archive<?> archive) {
return archive.contains(PLUGIN_DESCRIPTOR_PATH);
}
private static String getPluginName(Archive<?> archive) {
InputStream is = archive.get(PLUGIN_DESCRIPTOR_PATH).getAsset().openStream();
XMLEventReader rdr = null;
try {
rdr = XMLInputFactory.newInstance().createXMLEventReader(is);
XMLEvent event = null;
while (rdr.hasNext()) {
event = rdr.nextEvent();
if (event.getEventType() == XMLEvent.START_ELEMENT) {
break;
}
}
StartElement startElement = event.asStartElement();
String tagName = startElement.getName().getLocalPart();
if (!"plugin".equals(tagName)) {
throw new IllegalArgumentException("Illegal start tag found in the plugin descriptor. Expected 'plugin' but found '" + tagName + "' in the plugin '" + archive + "'.");
}
Attribute nameAttr = startElement.getAttributeByName(new QName("name"));
if (nameAttr == null) {
throw new IllegalArgumentException("Couldn't find the name attribute on the plugin tag in the plugin descriptor of plugin '" + archive + "'.");
}
return nameAttr.getValue();
} catch (XMLStreamException e) {
throw new IllegalArgumentException("Failed to extract the plugin name out of the RHQ plugin archive [" + archive + "]", e);
} catch (FactoryConfigurationError e) {
throw new IllegalArgumentException("Failed to extract the plugin name out of the RHQ plugin archive [" + archive + "]", e);
} finally {
closeReaderAndStream(rdr, is, archive);
}
}
}