Plugin ExtensionsRHQ 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:
There are two main extension models:
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. Classloader Issues / Class Sharing Between Plugins
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). Shared vs. Instance ClassloadersBy 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).
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: <plugin name="Z"> <depends plugin="A" /> <server name="Z1.server" classLoader="instance"> <!-- notice classLoader attribute --> <runs-inside> <parent-resource-type name="B1.server" plugin="B"/> <parent-resource-type name="C1.server" plugin="C"/> </runs-inside> </server> <server name="Z2.server" sourcePlugin="D" sourceType="D1" classLoader="instance"> </server> </plugin> This means that if Z is deployed, then:
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. Plugin Descriptor Content That Deals With Plugin ExtensionsA plugin can depend on other plugins in several ways:
<depends>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:
EmbeddedA <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. Using the Embedded style of extension implicitly adds an "optional dependency" on the source plugin. InjectionA 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: <runs-inside> <parent-resource-type name="JMX Server" plugin="JMX" /> <parent-resource-type name="JBoss Server" plugin="JBossAS" /> </runs-inside> Using the Injection style of extension implicitly adds an "optional dependency" on the parent plugin. Optional Dependency SemanticsWhen 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. Caveats
DiscoveryThe plugin container will invoke discovery components to discover resource types as appropriate.
Discovery ClassloadersEach 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 DeployerThe 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. Use CasesThis 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. JBossAS extends the JMX pluginThe 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: <plugin name="JMX"> <server name="JMX Server" discovery="JMXDiscoveryComponent" class="JMXServerComponent"> ... </server> </plugin> JBossAS Plugin Descriptor: <plugin name="JBossAS"> <depends plugin="JMX" useClasses="true"/> <!-- JBossASXXXComponent classes extend the JMXXXXComponent classes --> <server name="JBossAS Server" discovery="JBossASDiscoveryComponent" class="JBossASServerComponent"> ... </server> </plugin> Things to consider:
Standalone JVM MBeanServer and Embedded In JBossASRecall 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: <plugin name="JMX"> <server name="JMX Server" discovery="JMXDiscoveryComponent" class="JMXServerComponent"> <service name="VM Memory System" discovery="MBeanResourceDiscoveryComponent" class="MBeanResourceComponent" description="The memory system of the Java virtual machine"> ... </service> <!-- other platform MBean services similar to above can be defined here --> ... </server> </plugin> JBossAS Plugin Descriptor: <plugin name="JBossAS"> <depends plugin="JMX" useClasses="true"/> <!-- JBossASXXXComponent classes extend the JMXXXXComponent classes --> <server name="JBossAS Server" discovery="JBossASDiscoveryComponent" class="JBossASServerComponent"> <!-- effectively duplicates the JMX plugin's resource type and its children types --> <server name="JBoss AS JVM" description="JVM of the JBossAS" sourcePlugin="JMX" sourceType="JMX Server" discovery="org.rhq.plugins.jmx.EmbeddedJMXServerDiscoveryComponent" class="org.rhq.plugins.jmx.JMXServerComponent"> ... </server> ... </server> </plugin> Things to consider:
Hibernate Able to Run in Either Standalone JVM or JBossASHibernate 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: <plugin name="JMX"> <server name="JMX Server" discovery="JMXDiscoveryComponent" class="JMXServerComponent"> ... </server> </plugin> JBossAS Plugin Descriptor: <plugin name="JBossAS"> <depends plugin="JMX" useClasses="true"/> <!-- JBossASXXXComponent classes extend the JMXXXXComponent classes --> <server name="JBossAS Server" discovery="JBossASDiscoveryComponent" class="JBossASServerComponent"> ... </server> </plugin> Hibernate Plugin Descriptor: <depends plugin="JMX" useClasses="true"/> <service name="Hibernate Statistics" discovery="org.rhq.plugins.jmx.MBeanResourceDiscoveryComponent" class="StatisticsComponent"> <runs-inside> <parent-resource-type name="JMX Server" plugin="JMX"/> <parent-resource-type name="JBossAS Server" plugin="JBossAS"/> </runs-inside> ... </service> </plugin> Things to consider:
|