RHQ plugins have the ability for one plugin to extend or use another plugin. Examples to illustrate why you would need this type of functionality is: "Tomcat embedded in a JBossAS server" and "Hibernate running in some JMX-enabled container".
In this section, we'll be using the following terms:
- a "parent plugin" is one that has another plugin depending on it.
- a "child plugin" is a plugin that depends on one or more other plugins
- a "dependent plugin" is another name for a "child plugin"
- a "plugin root level resource type" is a resource type defined as a direct child element under the <plugin> element in a plugin descriptor.
- a "parent resource type" is a resource type that has one or more child resource types associated with it
- a "child resource type" is a resource type that has a parent resource type (plugin root level resource types normally do not have a parent, unless it is using the Injection extension model as described below)
There are two main extension models:
- Injection: A plugin root level resource type runs inside one or more resource types as defined in one or more parent plugins
- Think of the dependent plugin being able to say "I run inside another plugin".
- The dependent plugin defines a new child resource type under an existing parent resource type (which is defined in the parent plugin).
- The dependent plugin defines a child resource type that is injected into another plugin's resource type hierarchy. It is injected as a child type to the parent plugin's resource type (called the "parent resource type").
- The child resource type knows about its parent resource types, but the parents do not know about the child (this is because knowledge of plugins flow down, not up - that is to say, a parent plugin's type information is known to its child plugin, but a parent plugin does not know anything about the child plugins that depend on it).
- An example that uses the Injection extension model is "Hibernate inside JBossAS and/or Tomcat"
- This extension model allows a resource type to have multiple parent resource types - e.g. a Hibernate service type has JBossAS server and Tomcat server as a parent
- Embedded: A plugin embeds another plugin's resource type
- Think of the dependent plugin being able to say "I know something else runs inside of me".
- The dependent plugin defines a new parent resource type that will contain an existing child resource type (which is defined in the parent plugin)
- Note the reversal of parent-child relationships - the child plugin defines a parent resource type - the parent plugin defines the existing child resource type.
- The parent resource type knows about its child resource type, but the child does not know about the parent (again, this is because knowledge of plugins flow down, not up - that is to say, the child plugin will know about the parent plugins it depends on, but a parent plugin does not know anything about the child plugins that depend on it).
- The dependent plugin pulls in a copy of the parent plugin's resource type
- An example that uses the Embedded extension model is "JMX Server inside JBossAS"
- The parent plugin's resource type embedded as a child by the child plugin will be a copied version of a parent plugin's resource type and thus is a completely separate type - for example, the "JBoss AS JVM" resource type defined by the JBossAS plugin is a copy of (and is considered a different type from) the "JMX Server" resource type defined in the parent JMX plugin. The "JMX Server" resource type defined in the JMX plugin has no parent resource type whereas the copy of that type known as "JBoss AS JVM" has a parent resource type of "JBossAS Server".
Both extension models are mutually exclusive - a plugin can be extended using one or the other, but not both. For example, a plugin "A" resource type cannot be a child to a plugin "B" resource type if another plugin "A" resource type is a parent to a plugin "B" resource type. A more concrete example: JMX Server cannot be run inside of a JBossAS server while at the same time have a JBossAS server run inside of a JMX Server (or one of the other JMX plugin's resource types). However, a plugin can be extended in different ways by different plugins. For example, if a plugin "A" resource type is a child to a plugin "B" resource type, it is OK if a plugin "A" resource type is a parent to a plugin "C" resource type. A more concrete example: JMX Server runs inside of a JBossAS server (child/parent) while at the same time a JMX Server can run a Hibernate service inside of it (parent/child).
Notice that metadata and class definitions flow in only one direction - from a parent plugin to its dependent plugin. Information cannot flow in the other direction. The JBossAS plugin can access the JMX plugin metadata and classes (because the JBossAS plugin extends the JMX plugin using the Embedded model), but the JMX plugin cannot access the JBossAS plugin's metadata or classes.
|The Plugin Dependencies and Class Loaders design page provides more low-level details on plugin classloading issues.|
All plugins will have their own classloader while running in the plugin container. These are called "plugin classloaders". Each resource in inventory will be assigned a classloader (called a "resource classloader"), which may or may not be the same as its plugin classloader (depending on the needs of the classes implementing the manage resource's components, as defined in the plugin descriptor, more on this below).
All plugin classloaders are isolated from one another, unless extended using the <depends> useClasses attribute set to true. If a plugin is a direct dependent of another plugin, and that dependency is defined with <depends useClasses="true">, then that parent plugin jar's classes (and all of its parent jars) will be available to the dependent plugin's classloader.
Important note: you can only use classes from one dependency. That is to say, if you have multiple <depends> defined, only one of them can be defined with useClasses="true". Note that a plugin has access to all classes from that useClasses dependent plugin as well as that plugin's dependencies. For example, if A depends on B which depends on C (and all have useClasses='true'), then A can use classes from plugin B and C.
The typical reason you would want to depend on other plugins is because one resource defined in one plugin is deployed and running in another resource defined in another plugin (e.g. Hibernate is running inside of JBossAS). The child plugin needs a way to connect to its parent resource to perform things like discovery and management of the child resource. For example, in the case of Hibernate running inside of a JBossAS instance, the Hibernate plugin components need a connection to that JBossAS instance in order to talk to the Hibernate management MBean. How do the Hibernate plugin components do this when it was the JBossAS plugin that connected to the managed JBossAS instance? How do the Hibernate components know which JBossAS client jars to use (e.g. what if I have one Hibernate running in JBossAS 4.0 and another in JBossAS 4.3 and they require different client jars)? And to throw another wrinkle in this problem - recall that the Hibernate plugin descriptor says that Hibernate can run in either a JBossAS instance (as defined in the JBossAS plugin) or a standalone JMX Server (as defined in the JMX plugin). How does the Hibernate plugin component know which connection to use - the "jnp JBossAS" connection or the "remote JMX" connection?
You must write plugins with all of this in mind. The good news is alot of this work is done for you if you need to go through JMX to connect to your managed resource - the JMX plugin provides all the necessary classes and implementations to connect to things like a remote JMX Server or a JBossAS Server (with the help of the third-party EMS library). In fact, the JBossAS plugin has a dependency on the JMX plugin so the JBossAS components can delegate to the JMX plugin for connections to all the different versions of JBossAS. What this means is if you need to write a plugin that manages things via JMX, you can probably just do what the JBossAS plugin does - depend on the JMX plugin and delegate to it all of the work necessary to connect to remote JMX servers (the JMX plugin can even connect to other JMX-based servers like WebLogic, WebSphere, etc as well as to standalone JMX Servers that have a remotely-accessible MBeanServer).
By default, all managed resources will be assigned a "resource classloader" that is shared (it could be the same as the plugin classloader or the classloader of the parent resource; in either case, the classloader is shared). But for the cases when a resource component needs a create a connection to its actual managed resource (e.g. the JBossAS component needs to use the JBossAS client jars to connect to the managed JBossAS isntance, as in the Hibernate-JBossAS example above), that resource's classloader usually must be different than the normal, shared plugin classloader. That's because the resource will usually need to have within its own classloader the client jars necessary to connect to the managed resource (and those client jars are usually very specific to the version of the resource being managed).
The connecting resource (the JBossAS resource or the JMX server resource in the above example) should be in charge of creating the connection, since it knows how to do it (e.g. the Hibernate plugin doesn't know how to connect to a JBossAS 4.0 or JBossAS 4.3 or JMX Server - at least, it shouldn't have to). Having the connection management delegated to the Hibernate resource's parent resource allows for code reuse and frees child-plugin developers from having to know how to connect to the parent resource where the child resources are running.
But this involves one important problem - the child plugin components need the classes from the parent plugin; and not just any classes, but very specific classes that may be dependent on the exact instance of the parent resource (e.g. it matters a great deal if a parent JBossAS instance is of version 3.6, 4.0 or 4.3 because the client jars are different among all of them).
|In reality, the Hibernate plugin-JBossAS plugin example from above doesn't quite match what is going to be explained. That's because it just so happens the JBossAS plugin and the JMX plugin uses the third-party EMS library's classloader-creation feature which actually handles the creation of connection-specific classloaders - so the RHQ plugin container doesn't have to. But we'll continue with that example since the concepts are the same, even though in that particular example, EMS can free the plugin container from having to worry about classloader creation. Note that, for the record, the JBossAS 5.x plugin does rely on the plugin container resource classloader feature (it disables the EMS classloader-creation feature and lets the RHQ plugin container do it) - so you could look at its source code and plugin descriptor to see it use the features being explained in this section.|
When you have a resource that requires its own "connection classloader" (that is, a classloader that contains classes necessary to connect to a managed resource), you need to specify the attribute classLoader="instance" on the resource type. In addition, you must make sure your resource type's discovery component implements the ClassLoaderFacet in order for it to tell the plugin container where any additional connection classes (e.g. client jars) can be found for the specific version of the specific resource being managed. For example, the JBossAS Server resource type needs its own resource classloader because it needs to place in that classloader the very specific client jars necessary to connect to the version of JBossAS that is being monitored. So it needs to define its classLoader attribute as "instance" and its discovery component's ClassLoaderFacet needs to return information on the location of the managed JBossAS instance's client jars. With the resource classloader instance created appropriately, the JBossAS plugin components can instantiate the correct client classes and successfully connect to the JBossAS instance being managed.
Any child resources running in that JBossAS resource needs to make sure their components "share" that classloader so they too can use the connection in order to manage the children. For example, if Hibernate is running in JBossAS, the Hibernate components will have the appropriate resource classloaders assigned to them so they can be assured to use that connection in order to monitor Hibernate running in the JBossAS instance.
Let's go over a more generic example. Suppose a developer is writing plugin "Z" - and there are already plugins A, B, C and D that may or may not be deployed. Here's Z's descriptor:
This means that if Z is deployed, then:
- plugin A must be deployed as well (i.e. if A does not exist, Z will not deploy). This is because <depends plugin="A"/> denotes a "required" dependency.
- plugin D may or may not exist; if it doesn't exist, then Z2.server is ignored and that resource type doesn't really exist.
- Plugin B and plugin C may or may not exist. If they don't exist, then Z1.server has no parent types. If only one of B or C does not exist, Z1.server has the parent types that do exist.
- When Z1.server is detected, the component's classloader is a "resource-level classloader" (because classLoader="instance"). This means that this resource's classloader will be its own classloader. If that classLoader attribute was "shared" (or if it was not specified), that would have meant that the component will share a classloader with its parent resource (which could be the main plugin classloader if this resource does not depend on another resource via injection or extension as defined in the plugin descriptor). In other words, classLoader="shared" would have meant that every resource of type Z1.server would use the same classloader, that of its parent (or the plugin itself).
- With classLoader="instance", the ResourceDiscoveryComponent implementation can optionally define a "ClassLoaderFacet" whose method "getAdditionalClasspathUrls" returns a List<URL> that points to additional jars that should be placed in the resource's classloader. When the plugin container needs to create a classloader for a resource, it checks if the resource's discovery component implements this facet and, if so, it gets the additional classpath URLs and adds them to the resource classloader when it creates it.
For the Future?
Would it make sense to allow for a ClassLoaderFacet to provide "hideClasses()" that returns a regex that allows the plugin to tell the PC that its classloader should hide classes (i.e. classes that the plugin wants to provide itself and not have the parent provide) - this is useful if, for example, the plugin wants to use its own JMX classes and not rely on the JMX classes that the parent classloader provides. Nothing like this exists today and, as of today, no use-cases have appeared to indicate this is needed.
- Note that the plugin container does not support "multiple inheritance" of plugins. Having multiple <depends> does not automatically place all classes in all plugins in the resource classloader. Only the one <depends> that has a useClasses attribute set to "true" will mark which plugin has its classes in the resource classloader.
- At the top of the classloader hierarchy above all plugin classloaders and resource classloaders is a "root plugin classloader". This root plugin classloader will hide the RHQ Agent's classes from the plugins themselves. This means that a plugin will not be able to access any classes in any agent libraries, with some exceptions where those exceptions are configurable (e.g. the agent is able to tell the plugin container to pass through log4j.jar so the plugins can use log4j for logging without having to include their own logging jar). This root plugin classloader is configurable such that it can pass through all classes (which is used by the Embedded Jopr project - it needs to allow all plugins to access the embedding JBossAS server classes).
- A discovery component needs to be created in a proper classloader. Top level discovery components (that is, discover components that look for resources which parent will be the top level platform) are created in the plugin classloader itself. Discovery components for child services (i.e. the result of service scans whose discovered resources will have parent resources of servers, specifically NOT the top platform) will be created in a "discovery classloader" that consists of the discovery component's plugin classloader whose parent classloader is that of the parent resource's classloader.
- If a resource is involved in Injection (runs-inside) or Extension (sourcePlugin/sourceType), its classloader depends on its "classLoader" attribute value and its parent's "classLoader" attribute. This table describes the classloader for a resource based on the different combinations:
|Resource classLoader||Parent classLoader||Classloader Description|
|shared||shared||Both resource and parent are willing to share their classloader. If we reached the top of the resource hierarchy (i.e. the parent is the platform resource), our resource's classloader needs only be its own plugin classloader. If we are not at the top, the resource is running inside its parent resource and thus needs to have that parent resource's classloader, but it also needs to have its own classes from its own plugin. Therefore, in this second case, the resource still gets assigned its own plugin classloader but this means that its plugin classloader must also have the parent plugin classloader as its parent. (e.g. RHQ-Server plugin resource runs inside JBossAS server) - the plugin developer must ensure this via the <depends useClasses> element.|
|instance||shared||Resource wants its own classloader because it needs to provide additional jars in order to connect properly to the managed resource; this is so, even though the parent is willing to share its classloader. If we reached the top of the resource hierarchy, our resource's new classloader needs to follow up its plugin classloader hierachy. If we are not at the top, the resource is running inside its parent resource and thus needs to have that parent resource's classloader. (e.g. JBossAS 5 server)|
|shared||instance||Resource is willing to share its own classloader, but the parent has created its own instance. So, even though this resource says it can be shared, it really needs to have its own instance because the parent has its own instance. Therefore, the resource has its own instance of a classloader whose parent classloader is that of its parent resource (e.g. Hibernate running in JBossAS).|
|instance||instance||Same as (shared,instance), the resource has its own instance of a classloader whose parent CL is that of the classloader of its parent resource.|
Note that if a child resource and its parent resource are both from the same plugin, the child will always use the parent's classloader no matter what.
|See BZ 863449 for a specific bug that has been found when using instance classloaders.|
A plugin can depend on other plugins in several ways:
- Embedded Extension
- Injection Extension
There is a <depends> element directly under the <plugin> element that defines one or more parent plugins that the plugin depends on. When using <depends>, you are specifying a "required dependency". This means the plugin will not deploy successfully, unless all <depends> plugins are also successfully deployed. You can define if you want to pull in classes from one of the dependency jars by specifying the "useClasses" attribute of the <depends> tag. <depends useClasses="true" plugin="A"> means the plugin wants to use classes from plugin A. You can only set useClasses to true for one and only one <depends> in a single plugin descriptor. If no <depends> element has a "useClasses" attribute, the last <depends> specified in the plugin descriptor will default its useClasses attribute to true.
If you are only depending on another plugin for its resource types to inject or embed, you usually do not have to specify <depends> on that other plugin. You normally will specify <depends> for one of two reasons:
- Your plugin needs access to another plugin's classes
- You want to require that your plugin is only ever deployed when another plugin is also deployed.
A <server> or <service> can be a copy of a source resource type found in another plugin - the plugin descriptor specifies this kind of extension through the <server>/<service> elements usage of the "sourcePlugin" and "sourceType" attributes. When sourcePlugin/SourceType is specified, the <server>/<service> will be a copy of the source resource type which means it will have the same metadata as the source with the exception that the <server>/<service> can override the discovery and resource classes and will potentially have a different name. To be clear, the new type cannot add or remove other metadata from the source. This means you cannot add or remove metrics, configuration properties, operations, etc. The new type is essentially identical to the source type - you are simply embedding that original type in your new plugin hierachy. Using the Embedded style of extension implicitly adds an "optional dependency" on the source plugin.
A plugin root level resource type (that is, a direct child of <plugin>) can define the parent resource types that it can run inside of. In effect, this injects the root level resource type as a child type to another plugin's resource type.
You define this directly under the root level type via the <runs-inside> element:
Using the Injection style of extension implicitly adds an "optional dependency" on the parent plugin.
When using Embedded or Injection, you are essentially defining an "optional dependency". This means the plugin will still deploy, even if one of the plugins whose types it is embedding or injecting is missing. For example, the Hibernate plugin has an optional <runs-inside> dependency on the JBossAS plugin. If the JBossAS plugin is not deployed, the Hibernate plugin can still deploy, it just will not have any JBossAS related types in its type hierachy.
If any type relies on an optional plugin via the Embedded model (sourcePlugin attribute) and that optional plugin is missing, then the type will be ignored and will not exist.
If any type relies on optional plugins via the Injection model (runs-inside element) and some of those optional plugins are missing, then the type will exist, but it will not have parent resource types for those parent resource types whose plugins are missing.
- Embedded: A <server> or <service> with a sourcePlugin/sourceType will take that source definition and duplicate it. This includes all children resources as well. The <server>/<service> can override things like discovery and class but today cannot add/remove/change any other metadata - the source type is copied as-is.
For the Future?
Do we want it also to add or remove things like configuration, operation and metric metadata? How do you denote that you want to remove one?
- Injection: Only a root level <server> or <service> can have a <runs-inside> element.
For the Future?
Do we also want to allow non-root-level types to accept <runs-inside>?
The plugin container will invoke discovery components to discover resource types as appropriate.
- For the Embedded model: the new child type that is copied from the source plugin can have its own discovery component defined by the child plugin - that new component's job is to discover the embedded resource using its own custom mechanism. The plugin container will call that discovery component and it is that component that will build the proper resource details for the discovered resources.
- For the Injection model: the plugin container will take a parent component, look at all of its child resource types (which can be more than one type) and run a discovery for each child type, injecting that parent component into each child type's discovery method.
Each discovery component is assigned a "discovery classloader". If the discovery component's parent resource is the root platform, the discovery classloader is simply just the plugin classloader. If the parent resource is a top level server or of a low-level resource, the discovery classloader will be the plugin classloader but will have a parent classloader that is the classloader of the parent resource in order for the discovery component to talk to its parent resource using connection classes provided by the parent resource classloader.
Plugin developers can add things to their discovery component's classloader by implementing the ClassLoaderFacet - see the javadocs for that class for more information.
The plugin deployer running in the RHQ Server is very simple - whatever plugin jar it knows about, it reads its descriptor and inserts the metadata into the DB. The plugin deployer never runs the plugins, or even instantiates classes within the plugin jars (that's the job of the agent-side plugin container). It simply reads the plugin jar's XML descriptors, builds the metadata from those descriptors and inserts that metadata into the database.
The plugin deployer does, however, ensure that the plugins are deployed in the proper order. A dependency graph is built that determines the order in which plugins are deployed. Child plugins are deployed after their parent plugins are deployed. For example, if plugin A depends on plugin B, then plugin B needs to be loaded first, then plugin A. Circular dependencies are not allowed - exceptions will be raised and errors logged if a circular dependency among plugins are found (for example, if Plugin A depends on Plugin B, B depends on C and C depends on A). A plugin can depend on more than one plugin. All parent plugins will be deployed prior to the child plugin getting deployed.
When a plugin is successfully deployed, its content is stored in the database. This allows you to upload a plugin jar to a single RHQ Server in the RHQ Server Cloud - all other servers in the cloud will periodically check the database and if they see a new one added, they will download the plugin jar content for themselves.
When updating plugins, the plugin deployer will ensure it only updates newer versions of its plugins. It will check plugin versions and timestamps of the plugin jar files to determine whether or not a plugin needs to be updated.
This section will illustrate some of the use-cases that drove the design of the Embedded and Injection extension models. Use these as examples for how you can build your own plugin extensions.
The JMX plugin is a generic plugin and can be used by many other plugins. If you have a product that is manageable by JMX, you probably will want to add a dependency on the JMX plugin to pick up all its utilities. Note that the JMX plugin includes the third-party EMS library which provides generic JMX access to many different JMX vendors and implementations - any plugin that depends on the JMX plugin will also pick up the ability to use the EMS library for free.
In this example, consider that a JBossAS has a JMX server running inside of it that houses all the JBossAS 4.x services used for manageability.
JMX Plugin Descriptor:
JBossAS Plugin Descriptor:
Things to consider:
- The JBossAS plugin classloader will also have the JMX plugin's classes in it (due to useClasses="true" in the JBossAS plugin's <depends> tag).
- Note that the JBossAS plugin descriptor above does not actually define or use any source types related to or referencing the JMX plugin. But next we'll see how we can embed the JMX plugin's generic JVM MBeanServer into the JBossAS server resource type hierarchy.
Recall that, since Java5, Java Virtual Machines have a "built-in" JMX MBeanServer called the "platform MBeanServer". Any JVM of version 5 or higher has this platform MBeanServer, allowing those JVMs to be monitored (the platform MBeanServer has MBeans to monitor things like the memory subsystem, the garbage collectors, the threading subsystem, et. al.). The JMX plugin can define resource types for each platform MBean, allowing it to monitor those MBeans as RHQ service resources.
The platform MBeanServer can be found in any standalone JVM process or it can be housed inside of a JBossAS server (that is, embedded inside the JBossAS VM process). We will use the Embedded extension model to define both of these situations:
JMX Plugin Descriptor:
JBossAS Plugin Descriptor:
Things to consider:
- <server> has sourcePlugin and sourceType - this typically indicates an "optional" dependency. However, because the JBossAS plugin needs to use and extend the JMX plugin's component classes, it specifies <depends plugin="JMX" useClasses="true"/> changing it to a "requires" dependency - therefore, you cannot deploy the JBossAS plugin without also deploying the JMX plugin with it.
- We now have two JMX Server discoveries happening, one with JMXDiscoveryComponent (from the JMX plugin) and one with org.rhq.plugins.jmx.EmbeddedJMXServerDiscoveryComponent. This means the JBossAS plugin is asking for a special discovery component to run to discover this "JBoss AS JVM" resource. This illustrates that an embedded resource type can be discovered using a different discovery component from that of the source plugin type.
- The sourcePlugin/sourceType makes a duplicate copy of a resource type and gives it a unique name. During runtime, they are seen as two distinct and different resource types.
Hibernate can be deployed/run in either a standalone J2SE JVM instance or a JBossAS server. We will use the Injection extension model to define this situation:
JMX Plugin Descriptor:
JBossAS Plugin Descriptor:
Hibernate Plugin Descriptor:
Things to consider:
- Plugins can have "multiple dependency" support - a plugin can depend on more than one other plugin. If Hibernate can run inside a Tomcat Server, we could have added a <parent-resource-type> element to describe this. This would add a second optional dependency on the Tomcat plugin. Remember, however, that a plugin descriptor can only have one dependency that denotes "useClasses=true". Because the Hibernate plugin uses and extends the JMX plugin classes, it needs to <depend> on the JMX plugin with useClasses=true.
- Hibernate is injecting its "Hibernate Statistics" resource type definition as a child to the parent types of JMX Server and JBossAS Server.
- Because the Hibernate plugin has a direct dependency on the JBossAS plugin, it also implicitly is a dependent of any other plugins the JBossAS plugin is dependent on. It just so happens in this example, there are no additional dependencies, but the point should be noted that transitive dependencies like that are supported.