modules/core/client-api/src/main/java/org/rhq/core/clientapi/agent/metadata/PluginMetadataManager.java
| 50 +++
modules/core/client-api/src/main/java/org/rhq/core/clientapi/agent/metadata/PluginMetadataParser.java
| 78 +++++
modules/core/client-api/src/main/resources/rhq-plugin.xsd
| 46 +++
modules/core/client-api/src/test/java/org/rhq/core/clientapi/agent/metadata/test/MetadataManagerTest.java
| 22 +
modules/core/client-api/src/test/java/org/rhq/core/clientapi/agent/metadata/test/PluginMetadataParserTest.java
| 83 ++++++
modules/core/client-api/src/test/resources/metadata-manager-test-2.xml
| 7
modules/core/client-api/src/test/resources/metadata-manager-test-3.xml
| 9
modules/core/plugin-api/src/main/java/org/rhq/core/pluginapi/inventory/ResourceDiscoveryCallback.java
| 43 +++
modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/DiscoveryCallbackTest.java
| 135 ++++++++++
modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/discoverycallback/PluginOneCallback.java
| 22 +
modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/discoverycallback/PluginTwoCallback1.java
| 21 +
modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/discoverycallback/PluginTwoCallback2.java
| 19 +
modules/core/plugin-container-itest/src/test/resources/discovery-callback1.xml
| 21 +
modules/core/plugin-container-itest/src/test/resources/discovery-callback2.xml
| 24 +
modules/core/plugin-container/src/main/java/org/rhq/core/pc/inventory/InventoryManager.java
| 47 +++
modules/core/plugin-container/src/main/java/org/rhq/core/pc/plugin/PluginComponentFactory.java
| 24 +
16 files changed, 643 insertions(+), 8 deletions(-)
New commits:
commit af523918b7e179c15bcae948cb8e7102e5072732
Author: John Mazzitelli <mazz(a)redhat.com>
Date: Fri Aug 30 15:16:58 2013 -0400
BZ 988735 - discovery callbacks that allow extension plugins to alter discovered
details from some resource type
diff --git
a/modules/core/client-api/src/main/java/org/rhq/core/clientapi/agent/metadata/PluginMetadataManager.java
b/modules/core/client-api/src/main/java/org/rhq/core/clientapi/agent/metadata/PluginMetadataManager.java
index e75b7df..b9ee838 100644
---
a/modules/core/client-api/src/main/java/org/rhq/core/clientapi/agent/metadata/PluginMetadataManager.java
+++
b/modules/core/client-api/src/main/java/org/rhq/core/clientapi/agent/metadata/PluginMetadataManager.java
@@ -82,6 +82,11 @@ public class PluginMetadataManager {
private Set<ResourceType> ignoredResourceTypes = null;
private Object disabledIgnoredTypesLock = new Object(); // used when accessing
disabled and ignored collections
+ // these define the discovery callbacks per resource type. The key is the resource
type whose discovered details
+ // need to be funneled through callbacks. The value is a map whose key is plugin
names and whose values are
+ // discovery callback implementation classes defined in the plugins.
+ private Map<ResourceType, Map<String, List<String>>>
discoveryCallbacks = new HashMap<ResourceType, Map<String,
List<String>>>();
+
public PluginMetadataManager() {
}
@@ -203,6 +208,18 @@ public class PluginMetadataManager {
findDisabledResourceTypesInAllPlugins();
}
+ // squirrel away all the discovery callbacks
+ Map<ResourceType, List<String>> discoveryCallbacksMap =
parser.getDiscoveryCallbackClasses();
+ if (discoveryCallbacksMap != null) {
+ for (Map.Entry<ResourceType, List<String>> entry :
discoveryCallbacksMap.entrySet()) {
+ ResourceType resourceType = entry.getKey();
+ for (String className : entry.getValue()) {
+ addDiscoveryCallbackClassName(resourceType,
pluginDescriptor.getName(), className);
+ }
+ }
+ }
+
+ // return the top root types from the descriptor
Set<ResourceType> rootTypes = parser.getRootResourceTypes();
return rootTypes;
@@ -491,4 +508,37 @@ public class PluginMetadataManager {
}
return;
}
+
+ /**
+ * Given a resource type, this will return any discovery callbacks that are required
to be invoked
+ * when that resource type is being discovered.
+ * @param resourceType the type whose discovery callbacks should be returned
+ * @return the collection of callbacks, grouped by the plugins that defined them (may
be null)
+ */
+ public Map<String, List<String>> getDiscoveryCallbacks(ResourceType
resourceType) {
+ synchronized (discoveryCallbacks) {
+ Map<String, List<String>> map =
discoveryCallbacks.get(resourceType);
+ return map;
+ }
+ }
+
+ private void addDiscoveryCallbackClassName(ResourceType resourceType, String
pluginName, String className) {
+ synchronized (discoveryCallbacks) {
+ Map<String, List<String>> map =
discoveryCallbacks.get(resourceType);
+ if (map == null) {
+ map = new HashMap<String, List<String>>(1);
+ discoveryCallbacks.put(resourceType, map);
+ }
+
+ List<String> callbackListForPlugin = map.get(pluginName);
+ if (callbackListForPlugin == null) {
+ callbackListForPlugin = new ArrayList<String>(1);
+ map.put(pluginName, callbackListForPlugin);
+ }
+
+ callbackListForPlugin.add(className);
+ }
+
+ return;
+ }
}
diff --git
a/modules/core/client-api/src/main/java/org/rhq/core/clientapi/agent/metadata/PluginMetadataParser.java
b/modules/core/client-api/src/main/java/org/rhq/core/clientapi/agent/metadata/PluginMetadataParser.java
index d5ff2ef..f1bce29 100644
---
a/modules/core/client-api/src/main/java/org/rhq/core/clientapi/agent/metadata/PluginMetadataParser.java
+++
b/modules/core/client-api/src/main/java/org/rhq/core/clientapi/agent/metadata/PluginMetadataParser.java
@@ -37,6 +37,8 @@ import org.apache.commons.logging.LogFactory;
import org.rhq.core.clientapi.descriptor.plugin.Bundle;
import org.rhq.core.clientapi.descriptor.plugin.BundleTargetDescriptor;
import org.rhq.core.clientapi.descriptor.plugin.ContentDescriptor;
+import org.rhq.core.clientapi.descriptor.plugin.DiscoveryCallbacksType;
+import org.rhq.core.clientapi.descriptor.plugin.DiscoveryTypeCallbackType;
import org.rhq.core.clientapi.descriptor.plugin.DriftDescriptor;
import org.rhq.core.clientapi.descriptor.plugin.EventDescriptor;
import org.rhq.core.clientapi.descriptor.plugin.MetricDescriptor;
@@ -83,10 +85,11 @@ public class PluginMetadataParser {
private Set<ResourceType> rootResourceTypes = new
LinkedHashSet<ResourceType>();
- // TODO: this isn't the most elegant... should we put these in the domain
objects? or perhaps build another place for them to live?
private Map<ResourceType, String> discoveryClasses = new
HashMap<ResourceType, String>();
private Map<ResourceType, String> componentClasses = new
HashMap<ResourceType, String>();
+ private Map<ResourceType, List<String>> discoveryCallbackClasses = null;
+
// a map keyed on plugin name that contains the parsers for all other known plugin
descriptors
// this map is managed by this parser's PluginMetadataManager and is how the
manager shares information
// from other plugins to this parser instance
@@ -120,6 +123,20 @@ public class PluginMetadataParser {
return this.rootResourceTypes;
}
+ /**
+ * This returns all resource types that this plugin defines discovery callbacks for.
+ * When the resource types' discovery is run, the details will be funneled
through the discovery
+ * callbacks to give this plugin a chance to alter the discovered details.
+ * Note that it is very possible that the resource type keys are types that are NOT
defined
+ * by the plugin associated with this parser. The resource types will be defined
either in
+ * this plugin or one of its dependencies.
+ *
+ * @return map of all types that have one or more discovery callbacks defined. May be
null.
+ */
+ public Map<ResourceType, List<String>> getDiscoveryCallbackClasses() {
+ return discoveryCallbackClasses;
+ }
+
public void parseDescriptor() throws InvalidPluginDescriptorException {
ResourceType type;
@@ -147,6 +164,65 @@ public class PluginMetadataParser {
}
}
+ // find any declared discovery callbacks now - do this at the end in
+ // case we are defining callbacks on types in our own plugin
+ parseDiscoveryCallbacks();
+
+ return;
+ }
+
+ private void parseDiscoveryCallbacks() throws InvalidPluginDescriptorException {
+ DiscoveryCallbacksType jaxbCallbacks = pluginDescriptor.getDiscoveryCallbacks();
+ if (jaxbCallbacks == null) {
+ return;
+ }
+
+ List<DiscoveryTypeCallbackType> jaxbCallbacksList =
jaxbCallbacks.getTypeCallback();
+ if (jaxbCallbacksList == null || jaxbCallbacksList.isEmpty()) {
+ return;
+ }
+
+ for (DiscoveryTypeCallbackType jaxbCallback : jaxbCallbacksList) {
+ String plugin = jaxbCallback.getPlugin();
+ String type = jaxbCallback.getType();
+ String callbackClass = jaxbCallback.getCallbackClass();
+
+ LOG.debug("Plugin [" + pluginDescriptor.getName() + "] defined
a discovery class [" + callbackClass
+ + "] to listen for discovery details for type [{" + plugin +
"}" + type + "].");
+
+ if (callbackClass == null || callbackClass.length() == 0) {
+ // this should never happen - the XML parser should have failed to even
get this far
+ throw new InvalidPluginDescriptorException("Missing discovery class
in plugin ["
+ + pluginDescriptor.getName() + "] -> {" + plugin +
"}" + type);
+ }
+
+ if (plugin == null || plugin.length() == 0 || type == null || type.length()
== 0) {
+ // this should never happen - the XML parser should have failed to even
get this far
+ throw new InvalidPluginDescriptorException("Both plugin and type
must be defined for discovery callbacks in plugin ["
+ + pluginDescriptor.getName() + "] -> {" + plugin +
"}" + type + ":" + callbackClass);
+ }
+
+ ResourceType resourceType = getResourceTypeFromPlugin(type, plugin);
+ if (resourceType == null) {
+ LOG.warn("There is no type named [" + type + "] from a
plugin named [" + plugin
+ + "]. This is probably because that plugin is missing. The
discovery callback will be ignored");
+ continue;
+ }
+
+ if (discoveryCallbackClasses == null) {
+ discoveryCallbackClasses = new HashMap<ResourceType,
List<String>>();
+ }
+
+ List<String> callbacksList =
discoveryCallbackClasses.get(resourceType);
+ if (callbacksList == null) {
+ callbacksList = new ArrayList<String>(1);
+ discoveryCallbackClasses.put(resourceType, callbacksList);
+ }
+
+ String fqcn =
getFullyQualifiedComponentClassName(pluginDescriptor.getPackage(), callbackClass);
+ callbacksList.add(fqcn);
+ }
+
return;
}
diff --git a/modules/core/client-api/src/main/resources/rhq-plugin.xsd
b/modules/core/client-api/src/main/resources/rhq-plugin.xsd
index 2d50b8e..ee873aa 100644
--- a/modules/core/client-api/src/main/resources/rhq-plugin.xsd
+++ b/modules/core/client-api/src/main/resources/rhq-plugin.xsd
@@ -57,6 +57,18 @@
</xs:complexType>
</xs:element>
+ <xs:element ref="rhq:discovery-callbacks"
minOccurs="0" maxOccurs="1">
+ <xs:annotation>
+ <xs:documentation>
+ Defines the types and discovery classes whose discovered resources
must be
+ funneled through this plugin's discovery classback classes.
+ </xs:documentation>
+ <xs:appinfo>
+ <jaxb:property name="discoveryCallbacks" />
+ </xs:appinfo>
+ </xs:annotation>
+ </xs:element>
+
<xs:element ref="rhq:platform" minOccurs="0"
maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>
@@ -1154,4 +1166,38 @@
</xs:attribute>
</xs:attributeGroup>
+ <xs:element name="discovery-callbacks"
type="rhq:DiscoveryCallbacksType">
+ <xs:annotation>
+ <xs:documentation>A set of discovery callbacks this plugin defines to help
with resource discovery.</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:complexType name="DiscoveryCallbacksType">
+ <xs:sequence>
+ <xs:element name="type-callback"
type="rhq:DiscoveryTypeCallbackType" maxOccurs="unbounded"
minOccurs="1">
+ <xs:annotation>
+ <xs:documentation>A discovery callback to help with discovery for a
specific resource type.</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="DiscoveryTypeCallbackType">
+ <xs:attribute name="type" type="xs:string"
use="required">
+ <xs:annotation>
+ <xs:documentation>When a discovery component detects this resource
type, the discovered details will be processed by the discovery
callback.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="plugin" type="xs:string"
use="required">
+ <xs:annotation>
+ <xs:documentation>The plugin that defines the named resource type.
Must be either this plugin or one of its dependencies.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="callbackClass" type="rhq:classNameType"
use="required">
+ <xs:annotation>
+ <xs:documentation>The class name of this discovery callback
implementation.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
</xs:schema>
diff --git
a/modules/core/client-api/src/test/java/org/rhq/core/clientapi/agent/metadata/test/MetadataManagerTest.java
b/modules/core/client-api/src/test/java/org/rhq/core/clientapi/agent/metadata/test/MetadataManagerTest.java
index fbb6b1b..c6f3dc2 100644
---
a/modules/core/client-api/src/test/java/org/rhq/core/clientapi/agent/metadata/test/MetadataManagerTest.java
+++
b/modules/core/client-api/src/test/java/org/rhq/core/clientapi/agent/metadata/test/MetadataManagerTest.java
@@ -23,6 +23,7 @@
package org.rhq.core.clientapi.agent.metadata.test;
import java.net.URL;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -177,6 +178,27 @@ public class MetadataManagerTest {
assert testD.getBundleType().getResourceType().equals(testD);
}
+ @Test(dependsOnMethods = "loadPluginDescriptorTest3")
+ public void testDiscoveryCallbackDefinitions() {
+ ResourceType serverAType = this.metadataManager.getType("Server A",
"Test1");
+ ResourceType serverBType = this.metadataManager.getType("Extension Server
B", "Test2");
+ assert serverAType != null : "Where's Server A?";
+ assert serverBType != null : "Where's Extension Server B?";
+
+ Map<String, List<String>> serverACallbacks =
this.metadataManager.getDiscoveryCallbacks(serverAType);
+ assert serverACallbacks.size() == 1 : serverACallbacks;
+ assert serverACallbacks.get("Test3").size() == 2 : serverACallbacks;
+ assert
serverACallbacks.get("Test3").get(0).equals("org.rhq.plugins.test3.DiscoveryCallback1")
: serverACallbacks;
+ assert
serverACallbacks.get("Test3").get(1).equals("org.rhq.plugins.test3.DiscoveryCallbackAnother1")
: serverACallbacks;
+
+ Map<String, List<String>> serverBCallbacks =
this.metadataManager.getDiscoveryCallbacks(serverBType);
+ assert serverBCallbacks.size() == 2 : serverBCallbacks;
+ assert serverBCallbacks.get("Test3").size() == 1 : serverBCallbacks;
+ assert serverBCallbacks.get("Test2").size() == 1 : serverBCallbacks;
+ assert
serverBCallbacks.get("Test3").get(0).equals("org.rhq.plugins.test3.DiscoveryCallback2")
: serverBCallbacks;
+ assert
serverBCallbacks.get("Test2").get(0).equals("org.rhq.plugins.test2.DiscoveryCallbackTest2")
: serverBCallbacks;
+ }
+
private ResourceType getResourceType(ResourceType typeToGet) {
for (ResourceType type : metadataManager.getAllTypes()) {
if (type.equals(typeToGet)) {
diff --git
a/modules/core/client-api/src/test/java/org/rhq/core/clientapi/agent/metadata/test/PluginMetadataParserTest.java
b/modules/core/client-api/src/test/java/org/rhq/core/clientapi/agent/metadata/test/PluginMetadataParserTest.java
index 5e08676..1c86934 100644
---
a/modules/core/client-api/src/test/java/org/rhq/core/clientapi/agent/metadata/test/PluginMetadataParserTest.java
+++
b/modules/core/client-api/src/test/java/org/rhq/core/clientapi/agent/metadata/test/PluginMetadataParserTest.java
@@ -51,13 +51,94 @@ import static org.testng.Assert.assertTrue;
public class PluginMetadataParserTest {
@Test
+ void discoveryCallback() throws Exception {
+ PluginDescriptor pluginDescriptor = toPluginDescriptor("" + //
+ "<plugin name='TestServer' displayName='Test Server'
package='org.rhq.plugins.test'" + //
+ "
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'"
+ //
+ " xmlns='urn:xmlns:rhq-plugin'>" + //
+ " <discovery-callbacks>" + //
+ " <type-callback type='testServerType'
plugin='TestServer' callbackClass='TestCallback' />" + //
+ " </discovery-callbacks>" + //
+ " <server name='testServerType'" + //
+ " class='org.rhq.plugins.test.TestServer'" + //
+ "
discovery='org.rhq.plugins.test.TestServerDiscoveryComponent'/>" + //
+ "</plugin>");
+
+ Map<String, PluginMetadataParser> parsersByPlugin = new HashMap<String,
PluginMetadataParser>(0);
+ PluginMetadataParser parser = new PluginMetadataParser(pluginDescriptor,
parsersByPlugin);
+
+ Map<ResourceType, List<String>> map =
parser.getDiscoveryCallbackClasses();
+ assertEquals(map.size(), 1, "Should have one discovery callback: " +
map);
+ ResourceType rt = new ResourceType("testServerType",
"TestServer", null, null);
+ assertEquals(map.get(rt).get(0), "org.rhq.plugins.test.TestCallback",
"incorrect classname in map: " + map);
+
+ // in a second plugin, define a discovery callback that points back to a type in
the first plugin
+ PluginDescriptor pluginDescriptor2 = toPluginDescriptor("" + //
+ "<plugin name='TestServer2' displayName='Test
Server' package='org.rhq.plugins.test2'" + //
+ "
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'" + //
+ " xmlns='urn:xmlns:rhq-plugin'>" + //
+ " <discovery-callbacks>" + //
+ " <type-callback type='testServerType'
plugin='TestServer' callbackClass='TestCallback2' />" + //
+ " </discovery-callbacks>" + //
+ " <server name='testServerType2'" + //
+ " class='org.rhq.plugins.test2.TestServer2'" +
//
+ "
discovery='org.rhq.plugins.test2.TestServerDiscoveryComponent2'/>" + //
+ "</plugin>");
+
+ parsersByPlugin.put("TestServer", parser);
+ PluginMetadataParser parser2 = new PluginMetadataParser(pluginDescriptor2,
parsersByPlugin);
+ map = parser2.getDiscoveryCallbackClasses();
+ assertEquals(map.size(), 1, "Should have one discovery callback: " +
map);
+ rt = new ResourceType("testServerType", "TestServer", null,
null);
+ assertEquals(map.get(rt).get(0), "org.rhq.plugins.test2.TestCallback2",
"incorrect classname in map: " + map);
+
+ // define multiple callbacks to multiple plugins
+ PluginDescriptor pluginDescriptor3 = toPluginDescriptor("" + //
+ "<plugin name='TestServer3' displayName='Test
Server' package='org.rhq.plugins.test3'" + //
+ "
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'" + //
+ " xmlns='urn:xmlns:rhq-plugin'>" + //
+ " <discovery-callbacks>" + //
+ " <type-callback type='testServerType'
plugin='TestServer' callbackClass='TestCallback31' />" + //
+ " <type-callback type='testServerType2'
plugin='TestServer2' callbackClass='TestCallback32' />" + //
+ " <type-callback type='testServerType3'
plugin='TestServer3' callbackClass='TestCallback33' />" + //
+ " <type-callback type='testServerType3'
plugin='TestServer3' callbackClass='TestCallback34' />" + //
+ " </discovery-callbacks>" + //
+ " <server name='testServerType3'" + //
+ " class='org.rhq.plugins.test3.TestServer3'" +
//
+ "
discovery='org.rhq.plugins.test3.TestServerDiscoveryComponent3'/>" + //
+ "</plugin>");
+
+ parsersByPlugin.put("TestServer2", parser2);
+ PluginMetadataParser parser3 = new PluginMetadataParser(pluginDescriptor3,
parsersByPlugin);
+ map = parser3.getDiscoveryCallbackClasses();
+ assertEquals(map.size(), 3, "Should have three keys in discovery callbacks
map: " + map);
+
+ rt = new ResourceType("testServerType", "TestServer", null,
null);
+ List<String> list = map.get(rt);
+ assertEquals(list.size(), 1);
+ assertEquals(list.get(0), "org.rhq.plugins.test3.TestCallback31",
"incorrect classname in map: " + map);
+
+ rt = new ResourceType("testServerType2", "TestServer2", null,
null);
+ list = map.get(rt);
+ assertEquals(list.size(), 1);
+ assertEquals(list.get(0), "org.rhq.plugins.test3.TestCallback32",
"incorrect classname in map: " + map);
+
+ rt = new ResourceType("testServerType3", "TestServer3", null,
null);
+ list = map.get(rt);
+ assertEquals(list.size(), 2);
+ assertEquals(list.get(0), "org.rhq.plugins.test3.TestCallback33",
"incorrect classname in map: " + map);
+ assertEquals(list.get(1), "org.rhq.plugins.test3.TestCallback34",
"incorrect classname in map: " + map);
+
+ }
+
+ @Test
void allTypesShouldHaveOneElementForDescriptorWithOnlyOneResourceType() throws
Exception {
PluginDescriptor pluginDescriptor = toPluginDescriptor("" + //
"<plugin name='TestServer' displayName='Test Server'
package='org.rhq.plugins.test'" + //
"
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'"
+ //
" xmlns='urn:xmlns:rhq-plugin'>" + //
" <server name='testServer'" + //
- " class='org.rhq.plugins.test.TestServer'" + //
+ " class='org.rhq.plugins.test.TestServer'" + //
"
discovery='org.rhq.plugins.test.TestServerDiscoveryComponent'/>" + //
"</plugin>");
diff --git a/modules/core/client-api/src/test/resources/metadata-manager-test-2.xml
b/modules/core/client-api/src/test/resources/metadata-manager-test-2.xml
index f19b2f4..144ffcc 100644
--- a/modules/core/client-api/src/test/resources/metadata-manager-test-2.xml
+++ b/modules/core/client-api/src/test/resources/metadata-manager-test-2.xml
@@ -3,7 +3,12 @@
xmlns="urn:xmlns:rhq-plugin">
<depends plugin="Test1" />
- <server name="Extension Server B" discovery="ServerBComponent"
class="ServerBServerComponent"
+
+ <discovery-callbacks>
+ <type-callback type="Extension Server B" plugin="Test2"
callbackClass="DiscoveryCallbackTest2"/>
+ </discovery-callbacks>
+
+ <server name="Extension Server B" discovery="ServerBComponent"
class="ServerBServerComponent"
description="Extension Server B Test Server"
sourcePlugin="Test1" sourceType="Server A">
diff --git a/modules/core/client-api/src/test/resources/metadata-manager-test-3.xml
b/modules/core/client-api/src/test/resources/metadata-manager-test-3.xml
index 48cd715..41907cc 100644
--- a/modules/core/client-api/src/test/resources/metadata-manager-test-3.xml
+++ b/modules/core/client-api/src/test/resources/metadata-manager-test-3.xml
@@ -1,9 +1,16 @@
-<plugin name="Test3" displayName="Test Two"
package="org.rhq.plugins.test2"
+<plugin name="Test3" displayName="Test Three"
package="org.rhq.plugins.test3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="urn:xmlns:rhq-plugin">
<depends plugin="Test1" />
<depends plugin="Test2" />
+
+ <discovery-callbacks>
+ <type-callback type="Server A" plugin="Test1"
callbackClass="DiscoveryCallback1"/>
+ <type-callback type="Server A" plugin="Test1"
callbackClass="DiscoveryCallbackAnother1"/>
+ <type-callback type="Extension Server B" plugin="Test2"
callbackClass="DiscoveryCallback2"/>
+ </discovery-callbacks>
+
<server name="Injection C To Server A"
discovery="ServerCComponent" class="ServerCServerComponent"
description="injects child C to server A and server B">
<runs-inside>
diff --git
a/modules/core/plugin-api/src/main/java/org/rhq/core/pluginapi/inventory/ResourceDiscoveryCallback.java
b/modules/core/plugin-api/src/main/java/org/rhq/core/pluginapi/inventory/ResourceDiscoveryCallback.java
new file mode 100644
index 0000000..a1360af
--- /dev/null
+++
b/modules/core/plugin-api/src/main/java/org/rhq/core/pluginapi/inventory/ResourceDiscoveryCallback.java
@@ -0,0 +1,43 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2013 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.core.pluginapi.inventory;
+
+import java.util.Set;
+
+/**
+ * When another discovery component discovers resources, the discovered details can be
funneled through
+ * implementations of this callback interface, thus allowing callbacks to tweek details
of discovered resources.
+ * This is helpful, for example, when one plugin wants to alter the name or description
of some other plugin's
+ * resource type. This is mainly used when writing plugins that cooperate with each
other.
+ */
+public interface ResourceDiscoveryCallback {
+ /**
+ * When a set of resource details have been discovered, those details are passed to
the callback via this method.
+ * The callback can tweek those details as it sees fit or it can simply leave the
details as-is and simply return.
+ *
+ * @param discoveredDetails a set of resource details that were discovered and can be
altered by the callback
+ *
+ * @throws Exception
+ */
+ void discoveredResources(Set<DiscoveredResourceDetails> discoveredDetails)
throws Exception;
+}
\ No newline at end of file
diff --git
a/modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/DiscoveryCallbackTest.java
b/modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/DiscoveryCallbackTest.java
new file mode 100644
index 0000000..4b4b985
--- /dev/null
+++
b/modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/DiscoveryCallbackTest.java
@@ -0,0 +1,135 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2012 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.core.pc.inventory;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.arquillian.testng.Arquillian;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.rhq.core.clientapi.server.discovery.InventoryReport;
+import org.rhq.core.domain.resource.InventoryStatus;
+import org.rhq.core.domain.resource.Resource;
+import org.rhq.core.pc.PluginContainer;
+import org.rhq.core.pc.PluginContainerConfiguration;
+import org.rhq.core.pc.inventory.discoverycallback.PluginOneCallback;
+import org.rhq.core.pc.inventory.discoverycallback.PluginTwoCallback1;
+import org.rhq.core.pc.inventory.discoverycallback.PluginTwoCallback2;
+import org.rhq.core.pc.inventory.testplugin.TestResourceComponent;
+import org.rhq.core.pc.inventory.testplugin.TestResourceDiscoveryComponent;
+import org.rhq.test.arquillian.AfterDiscovery;
+import org.rhq.test.arquillian.BeforeDiscovery;
+import org.rhq.test.arquillian.FakeServerInventory;
+import org.rhq.test.arquillian.MockingServerServices;
+import org.rhq.test.arquillian.RunDiscovery;
+import org.rhq.test.shrinkwrap.RhqAgentPluginArchive;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.util.Set;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.when;
+
+/**
+ * A unit test for testing discovery callbacks.
+ */
+public class DiscoveryCallbackTest extends Arquillian {
+
+ @Deployment(name = "test")
+ @TargetsContainer("pc")
+ public static RhqAgentPluginArchive getTestOnePlugin() {
+ RhqAgentPluginArchive pluginJar1 = ShrinkWrap.create(RhqAgentPluginArchive.class,
"test-discovery-callback1-plugin.jar");
+ RhqAgentPluginArchive pluginJar2 = ShrinkWrap.create(RhqAgentPluginArchive.class,
"test-discovery-callback2-plugin.jar");
+ pluginJar1.setPluginDescriptor("discovery-callback1.xml").addClasses(
+ TestResourceDiscoveryComponent.class, TestResourceComponent.class,
PluginOneCallback.class);
+ pluginJar2.setPluginDescriptor("discovery-callback2.xml").addClasses(
+ TestResourceDiscoveryComponent.class, TestResourceComponent.class,
PluginTwoCallback1.class, PluginTwoCallback2.class);
+ pluginJar2.withRequiredPluginsFrom(pluginJar1);
+ return pluginJar2;
+ }
+
+ @ArquillianResource
+ private MockingServerServices serverServices;
+
+ @ArquillianResource
+ private PluginContainerConfiguration pluginContainerConfiguration;
+
+ @ArquillianResource
+ private PluginContainer pluginContainer;
+
+ private FakeServerInventory fakeServerInventory;
+
+ private FakeServerInventory.CompleteDiscoveryChecker discoveryCompleteChecker;
+
+ @BeforeDiscovery
+ public void resetServerServices() throws Exception {
+ // Set up our fake server discovery ServerService, which will auto-import all
Resources in reports it receives.
+ serverServices.resetMocks();
+ fakeServerInventory = new FakeServerInventory();
+ discoveryCompleteChecker =
fakeServerInventory.createAsyncDiscoveryCompletionChecker(2);
+
when(serverServices.getDiscoveryServerService().mergeInventoryReport(any(InventoryReport.class))).then(
+ fakeServerInventory.mergeInventoryReport(InventoryStatus.COMMITTED));
+ }
+
+ @AfterDiscovery
+ public void waitForAsyncDiscoveries() throws Exception {
+ if (discoveryCompleteChecker != null) {
+ discoveryCompleteChecker.waitForDiscoveryComplete(10000);
+ }
+ }
+
+ @RunDiscovery
+ @Test(groups = "pc.itest.discoverycallbacks", priority = 20) // setting the
group is important! otherwise, other tests will fail
+ public void testDiscoveryCallbacks() throws Exception {
+ // make sure our inventory is as we expect it to be
+ validatePluginContainerInventory();
+ }
+
+ private void validatePluginContainerInventory() throws Exception {
+ System.out.println("Validating PC inventory...");
+
+ Resource platform = pluginContainer.getInventoryManager().getPlatform();
+ Assert.assertNotNull(platform);
+ Assert.assertEquals(platform.getInventoryStatus(), InventoryStatus.COMMITTED);
+
+ Set<Resource> servers = platform.getChildResources();
+ Assert.assertNotNull(servers);
+ Assert.assertEquals(servers.size(), 2);
+
+ for (Resource server : servers) {
+ if (server.getResourceType().getName().equals("TestServerOne")) {
+ Assert.assertEquals(server.getName(),
"PluginTwoCallback1:name");
+ Assert.assertEquals(server.getVersion(),
"PluginTwoCallback1:1.0");
+
Assert.assertEquals(server.getPluginConfiguration().getSimpleValue("TestServerOne.prop1"),
"PluginOneCallback:prop1");
+
Assert.assertEquals(server.getPluginConfiguration().getSimpleValue("TestServerOne.prop2"),
"PluginTwoCallback1:prop2");
+ } else if
(server.getResourceType().getName().equals("TestServerTwo")) {
+ Assert.assertEquals(server.getName(),
"PluginTwoCallback2:name");
+ Assert.assertEquals(server.getVersion(),
"PluginTwoCallback2:1.0");
+
Assert.assertEquals(server.getPluginConfiguration().getSimpleValue("TestServerTwo.prop1"),
null);
+
Assert.assertEquals(server.getPluginConfiguration().getSimpleValue("TestServerTwo.prop2"),
"PluginTwoCallback2:prop2");
+ } else {
+ assert false : "got a resource we were not expecting: " +
server;
+ }
+ }
+
+ System.out.println("PC inventory validated successfully!");
+ }
+
+}
diff --git
a/modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/discoverycallback/PluginOneCallback.java
b/modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/discoverycallback/PluginOneCallback.java
new file mode 100644
index 0000000..6b24987
--- /dev/null
+++
b/modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/discoverycallback/PluginOneCallback.java
@@ -0,0 +1,22 @@
+package org.rhq.core.pc.inventory.discoverycallback;
+
+import org.rhq.core.domain.configuration.PropertySimple;
+import org.rhq.core.pluginapi.inventory.DiscoveredResourceDetails;
+import org.rhq.core.pluginapi.inventory.ResourceDiscoveryCallback;
+
+import java.util.Set;
+
+public class PluginOneCallback implements ResourceDiscoveryCallback{
+ @Override
+ public void discoveredResources(Set<DiscoveredResourceDetails>
discoveredDetails) throws Exception {
+ // we know our test discovery component detects one singleton server, let's
tweek the details.
+ // note that plugin 2 (the one that depends our plugin) will also have a callback
and will
+ // have a chance to overwrite what we do here. Our test code will test that the
child plugin
+ // callback in that plugin's callback (PluginTwoCallback1) can tweek what we
do here.
+ DiscoveredResourceDetails details = discoveredDetails.iterator().next();
+ details.setResourceName("This will be overwritten by plugin two's test
callback, so this string doesn't matter");
+ details.setResourceVersion("This will be overwritten by plugin two's
test callback, so this string doesn't matter");
+ details.getPluginConfiguration().put(new
PropertySimple("TestServerOne.prop1", "PluginOneCallback:prop1"));
+ System.out.println("!!!!!!!!!!!!!!" + this.getClass().getName() +
"==>" + discoveredDetails);
+ }
+}
diff --git
a/modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/discoverycallback/PluginTwoCallback1.java
b/modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/discoverycallback/PluginTwoCallback1.java
new file mode 100644
index 0000000..e346b5a
--- /dev/null
+++
b/modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/discoverycallback/PluginTwoCallback1.java
@@ -0,0 +1,21 @@
+package org.rhq.core.pc.inventory.discoverycallback;
+
+import org.rhq.core.domain.configuration.PropertySimple;
+import org.rhq.core.pluginapi.inventory.DiscoveredResourceDetails;
+import org.rhq.core.pluginapi.inventory.ResourceDiscoveryCallback;
+
+import java.util.Set;
+
+public class PluginTwoCallback1 implements ResourceDiscoveryCallback{
+ @Override
+ public void discoveredResources(Set<DiscoveredResourceDetails>
discoveredDetails) throws Exception {
+ // the resource was discovered, and our plugin one's callback has tweeked
these details, but now we can
+ // further tweek the details here
+ DiscoveredResourceDetails details = discoveredDetails.iterator().next();
+ details.setResourceName("PluginTwoCallback1:name");
+ details.setResourceVersion("PluginTwoCallback1:1.0");
+ // notice we do not touch plugin config property TestServerOne.prop1, let the
first plugin's callback touch it
+ details.getPluginConfiguration().put(new
PropertySimple("TestServerOne.prop2", "PluginTwoCallback1:prop2"));
+ System.out.println("!!!!!!!!!!!!!!" + this.getClass().getName() +
"==>" + discoveredDetails);
+ }
+}
diff --git
a/modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/discoverycallback/PluginTwoCallback2.java
b/modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/discoverycallback/PluginTwoCallback2.java
new file mode 100644
index 0000000..fdcab1c
--- /dev/null
+++
b/modules/core/plugin-container-itest/src/test/java/org/rhq/core/pc/inventory/discoverycallback/PluginTwoCallback2.java
@@ -0,0 +1,19 @@
+package org.rhq.core.pc.inventory.discoverycallback;
+
+import org.rhq.core.domain.configuration.PropertySimple;
+import org.rhq.core.pluginapi.inventory.DiscoveredResourceDetails;
+import org.rhq.core.pluginapi.inventory.ResourceDiscoveryCallback;
+
+import java.util.Set;
+
+public class PluginTwoCallback2 implements ResourceDiscoveryCallback{
+ @Override
+ public void discoveredResources(Set<DiscoveredResourceDetails>
discoveredDetails) throws Exception {
+ DiscoveredResourceDetails details = discoveredDetails.iterator().next();
+ details.setResourceName("PluginTwoCallback2:name");
+ details.setResourceVersion("PluginTwoCallback2:1.0");
+ // notice we do not touch plugin config property TestServerTwo.prop1, our test
code will check that it is null
+ details.getPluginConfiguration().put(new
PropertySimple("TestServerTwo.prop2", "PluginTwoCallback2:prop2"));
+ System.out.println("!!!!!!!!!!!!!!" + this.getClass().getName() +
"==>" + discoveredDetails);
+ }
+}
diff --git
a/modules/core/plugin-container-itest/src/test/resources/discovery-callback1.xml
b/modules/core/plugin-container-itest/src/test/resources/discovery-callback1.xml
new file mode 100644
index 0000000..addfabd
--- /dev/null
+++ b/modules/core/plugin-container-itest/src/test/resources/discovery-callback1.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<plugin name="DiscoveryCallbackPluginOne"
+ package="org.rhq.core.pc.inventory.testplugin"
+ version="1.0"
+ xmlns="urn:xmlns:rhq-plugin"
+ xmlns:c="urn:xmlns:rhq-configuration">
+
+ <discovery-callbacks>
+ <type-callback plugin="DiscoveryCallbackPluginOne"
type="TestServerOne"
callbackClass="org.rhq.core.pc.inventory.discoverycallback.PluginOneCallback"/>
+ </discovery-callbacks>
+
+ <server name="TestServerOne"
+ discovery="TestResourceDiscoveryComponent"
+ class="TestResourceComponent">
+ <plugin-configuration>
+ <c:simple-property name="TestServerOne.prop1"/>
+ <c:simple-property name="TestServerOne.prop2"/>
+ </plugin-configuration>
+ </server>
+</plugin>
diff --git
a/modules/core/plugin-container-itest/src/test/resources/discovery-callback2.xml
b/modules/core/plugin-container-itest/src/test/resources/discovery-callback2.xml
new file mode 100644
index 0000000..fcf24ef
--- /dev/null
+++ b/modules/core/plugin-container-itest/src/test/resources/discovery-callback2.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<plugin name="DiscoveryCallbackPluginTwo"
+ package="org.rhq.core.pc.inventory.testplugin"
+ version="1.0"
+ xmlns="urn:xmlns:rhq-plugin"
+ xmlns:c="urn:xmlns:rhq-configuration">
+
+ <depends plugin="DiscoveryCallbackPluginOne" />
+
+ <discovery-callbacks>
+ <type-callback plugin="DiscoveryCallbackPluginOne"
type="TestServerOne"
callbackClass="org.rhq.core.pc.inventory.discoverycallback.PluginTwoCallback1"
/>
+ <type-callback plugin="DiscoveryCallbackPluginTwo"
type="TestServerTwo"
callbackClass="org.rhq.core.pc.inventory.discoverycallback.PluginTwoCallback2"
/>
+ </discovery-callbacks>
+
+ <server name="TestServerTwo"
+ discovery="TestResourceDiscoveryComponent"
+ class="TestResourceComponent">
+ <plugin-configuration>
+ <c:simple-property name="TestServerTwo.prop1" />
+ <c:simple-property name="TestServerTwo.prop2" />
+ </plugin-configuration>
+ </server>
+</plugin>
diff --git
a/modules/core/plugin-container/src/main/java/org/rhq/core/pc/inventory/InventoryManager.java
b/modules/core/plugin-container/src/main/java/org/rhq/core/pc/inventory/InventoryManager.java
index 406d65d..f838af5 100644
---
a/modules/core/plugin-container/src/main/java/org/rhq/core/pc/inventory/InventoryManager.java
+++
b/modules/core/plugin-container/src/main/java/org/rhq/core/pc/inventory/InventoryManager.java
@@ -117,6 +117,7 @@ import org.rhq.core.pluginapi.inventory.ManualAddFacet;
import org.rhq.core.pluginapi.inventory.ProcessScanResult;
import org.rhq.core.pluginapi.inventory.ResourceComponent;
import org.rhq.core.pluginapi.inventory.ResourceContext;
+import org.rhq.core.pluginapi.inventory.ResourceDiscoveryCallback;
import org.rhq.core.pluginapi.inventory.ResourceDiscoveryComponent;
import org.rhq.core.pluginapi.inventory.ResourceDiscoveryContext;
import org.rhq.core.pluginapi.operation.OperationContext;
@@ -337,20 +338,58 @@ public class InventoryManager extends AgentService implements
ContainerService,
long timeout = getDiscoveryComponentTimeout(context.getResourceType());
+ Set<DiscoveredResourceDetails> results;
+
try {
ResourceDiscoveryComponent proxy =
this.discoveryComponentProxyFactory.getDiscoveryComponentProxy(
context.getResourceType(), component, timeout, parentResourceContainer);
- Set<DiscoveredResourceDetails> results =
proxy.discoverResources(context);
- return results;
+ results = proxy.discoverResources(context);
} catch (TimeoutException te) {
log.warn("Discovery for Resources of [" + context.getResourceType()
+ "] has been running for more than "
+ timeout + " milliseconds. This may be a plugin bug.", te);
- return null;
+ results = null;
} catch (BlacklistedException be) {
// Discovery did not run, because the ResourceType was blacklisted during a
prior discovery scan.
log.debug(ThrowableUtil.getAllMessages(be));
- return null;
+ results = null;
+ }
+
+ if (results == null || results.isEmpty()) {
+ return results;
}
+
+ // funnel the results through any discovery callbacks that are defined on the
discovered resource type
+ Map<String, List<String>> callbacks =
this.pluginManager.getMetadataManager().getDiscoveryCallbacks(context.getResourceType());
+ if (callbacks == null || callbacks.isEmpty()) {
+ return results; // no callbacks defined, return the discovered details as-is
+ }
+
+ ClassLoader originalContextClassLoader =
Thread.currentThread().getContextClassLoader();
+ int callbackCount = 0;
+ PluginComponentFactory pluginComponentFactory =
PluginContainer.getInstance().getPluginComponentFactory();
+ for (Map.Entry<String, List<String>> entry : callbacks.entrySet()) {
+ String pluginName = entry.getKey();
+ List<String> callbackClassNames = entry.getValue();
+ for (String className : callbackClassNames) {
+ ResourceDiscoveryCallback callback =
pluginComponentFactory.getDiscoveryCallback(pluginName, className);
+ try {
+
Thread.currentThread().setContextClassLoader(callback.getClass().getClassLoader());
+ // notice we call inline, in our calling thread - no time outs or
anything; hopefully the plugin plays nice
+ callback.discoveredResources(results);
+ callbackCount++;
+ if (log.isDebugEnabled()) {
+ log.debug("Discovery callback [{" + pluginName +
"}" + className + "] done. #invocations=" + callbackCount);
+ }
+ } catch (Throwable t) {
+ log.error("Discovery callback [{" + pluginName +
"}" + className + "] failed with exception: "
+ + ThrowableUtil.getAllMessages(t), t);
+ } finally {
+
Thread.currentThread().setContextClassLoader(originalContextClassLoader);
+ }
+ }
+ }
+
+ return results;
}
/**
diff --git
a/modules/core/plugin-container/src/main/java/org/rhq/core/pc/plugin/PluginComponentFactory.java
b/modules/core/plugin-container/src/main/java/org/rhq/core/pc/plugin/PluginComponentFactory.java
index 1f3fcd7..9f0502c 100644
---
a/modules/core/plugin-container/src/main/java/org/rhq/core/pc/plugin/PluginComponentFactory.java
+++
b/modules/core/plugin-container/src/main/java/org/rhq/core/pc/plugin/PluginComponentFactory.java
@@ -43,6 +43,7 @@ import org.rhq.core.pc.inventory.ResourceContainer;
import org.rhq.core.pluginapi.inventory.ClassLoaderFacet;
import org.rhq.core.pluginapi.inventory.ResourceComponent;
import org.rhq.core.pluginapi.inventory.ResourceContext;
+import org.rhq.core.pluginapi.inventory.ResourceDiscoveryCallback;
import org.rhq.core.pluginapi.inventory.ResourceDiscoveryComponent;
/**
@@ -225,6 +226,29 @@ public class PluginComponentFactory implements ContainerService {
}
}
+ /**
+ * This will create a new {@link ResourceDiscoveryCallback} instance that can be used
to process
+ * details of newly discovered resources.
+ *
+ * @return a new discovery callback loaded in the proper classloader
+ * @throws PluginContainerException if failed to create the discovery callback
instance
+ */
+ public ResourceDiscoveryCallback getDiscoveryCallback(String pluginName, String
callbackClassName)
+ throws PluginContainerException {
+
+ // same classloader as plugin discovery component would use - with null parent,
its just the plugin classloader
+ ClassLoader classLoader = getDiscoveryComponentClassLoader(null, pluginName);
+
+ ResourceDiscoveryCallback callback = (ResourceDiscoveryCallback)
instantiateClass(classLoader,
+ callbackClassName);
+
+ if (log.isDebugEnabled()) {
+ log.debug("Created discovery callback [" + callbackClassName +
"] for plugin [" + pluginName + ']');
+ }
+
+ return callback;
+ }
+
private List<URL> askDiscoveryComponentForAdditionalClasspathUrls(Resource
resource,
ResourceContainer parentContainer) throws Throwable {