Creating a System Configuration Collector Plugin
You want to create a new plugin for use with the System Configuration Collector UI in Windchill that will perform some function that the available plugin lack. The plugin will likely assist you in debugging Windchill configuration, installation and runtime issues.
Background
The System Configuration Collector is a Windchill UI page, currently accessed via the UI navigation path of > > when logged into Windchill as a user from the system administrators LDAP group. The UI provides functionality that system administrators can use to collect, save and also send Windchill system and configuration information to both local system and remote systems, including PTC Technical Support.
The directive of the System Configuration Collector is one that allows a user to gather as much appropriate information at one time regarding a Windchill system. The information collected is targeted to consumers who can then more readily use the information to diagnose any potential system and/or configuration issue with the Windchill instance.
On the System Configuration Collector page the system administrator can select categories of problems, such as Performance and/or Runtime related issues. A number of corresponding plugins will then execute. The list of plugins that will execute can be seen by expanding each respective category root tree node on the UI. Each plugin collects a discrete set of about the current Windchill system and configuration. For example, some plugins collect information such as Windchill Method Server logs, xconf properties files, JMX Mbean configuration information and database information.
The plugins are the discrete means to accomplish a task for the System Configuration Collector categories. These plugins can be designed to accomplish any task that can be done through Java, including executing SQL and QML scripts, java command utilities (those which include a main() method) and Windchill Windu tasks.
The plugins comprise a set of software operations for one or more categories that adds specific capabilities to the System Configuration Collector. The System Configuration Collector was designed using plugin architecture such that the functionality provided could be customized by PTC R&D, PTC Global Services, PTC Technical Support and PTC customers. The plugin architecture is one that allows a developer to extend and add new functionality to the System Configuration Collector on a live Windchill system. Therefore, there is no need to restart the Windchill servers for the new plugin to be recognized by the System Configuration Collector. The plugin architecture allows for immediate use of the developed plugin.
Due to the plugin architecture and ability of a plugin to operate against other Java systems, databases, WinDu tasks, scripts, etc, a System Configuration Collector plugin is a very powerful piece of functionality.
A Plugin is also a JMX Management Bean (MBean). This allows for the management of plugins via a JMX console such as those delivered with Windchill; JConsole and VisualVM. Using the MBean interface allows for a means to more easily manage and deploy plugins.
For more information regarding the System Configuration Collector and its uses see
System Configuration Collector.
For more information regarding JMX MBeans see
Windchill MBeans.
Scope/Applicability/Assumptions
The scope is restricted to the System Configuration Collector UI page and creating plugins that operate on that page.
Assume you have an issue with a Windchill system that you must diagnose and no current plugin provides the functionality you need to assist you in diagnosing the issue.
Assume you want to extend the functionality of an existing plugin.
Intended Outcome
Once a plugin is properly designed, created and deployed in Windchill it can be immediately used with the System Configuration Collector page. The plugin will then execute whatever functionality it was designed to do to assist you in your diagnosis.
After creating and deploying a plugin, refreshing the System Configuration Collector UI will allow a user to make use of the deployed plugin. In the screenshot below, the plugin has been added to the user defined “Custom Plugins” category. This category only contains the custom created plugin “Tomcat Property Config File” For more information see the section “Sample Code”.
A user can now make use of this plugin in any category they create and execute the plugin accordingly.
Another option is to make use of a custom plugin through a JMX console. After creating and deploying a plugin in a Windchill system a user can load that plugin through a JMX console and make use of its execution. This is accomplished due to each plugin also being a JMX MBean.
A user must first run the reloadPlugins() MBean operation on the CustomerSupport MBean. See the below screenshot. This forces the plugin architecture to reload all MBeans and will find the custom plugin that was previously deployed.
After reloading the plugins, the custom created plugin can be seen in the Plugin MBean list.
In this case, the newly loaded plugin is the “TomcatConfigProperties” MBean. For more information see the section “Sample Code”.
A user can now edit the attributes of this plugin through the JMX Console or run operations for this plugin (execute the plugin). Additionally, a user can add the custom plugin to existing custom categories. For a more information regarding using JMX MBeans consult the documentation.
For more information regarding JMX MBeans see
Windchill MBeans.
The created plugin will execute from either the System Configuration Collector UI or a JMX Console.
Solution
Design, develop, deploy and execute a custom plugin with the System Configuration Collector UI or a JMX Console.
Prerequisite Knowledge
To apply this solution, you need to have an understanding of the following:
• Basic development using Java – For developing plugins
• Knowledge of Java Management Beans is helpful – For developing complex plugins
• Knowledge of Java Jar Service Provider is helpful – To understand plugin deployment
Solution Elements
Element | Type | Description |
---|
| | |
PluginMBean | Java Class - Interface | As noted earlier, each plugin is an MBean and must implement the PluginMBean interface. This interface specifies the contract required to implement a plugin while ensuring its use as an MBean. This class can be extended into new interface hierarchies to provide additional functionality. Implementing this class allows a plugin to expose attributes and operations that can be executed from a JMX console. This class contains the primary APIs to implement MBean attributes and operations. |
AbstractPlugin | Java Class - Abstract | An abstract class that wraps much of the complexity of implementing the PluginMBean interface for a plugin. This class provides concrete implementations of methods that can be used via inheritance from classes which extend it. This is the base parent class for all plugins and any custom plugin should extend this class either directly or hierarchically. |
SelfAwareMBean | Java Class - Abstract | Each plugin is a SelfAwareMBean, that is, each plugin is a standard MBean (as previously described) that knows its own MBean ObjectName allowing it to maintain a single register of itself in the MBean registry. All custom plugins should implement a call to super() in the plugin constructor (refer to the section “Sample Code”) to ensure that each plugin is registered as a SelfAwareMBean. The AbstractPlugin base class implements a method call to register the plugin as a SelfAwareMBean. Anecdotally, all plugins should be SelfAwareMBeans as each plugin should eventually inherit from the base class AbstractPlugin which extends SelfAwareMBean. |
com.ptc.customersupport.plugins | Java Package | This Java package contains all the PTC provide plugin implementations. This package contains abstract source files for making the implementation of numerous plugins easier by providing default functionality for many required methods. Notably, the package includes the following abstract Java classes: AbstractMultipleFilePlugin AbstractQMLPlugin AbstractReportingPlugin AbstractSQLPlugin AbstactWinDUPlugin AbstractXconfPlugin GatherFilePlugin GatherFolderPlugin GatherLogsPlugin GatherEveryLogPlugin The functionality of these plugins can be used directly through Java inheritance to provide easier plugin implementations for common plugin functionality types. |
AbstractMultipleFilePlugin | Java Class - Abstract | This class wraps common functionality needed for a plugin to gather a list of files. |
AbstractQMLPlugin | Java Class - Abstract | This class wraps common functionality needed for a plugin to execute a QML (Query Markup Language) files/scripts. These scripts usually contain a .qml file extension. |
AbstractReportingPlugin | Java Class - Abstract | This class wraps common functionality needed for a plugin to execute plugins against Windchill Business Reporting. |
AbstractSQLPlugin | Java Class - Abstract | This class wraps common functionality needed for a plugin to execute a SQL (Structured Query Language) files/scripts. |
AbstractWinDUPlugin | Java Class – Abstract | This class wraps common functionality needed for a plugin to execute a WinDU (Windchill Diagnostic Utilitiy) task. |
AbstractXconfPlugin | Java Class – Abstract | This class wraps common functionality needed for a plugin to recursively identify referenced xconf files from a parent xconf file. |
GatherFilePlugin | Java Class - Abstract | This class wraps common functionality needed for a plugin to gather a single file. |
GatherFolderPlugin | Java Class – Abstract | This class wraps common functionality needed for a plugin to gather a parent and all child directories. |
GatherLogsPlugin | Java Class - Abstract | This class wraps common functionality needed for a plugin to gather a directory of log files. This class differs slightly from the similar GatherFolderPlugin in that it operates over a specified date range for a file, in particular, the file modification time. |
GatherEveryLogPlugin | Java Class - Abstract | This class wraps common functionality needed for a plugin to gather a directory of log files. This class differs slightly from the similar GatherLogsPlugin in that it ignores date range for a file, in particular, the file modification time. As such, it operates over every log file. |
CollectorMBean | Java Class - Interface | A Java MBean interface that specifies the contract for how a plugin collects its targeted data. This MBean does not need to be implemented directly by a plugin as AbstractPlugin takes care of the implementation details. |
Collector | Java Class | The concrete implementation class for the CollectorMBean interface. This is the class that all plugins essentially rely on to do their file collection. |
collect(…) | Java Method | The two collect(…) methods are part of the PluginMBean interface and must be implemented by all plugins (as all plugins must implement the PluginMBean interface). These methods specify the API’s necessary to interact with the Collector class and ultimately carry out the work of the plugin. In general cases, the AbstractPlugin and other Abstract classes (see above) in the plugin hierarchy provide default implementations that can be relied upon for the collect(…) methods. Specifically, the collectData(…) method of AbstractPlugin.java can often simply be called by the collect(…) methods. However, more advanced plugins that handle very specific needs may require these methods to be overridden and implemented directly. Generally, these cases are rare and only occur when a plugin isn’t simply collecting files but rather needs to run some Java process or invoke some separate operation that does processing which results in a file that then needs to be collected. There are plugins in the com.ptc.customer support.plugin.* packages that do this which can be examined for examples, specifically, see MBeanDumpPlugin.java, WDSPlugin.java and AbstractWinduPlugin.java. Note that there are two collect(…) methods that must be implemented as there are essentially two ways the PluginMBean interfaces specifies a user can collect data. One API requires a callNumber parameter and the other requires a topicIdentifier. Each of these parameters is used to distinguish locations that information is collected to. This method requires a specific Map return value. For greater detail regarding the return value and this method see Abstracting the Plugin in the section “Customization Points”. |
collectData(…) | Java Method | The collectData(…) method of AbstractPlugin.java wraps much of the complexity of implementing the collect(…) methods of the PluginMBean interface. This method sets up the broadcast message to marshaled to each server as determined by the plugin. It also correctly builds up the return Map that is expected. Since this method hides the complexity of using the collection framework directly, it can often be relied upon by the collect(…) methods. One can simply call this method with the appropriate parameter values in most plugin cases. This method requires a specific Map return value. For greater detail regarding the return value and this method see Abstracting the Plugin in the section “Customization Points”. |
PluginType | Java Class | This is a Java enumerated type class. Each plugin must specify one of the enumerated types of PluginType when initializing itself so that the Collector class knows what do with and where to place the collected information on the file system. |
PluginUtilities | Java Class | This is a Java class that provides utility methods that may be useful for creating more advanced plugins that do not rely on default behavior of the collect(…) methods. Of specific interest is the getReturnMap(boolean, String, String) method that will build up the Map return value that is expected by the collect(…) methods. This will assist in simplifying the implementation of advanced plugins which implement collect(…) methods with specific behavior. |
PluginPropertyUtils | Java Class | This is a Java class that provides utility methods for property information that may be useful for creating more advanced plugins. This class provides methods that allow a user to obtain xconf managed property value information as well as a means to resolve tokenized xconf paths into fully qualified canonical paths. |
*Resource | Java Classes | Each plugins strings should be localized so that they can be viewed in various locales. The com.ptc.customersupport.mbeans.plugin* packages contain resource classes. These resource Java classes maintain strings that can be localized and used in Java source code. A plugin can make use of these or new resource files to provide localized strings. |
<file_name>.jar | Java Jar File | This is a standard Java .jar file and can be named anything. The .jar file is the standard way to deploy a custom plugin in the System Configuration Collector for use. The .jar should contain the compiled .class files for the plugin and any helper Java classes along with the PluginMBean services file (see below). The .jar file must maintain the directory structure of the package for the .class files and the service file. For more information on Java Jar files specification see the section Additional Resources”. |
com.ptc.customersupport.mbeans.PluginMBean | Java Jar Service File | This is a services file contained in a Java .jar file that allows the System Configuration Collectors Java class loader to know if a new plugin has been deployed. The file structure is rigid and must be maintained, otherwise, Java class loader issues will result and the plugin will not load nor be available for use. The .jar file must contain the file com.ptc.customersupport.mbeans.PluginMBean at location META-INF\services. The file contents must be of type: <java source package>.<plugin class name> (without the brackets). For more information on Java Service Provider Interface see the section Additional Resources”. |
As discussed above, the plugin packages contain a hierarchy of Java inheritance that a plugin developer can rely on to create plugins. Below is a UML diagram showing the hierarchical relationship between the abstract classes and MBean interface APIs.
As seen above, all plugins implement the PluginMBean interface and, eventually, extend the AbstractPlugin class. A custom plugin can make use of the plugin hierarchy and the functionality provided by any of the abstract classes.
Procedures – Creating a Custom Plugin
Foremost, for the complete CustomPlugin.java class that is seen in the following sections as code snippets, refer to the section “Sample Code”.
Four distinct phases must be completed before a plugin can be used with the System Configuration Collector or a JMX Console.
1. Designing the plugin.
2. Implementing the plugin.
3. Compiling the plugin.
4. Deploying the plugin.
Designing the Plugin
There are numerous considerations to take into account when designing a plugin for the System Configuration Collector.
1. It is highly recommended, although not required, that a plugin class name include the word “Plugin”. Including the word “Plugin” as a suffix of the class name easily identifies its purpose. The plugin source file name should be of the form <ClassName>Plugin.java. For practicality, the <ClassName> should allude to the plugins purpose as well. For example, PTC provides a SiteXConfPlugin that collects the <Windchill>/site.xconf file.
2. It is highly recommend that a plugin developer maintain a reasonable Java package structure for their plugins. The PTC provided plugins generally maintain package structures relevant to their purpose and PluginType (see the section “Solution” for more information on the PluginType class). Many of the PTC plugins can be found in the package com.ptc.customersupport.mbeans.plugins. Placing custom plugins in this package may allow for easier debugging of custom plugins as well as identifying their locations after deployment. However, this is not a strict requirement and the package structure is left to the developer.
3. All plugins must implement the PluginMBean interface. Implementing the PluginMBean interface class allows a plugin to expose attributes and operations that be executed from a JMX console. In addition, the PluginMBean class enforces an API contract that all plugins must adhere to which is vitally important to the underlying collection framework (see the CollectorMBean and Collector class as noted in the section “Solution”.
4. All plugins must extend the AbstractPlugin class. The AbstractPlugin wraps much of the complexity of implementing the PluginMBean interface. It provides default implementations for many methods that are common to creating a plugin. Certainly, the default method implementations can be overridden to provide varying functionality.
For example:
public class CustomPlugin extends AbstractPlugin
implements PluginMBean {}
This example shows a CustomPlugin class that implements the PluginMBean interface and extends the AbstractPlugin class. Note that in this example the required methods of the interface, namely the collect(…) methods, are not implemented and this simple class would not compile. See the section “Sample Code” for a complete sample plugin implementation for sample plugins packaged with Windchill.
5. All plugin object constructors must make a method call to its super class. This is to ensure that all plugins initialize as SelfAwareMBeans (see “the section “Solution” details regarding SelfAwareMBean). Since all plugins must extend the AbstractPlugin class through the plugin hierarchy, the AbstractPlugin class will make the appropriate method calls to register a plugin as a SelfAwareMBean. Therefore, a plugin developer does not need to concern themselves with the actual plugin MBean registration other than ensuring their plugin calls the super() operator during construction and that the plugin inheritance eventually extends AbstractPlugin.java.
For example:
public CustomPlugin() throws NotCompliantMBeanException {
super(null);
// TODO Other initialization of this particular plugin
}
public CustomPlugin(final String displayName,
final String beanName, final String description,
final String pluginVersion)
throws NotCompliantMBeanException {
super(null, displayName, beanName, description, pluginVersion);
// TODO Other initialization of this particular plugin
}
These constructors are implemented to match the constructors of AbstractPlugin and make calls to AbstractPlugin through the super operator. This causes the CustomPlugin to be registered as a SelfAwarePlugin.
The constructors call to super takes a Class object. If the concrete class is known it can be specified, otherwise null can be used. Java will use Introspection to determine the Class name. For example, since the CustomPlugin implements the PluginMBean the call to super could alternatively be:
super(PluginMBean.class);
See for options to rely on inheritance to design more succinct constructors.
6. Due to implementing the PluginMBean interface and extending AbstractPlugin, a plugin must implement the two collect(…) methods. These are methods that essentially organize and hand-off work to the Collector class and collection framework.
For example:
@Override
public Map<String, Object> collect(final long callNumber,
final long maxAgeInDays,final long minAgeInDays,
final String pathTimeStamp) {
// TODO Do actual collection work and return correct Map
return null;
}
@Override
public Map<String, Object> collect(String topicIdentifier,
final long maxAgeInDays, final long minAgeInDays,
final String pathTimeStamp) {
// TODO Do actual collection work and return correct Map
return null;
}
The collect(…) method implementations shown here do not do anything useful and are merely presented here for clarity. They should do whatever work the plugin collection needs to accomplish and return the correct Map<String, Object> value that is expected.
There are numerous default implementations of the collect(…) methods that can be relied upon through the Abstract classes. Often, a plugin simply needs to call the collectData(…) method of AbstractPlugin.java passing through the parameters of the collect(…) method to the collectData(…) method. The collectData(…) method wraps the complexity of calling the collection framework directly in the collect(…) methods. The collect(…) methods are required by the PluginMBean interface API since the flexibility is necessary to allow a developer to implement the collect(…) methods as necessary since very advanced plugins might desire to avoid using the default behavior of collectData(…). Additionally, the Abstract classes have additional methods that can be relied upon. Many of these methods are of the form collectXYZ(…) that can be called by the collect(…) method implementations of specific plugins. These collect (…) methods wrap the specific collect implementations for the Abstract type of work the plugin does. For example, the GatherFolderPlugin.java class has collect(…) methods that are implemented such that it passes along the specific folder to be collected to the collection framework, provided that folder was correctly set in the plugin initialization.
The collect(…) methods must return a Map<String, Object>. This return Map is what is used by the plugin framework to understand on what server a plugin was executed and the plugins status. This return value is actually a Map inside of a Map; Map<String, Map<String, String>>. The outer Map contains the server information while the inner Map contains the plugin execution status for that server.
If a customizer does not rely on the default implementations of collect(…) methods either through using the parent collectData(…) or other collect(…) methods of Abstract classes one must take extreme care to ensure that the return Map is correctly built and contains valid values.
See the section “Customization Points” for additional detail on using default collect(…) implementations as well as greater detail regarding the methods return Map<String, Object> value.
7. The last consideration is what your plugin must do for actual work, and the most appropriate means to accomplish this. This can be accomplished by making numerous classes to work in conjunction with each other or wrapping all execution in one class.
Additionally, there are numerous utility methods and abstract classes, as discussed in the section “Solution” which can simplify plugin implementation. However, plugins can be designed to be very complex or very simple depending on the task the plugin is supposed to accomplish.
8. In summary, a properly designed plugin is required to have at least these characteristics:
◦ Implement the PluginMBean interface.
◦ Extend the AbstractPlugin class, either directly or through inheritance.
◦ 2 constructors with super() operator calls which eventually lead to AbstractPlugin.
◦ 2 collect(…) methods (if not relying on parent collect(…) implementations).
Implementing the Plugin
Implementation of a plugin is code that will do the actual work for the plugin. The main steps are to ensure a plugin is correctly initialized (see the design step above) and adheres to the required interface. The final step is providing the necessary method implementations for the plugin to carry out its work.
1. Plugin initialization:
During plugin construction, the object should initialize its MBean attributes (again, all plugins are MBeans). Using the example from the design phase we can expand on the constructor method:
public CustomPlugin() throws NotCompliantMBeanException {
super(null);
// super(PluginMBean.class);
initializeMBeanAttributes();
}
private void initializeMBeanAttributes() {
if (logger.isDebugEnabled()) {
logger.debug("Initializing " + CUSTOM_PLUGIN_NAME + " Plugin.");
}
// set the plugin display name
setDisplayName(CUSTOM_PLUGIN_NAME);
// set the plugin description
setDescription(CUSTOM_PLUGIN_DESCRIPTION);
// set the plugin MBean name
setMBeanName("TomcatConfigProperties");
// set the plugin enumerated type
setPluginType(PluginType.MISC);
// set the plugin version number
setPluginVersion("1.0");
// set whether the plugin relies on database
// administrator credentials
setDbCredentialsNeeded(false);
// set whether the plugin operates on each
// cluster node
setClusterAware(true);
// set whether the plugin relies on file
// date ranges for data collection
setDateRangeUsed(false);
// set whether the plugin should compress the
// output of its data collection
setCompressOutput(false);
}
Here the constructor has been expanded to include a private method that initializes all the plugin attributes including the MBean values that are exposed.
The value of the setMBeanName parameter should be a properly formed MBean Object Name. To avoid potential MBean loader issues, it is recommend to avoid using spaces in the string name. For complete details regarding MBean Object Name syntax refer to the section “Additional Resources” – Related Websites.
2. Plugin collect(…) methods:
The bulk of the plugin work is contained in the collect(…) methods and the implementation details are left for the plugin developer to implement. See the section “Sample Code” for examples.
In advanced plugins the work is generally sufficiently large for the collect(…) methods such that it is reasonable to implement helper Java classes and private helper methods. This will allow the plugin to have more discrete methods for easier maintenance. The implementation of any helper method is solely based on the plugin and the developer’s discretion, and therefore is also specific and left for the particular implementer of the plugin. Refer to the section “Customization Points” for greater detail regarding the collect(…) methods.
3. Plugin localization:
As Windchill is a distributed application with the potential to have clients in different locales it is highly recommended that any Java Strings that are externalized in a plugin should be localized.
The strings of note are the plugin name and plugin description. These values are presented to the user in the System Configuration Collector UI.
The com.ptc.customersupport.plugins.* packages contain numerous *Resource.java files that are referenced throughout many of the plugins, and found in the same packages. These *Resource.java files contain public String reference values that are localized and read during MBean plugin initialization.
However, it is not always possible or feasible to localize Java strings. If the values are not localized the values used are those entered and compiled into the class files; in the above cases, English.
Using our example from CustomPlugin we add a new plugin member variable:
private static ResourceBundle RESOURCE =
ResourceBundle.getBundle(
CustomPluginResource.class.getName());
This will be our reference to where our localized Java strings reside. Our CustomPlugin can then make use of this reference to retrieve the localized values. The refactored setDisplayName(string) and setDescription(string) methods become:
// set the localized plugin display name
setDisplayName(MBeanUtilities.formatMessage(
RESOURCE.getString(
CustomPluginResource.CUSTOM_PLUGIN_NAME)));
// set the localized plugin description
setDescription(MBeanUtilities.formatMessage(
RESOURCE.getString(
CustomPluginResource.CUSTOM_PLUGIN_DESC)));
The localized Strings CUSTOM_PLUGIN_NAME and CUSTOM_PLUGIN_DESC are retrieved from the correctly localized class file.
NOTE: The localized plugin Strings are based on the server locale as the localized plugin strings are read from the server during plugin initialization. The localized values will display according to server locale and not the client locale in the System Configuration Collector UI.
Compiling the Plugin
After all the plugin files have been implemented they must be compiled into .class files. Use the Java jdk version running Windchill to ensure proper class file compatibility. For information on how to compile Java source files using the javac command see the section “Additional Resources” – Related Websites.
Deploying the Plugin
Finally, the plugin packaged files must be bundled and deployed into Windchill. As mentioned in the section “Compiling the Plugin” the System Configuration Collector makes use of a Java class loader to load all files that are custom plugins. For the class loader to recognize a plugin, a service file must be created and deployed with the plugin (see Solution Elements and Related Websites for information on this file).
The steps to have a functionally deployed plugin are:
1. Package the files.
2. Jar the files.
3. Deploy the Jar.
1. At the root location of your packaged source directory create a directory named “META-INF”. Inside that directory, create a sub-directory named “services”. Inside the services directory, create a text file named “com.ptc.customersupport.mbeans.PluginMBean”. This is the service file which the class loader will examine for plugin class files. This file must contain each plugin to be loaded and must follow a strict syntax format. The format for each plugin entry in the service file is of the form:
<java source package>.<plugin class name> (without the brackets).
For example, returning to the CustomPlugin example the corresponding service file would appear as, and only contain this one line:
com.ptc.customersupport.mbeans.plugins.CustomPlugin
Here is an example of the package structure for the CustomPlugin source, class files and service file.
The items of note are:
◦ The package structure to the CustomPlugin source, com/ptc/customersupport/mbeans/plugins is maintained.
◦ The .class files exist in the /plugins directory. Optionally, the .java source files can exist there as well.
◦ The META-INF/services directory exists.
◦ The /services directory contains the “com.ptc.customersupport.mbeans.PluginMBean” file with an entry for each plugin to be loaded.
2. After creating the package structure and the services file it is necessary to bundle the files into a Java .jar file. See Related Websites for complete details on using Java’s jar command.
Using a Windchill shell, navigate to your root package location and create the .jar file.
Returning to our example above showing the package structure and using the command
jar cvfM CustomPlugin.jar ./com ./META-INF
yields CustomPlugin.jar that contains all the contents of the /com and /META-INF directories, including their subdirectories.
3. Lastly, this .jar file needs to be deployed into the System Configuration Collector class loader location. Copy this file and place it in <Windchill>/utilities/SystemConfigurationCollector/plugins directory.
Now the plugin can be used in the System Configuration Collector page. To do so, simply refresh the browser and add the custom plugin to a custom category. See the help on using the System Configuration Collector for more information on creating custom categories.
For more information regarding the System Configuration Collector and its uses see
System Configuration Collector.
Alternatively, you can deploy the plugin for use in a JMX console since all plugins are MBeans. Using a JMX Console navigate to the CustomerSupport MBean and run the reloadPlugins() operation. This operation forces the plugin MBean loader to reload all the available plugins including those at the deployment location. The below screen shot shows a user invoking the reloadPlugins CustomerSupport MBean operation.
After invoking the reloadPlugins CustomerSupport MBean operation, the custom plugin will be displayed under the com.ptc/plugins MBean node in the respective plugin type subdirectory for which the plugin was defined.
See Intended Outcome for more details and images regarding reloading plugins from a JMX Console.
Customization Points
Extending the PluginMBean Interface
A plugin does not necessaryily need to implement the PluginMBean interface directly. Instead a new interface that extends the PluginMBean interface could be defined and implemented by the concrete plugin implementation. However, the new plugin interface is still required to inherit the PluginMBean interface.
For example:
public interface XYZPluginMBean extends PluginMBean {}
Here the XYZPluginMBean extends the PluginMBean. This interface can then further define any required methods or attributes that are to be exposed via MBean operations.
The concrete implementation would then be:
public class XYZPlugin extends AbstractPlugin implements XYZPluginMBean {}
Abstracting the Plugin
Similar to Extending the PluginMBean Interface section, a developer can rely on the numerous abstract classes detailed in Solution Elements, or any abstract class the developer defines to initialize plugin MBean values to defaults. As previously stated, the abstract classes wrap much of the complexity of plugin creation. In addition to relying on the abstract classes for plugin initialization a developer can also rely on the abstract implementations of the collect(…) methods.
AbstractPlugin.java API: public AbstractPlugin(final Class mbeanInterface) throws NotCompliantMBeanException
Parameter | Default Value | Possible Values | Req? | Description |
---|
mbeanInterface | None | Class | Yes | A class name that is the concrete implementation for the plugin. |
AbstractPlugin.java API: public AbstractPlugin(final Class mbeanInterface, final String displayName, final String mBeanName,
final String description, final String pluginVersion) throws NotCompliantMBeanException {
Parameter | Default Value | Possible Values | Req? | Description |
---|
mbeanInterface | None | Class | Yes | A class name that is the concrete implementation for the plugin. |
displayName | None | String | Yes | A String representation of the plugin name. |
mBeanName | None | String | Yes | A String representation of the plugins mbean name. This value should adhere to proper Java MBean ObjectNames. See Related Websites for MBean Object Names. |
description | None | String | Yes | A String representation of the plugin description. |
pluginVersion | None | String | Yes | A version number associated with this plugin. |
AbstractPlugin.java API: public abstract Map<String, Object> collect(long callNumber, long maxAgeInDays, long minAgeInDays, String pathTimeStamp);
A developer is required to implement this method as it is Abstract. It is called by UI layers as an entry point to do the plugin work..
Parameter | Default Value | Possible Values | Req? | Description |
---|
callNumber | None | long | Yes | This is a long value that is associated with a PTC Technical Support call number. It is used as a location to collect plugin data to. This value should not need to be modified by the collect(...) method implementation. |
maxAgeInDays | None | long | Yes | A long value that is a starting time value for if files are collected with respect to time. This value should not need to be modified by the collect(...) method implementation. |
minAgeInDays | None | long | Yes | A long value that is an ending time value for if files are collected with respect to time. This value should not need to be modified by the collect(...) method implementation. |
pathTimeStamp | None | String | Yes | A String representation of a directory timestamp used as a name which plugins collect their data to. This value should not need to be modified by the collect(...) method implementation. |
A developer can usually pass this methods parameters to the collectData(…) methods of AbstractPlugin.java. As previously discussed, the collectData(…) method handles the complexity of calling the collection framework.
A developer can implement the collect methods in a way that does not rely on collectData(…) or any of the Abstract classes parent collectXYZ(…) method implementations. In general, this is an exception however. Only advanced plugins that require specialized work, such as running a Java process that does some work to build a file to be collected, will probably avoid simply calling the parent collect(…) method implementations. Even in this situation, after the Java process has completed and generated its data, parent classes could still be relied on to do the actual collection. MBeanDumpPlugin.java and WDSPlugin.java are two plugins PTC provides that do additional work other than simply call the parent collection methods, these can be examined in greater detail for those wishing to build highly complex plugins.
Return Value | Possible Values | Req? | Description |
---|
Map<String, Object> | A Map of type <String, Object> where Object is of type Map<String, String> | Yes | The return type is a Map that contains an inner Map. Map<String, Map<String, String>>. The “inner” Map<String, String> is a Map that contains the plugin execution status. The outer Map<String, Object> is a Map that contains which server the plugin executed on. This allows the plugin framework to report information across the cluster for each plugin executed. |
Care must be taken when providing the return type of the collect(…) method. The collectData(…) method of AbstractPlugin.java wraps all the complexity of creating a valid return type that the plugin framework will correctly interpret. This is also why it is recommended to use the collectData(…) method in the collect(…) method implementation. Not doing so increases the complexity of the implementation of the collect(…) method greatly.
The Outer Map: Map <String, Object>
The String key must be the server process ID and hostname of the name the plugin executed on. For example, 5524@DSTUSYNSKI03D. While any String is valid, the plugin framework will report this to the user which will appear as meaningless data if it isn’t properly built.
The Object is actually an inner Map of type <String, String>.
The Inner Map: Map <String, String>
The inner Map should have four key entries, one for each String, “success”, “path”, “message”, “location”.
Each of these keys values must be another String that corresponds to the appropriate value for the key. The “success” keys value should be true or false and is the value that states whether the collection framework was successful in collecting the plugin data or not. The “path” keys value is generated by the collection framework and it denotes the last directory in the canonical path for which the plugin data is collected to for the particular server the plugin executed on. The “message” keys value is a String message that might accompany the plugin generated by the collection framework. This is likely a status message of why a plugin failed. The value null should be used if no message is to be reported. The “location” keys value is a String representing a partial path to where the collection framework collects the plugin data.
When not relying on the collectData(…) method of AbstractPlugin.java or other parent Abstract classes care must be taken to ensure the collect(…) methods return type adheres to this form such that it can be correctly processed by the plugin framework. If implementing a plugin and building the return Map<String, Object> directly examine the MBeanDump.java and WDSPlugin.java implementations for greater detail. Both of these classes do not rely on the Abstract classes or the collection framework directly and therefore build the correct return type in their collect(…) method implementations.
For the advanced plugin that requires the return Map to be built a developer can rely on the PluginUtilites.getReturnMap(…) method which is discussed further in this section.
AbstractPlugin.java API: public abstract Map<String, Object> collect(String topicIdentifier, long maxAgeInDays, long minAgeInDays, String pathTimeStamp);
A developer is required to implement this method as it is Abstract. It is called by UI layers as an entry point to do the plugin work.
Parameter | Default Value | Possible Values | Req? | Description |
---|
topicIdentifier | None | String | Yes | This is a String representation of a directory used as a location to collect plugin data to. This value should not need to be modified by the collect(...) method implementation. |
maxAgeInDays | None | long | Yes | A long value that is a starting time value for if files are collected with respect to time. This value should not need to be modified by the collect(...) method implementation. |
minAgeInDays | None | long | Yes | A long value that is an ending time value for if files are collected with respect to time. This value should not need to be modified by the collect(...) method implementation. |
pathTimeStamp | None | String | Yes | A String representation of a directory timestamp used as a name which plugins collect their data to. This value should not need to be modified by the collect(...) method implementation. |
See the previous collect(…) method for detail on this methods usage.
AbstractPlugin.java API: public Map<String, Object> collectData(String srcPath, final long callNumber,
final long maxAgeInDays, final long minAgeInDays, String pathTimeStamp) {
Parameter | Default Value | Possible Values | Req? | Description |
---|
srcPath | None | String | Yes | This is a String representation of a location that a plugins source data is collected from. This is usually a tokenized Windchill property string. For example, the WindchillLogsPlugin.java would pass $(wt.logs.dir) as the parameter. This value can also be a canonical file path to the data to be collected. However, care must be ensured as this file path will likely not exist on each server the plugin is executed on. If a canonical file path is used the plugin should only execute on one cluster node by setting the plugin attribute isClusterAware to false during plugin initialization. |
callNumber | None | long | Yes | This is a long value that is associated with a PTC Technical Support call number. It is used as a location to collect plugin data to. |
maxAgeInDays | None | long | Yes | A long value that is a starting time value for if files are collected with respect to time. |
minAgeInDays | None | long | Yes | A long value that is an ending time value for if files are collected with respect to time. |
pathTimeStamp | None | String | Yes | A String representation of a directory timestamp used as a name which plugins collect their data to. |
This is the method wraps the complexity of using the collection framework directly for collecting plugin data. Although not strictly required, it is recommended, that it be called by the collect(…) method implementations for a plugin. Often, calling this method in the collect(…) method and returning its value is all that is required of a collect(…) method implementation.
The return value of this method is the same return value as the collect(…) methods described above.
AbstractPlugin.java API: public Map<String, Object> collectData(String srcPath, final String topicIdentifier,
final long maxAgeInDays, final long minAgeInDays, String pathTimeStamp) {
Parameter | Default Value | Possible Values | Req? | Description |
---|
srcPath | None | String | Yes | This is a String representation of a location that a plugins source data is collected from. This is usually a tokenized Windchill property string. For example, the WindchillLogsPlugin.java would pass $(wt.logs.dir) as the parameter. This value can also be a canonical file path to the data to be collected. However, care must be ensured as this file path will likely not exist on each server the plugin is executed on. If a canonical file path is used the plugin should only execute on one cluster node by setting the plugin attribute isClusterAware to false during plugin initialization. |
topicIdentifier | None | String | Yes | This is a String representation of a directory used as a location to collect plugin data to. |
maxAgeInDays | None | long | Yes | A long value that is a starting time value for if files are collected with respect to time. |
minAgeInDays | None | long | Yes | A long value that is an ending time value for if files are collected with respect to time. |
pathTimeStamp | None | String | Yes | A String representation of a directory timestamp used as a name which plugins collect their data to. |
See the previous collect(…) method for detail on this methods usage.
PluginUtilities.java API: public static Map<String, Object>
getReturnMap(final boolean success, final String path, final String message,
final String location) {
Parameter | Default Value | Possible Values | Req? | Description |
---|
success | None | boolean | Yes | The success value for the plugin execution to be placed in the inner Map for the plugin framework status. |
path | None | String | Yes | The path directory value which is the parent directory for where the file is collected to be placed in the inner Map for the plugin framework status. |
message | None | String | Yes | The message to be placed in the inner Map for the plugin framework status. |
location | None | String | Yes | The location partial file path to the PluginType directory be placed in the inner Map for the plugin framework status. |
This method will build up a return value for use with the collect(…) method implementations provided correct values are passed into this method via it’s parameters. This method is a utility method that makes the creation of the collect(…) methods return value easier. Essentially, it wraps the creation of the return value freeing the implementor of the collect(…) method the need to create the outer and inner Maps. As each parameters description details, the parameters are used to generate the inner Map and the method implementation will handle generating the outer Map<String, Object>. The resulting return value is a Map<String, Object> where the Object is of type Map<String, String> that can then be used as the return type of a collect(…) method.
While each of these parameters aren’t strictly required, they should be considered as such, otherwise the methods return value Map<String, Object> will simply be incomplete. The inner Map will not contain the correct data required by the plugin framework for reporting on the plugin execution status.
Note that this method will only be useful as a utility for use when a developer does not rely on the collectData(…) implementation of AbstractPlugin.java or other Abstract collectXYZ(…) method implementations.
Limitations
Plugins used on the System Configuration Collector or used as MBeans can be quite powerful. They can be used to collect files, directories, properties as well as execute java classes, windu tasks, scripts, and gather database information. Currently, the plugin execution is limited to SQL and QML scripts, WinDU tasks and Java code.
Sample Code
CustomPlugin.java
package com.ptc.customersupport.mbeans.plugins;
import java.util.Map;
import java.util.ResourceBundle;
import javax.management.NotCompliantMBeanException;
import org.apache.logging log4j.Logger;
import wt.jmx.core.MBeanUtilities;
import wt.log4j.LogR;
import com.ptc.customersupport.mbeans.AbstractPlugin;
import com.ptc.customersupport.mbeans.PluginMBean;
import com.ptc.customersupport.mbeans.PluginType;
public class CustomPlugin extends AbstractPlugin implements PluginMBean {
private static ResourceBundle RESOURCE =
ResourceBundle.getBundle(CustomPluginResource.class.getName());
private static final Logger logger = LogR.getLoggerInternal(CustomPlugin.class.getName());
public CustomPlugin() throws NotCompliantMBeanException {
super(null);
// super(PluginMBean.class);
initializeMBeanAttributes();
}
public CustomPlugin(final String displayName,
final String beanName, final String description,
final String pluginVersion)
throws NotCompliantMBeanException {
// super(PluginMBean.class, displayName, beanName,
// description, pluginVersion);
super(null, displayName, beanName, description, pluginVersion);
}
@Override
public Map<String, Object> collect(final long callNumber,
final long maxAgeInDays,final long minAgeInDays,
final String pathTimeStamp) {
// TODO Do actual collection work and return correct Map
return null;
}
@Override
public Map<String, Object> collect(String topicIdentifier,
final long maxAgeInDays, final long minAgeInDays,
final String pathTimeStamp) {
// TODO Do actual collection work and return correct Map
return null;
}
private void initializeMBeanAttributes() {
if (logger.isDebugEnabled()) {
logger.debug("Initializing " +
MBeanUtilities.formatMessage(
RESOURCE.getString(
CustomPluginResource.
CUSTOM_PLUGIN_NAME))
+ " Plugin.");
}
// set the localized plugin display name
setDisplayName(MBeanUtilities.formatMessage(
RESOURCE.getString(
CustomPluginResource.CUSTOM_PLUGIN_NAME)));
// set the localized plugin description
setDescription(MBeanUtilities.formatMessage(
RESOURCE.getString(
CustomPluginResource.CUSTOM_PLUGIN_DESC)));
// set the plugin MBean name
setMBeanName("TomcatConfigProperties");
// set the plugin enumerated type
setPluginType(PluginType.MISC);
// set the plugin version number
setPluginVersion("1.0");
// set whether the plugin relies on database
// administrator credentials
setDbCredentialsNeeded(false);
// set whether the plugin operates on each
// cluster node
setClusterAware(true);
// set whether the plugin relies on file
// date ranges for collection
setDateRangeUsed(false);
// set whether the plugin should compress the
// output of its collected data
setCompressOutput(false);
}
}
CustomPluginResource.java
package com.ptc.customersupport.mbeans.plugins;
import wt.util.resource.RBEntry;
import wt.util.resource.RBUUID;
import wt.util.resource.WTListResourceBundle;
@RBUUID("com.ptc.customersupport.mbeans.plugins.CustomPluginResource")
public class CustomPluginResource extends WTListResourceBundle{
@RBEntry("My Custom Plugin")
public static final String CUSTOM_PLUGIN_NAME = "0";
@RBEntry("A plugin that does xyz.")
public static final String CUSTOM_PLUGIN_DESC = "1";
}
TomcatConfigPropertiesPlugin.java
This is sample code that will collect the config.properties file at location installedProduct.location.Tomcat which is a wt.property value. The plugin is not localized for simplicity.
package demo;
import java.util.Map;
import javax.management.NotCompliantMBeanException;
import org.apache.logging log4j.Logger;
import wt.log4j.LogR;
import com.ptc.customersupport.mbeans.PluginType;
import com.ptc.customersupport.mbeans.plugins.GatherFilePlugin;
/**
* A sample Plugin that gathers the Tomcat config.properties file.
* The location of this file is controlled by the location of
* Tomcat on an installed system. Generally, this location is
* <Windchill>/tomcat/config.properties. However, there exists a
* wt.property which points to where the tomcat instance is
* installed. This property is installed.Product.location.Tomcat,
* and it is used to build a tokenized path to the location of the
* config.properties file.
* <BR><BR>
* This class extends the
* {@link com.ptc.customersupport.mbeans.files.GatherFilePlugin
* GatherFilePlugin} which is used to provide functionality that is
* already implemented that this Plugin can rely upon for default
* behavior. Most plugins will be able to make use of existing
* Abstract plugin classes for much of their behavior and method
* implementations. See the
* {@link com.ptc.customersupport.mbeans.files.GatherFilePlugin
* GatherFilePlugin} for more infomration on the parent class of
* this Plugin and it's methods.
* <BR><BR>
*/
public class TomcatConfigPropertiesPlugin extends GatherFilePlugin {
/*
* Optionally provide some form of localization for plugin strings.
*
* In order to localize the values like name, description,
* and version, follow these steps:
*
* 1. Create a tomcatConfigPropertiesPluginResource.rbInfo
* file with the correct values.
*
* 2. Get an instance of a ResouceBundle like this: private static
* ResourceBundle RESOURCE =
* ResourceBundle.getBundle(
* tomcatConfigPropertiesPluginResource.class.getName());
*
* 3. Set the name (or other value) like this:
* setDisplayName(MBeanUtilities.formatMessage(
* RESOURCE.getString(
* tomcatConfigPropertiesPluginResource.
* PLUGIN_DISPLAY_NAME)));
*/
// Set member variables for ease of use.
private static final String TCP_PLUGIN_NAME =
"Tomcat Property Config File";
private static final String TCP_PLUGIN_DESCRIPTION =
"<tomcat_home>/config.properties file.";
/*
* Optionally, provide some logging capabilities. Doing so
* can facilitate easier Plugin debugging or provide
* status/information messages to the running servers.
*/
private static final Logger logger = LogR.getLoggerInternal(
TomcatConfigPropertiesPlugin.class.getName());
/**
* Default constructor that sets all the required values.
*/
public TomcatConfigPropertiesPlugin() throws
NotCompliantMBeanException {
/*
* The operator call to super() allows the
* TomcatConfigPropertiesPlugin to rely on the parent class for
* much of the Plugin initialization. This call will set
* default values which are later overridden as necessary. This
* also sets up and initializes this Plugin as a SelfAwareMBean
* as the inheritance hierarchy eventually reaches
* AbstractPlugin.
*/
super();
/*
* This method call is simply for convenience of setting
* all the Plugin values in one place that we are
* overriding the default values for which were set when
* super() was called.
*/
initializeMBeanAttributes();
}
/**
* Initializes all the Plugin values for this specific Plugin.
* Those values which were initially set by parent classes are
* overidden.
*/
private void initializeMBeanAttributes() {
// Log a debug message that the Plugin is being initialized.
if (logger.isDebugEnabled()) {
logger.debug("Initializing " + TCP_PLUGIN_NAME + "" +
" Plugin.");
}
/*
* Set the attribute values that are common to all Plugins,
* regardless of the PluginType.
*/
// The name that will be used for this Plugin.
setDisplayName(TCP_PLUGIN_NAME);
// The description that will be displayed for this Plugin.
setDescription(TCP_PLUGIN_DESCRIPTION);
// Set the MBean ObjectName.
setMBeanName("TomcatConfigProperties");
/*
* Set the PluginType value. This will control where the
* data is collected to in terms of directory structure.
*/
setPluginType(PluginType.PROPERTY);
// Set the Plugin version information.
setPluginVersion("1.0");
/*
* Set the file name that we want to collect. This value
* overrides the default value set by the parent. The
* GatherFilePluginMBean Interface of the parent class
* specifies this method must be implemented. Since we
* are extending the parent class and want to collect a
* specific file (not the default file the parent specifies)
* we override the value and set it to the location:
* $(installedProduct.location.Tomcat)$(dir.sep)
* config.properties
*
* This is a tokenized wt.property value. When the
* collection framework operates on this Plugin, these
* tokenized values are replaced by their canonicalized
* paths and a file path results. This is how the collection
* framework knows where to find files to collect. Ideally
* tokenized or property values should be used to create
* a path to the file. If hard-coded file paths are used
* the Plugin will not operate across a cluster correctly.
*/
setFileName("$(installedProduct.location.Tomcat)" +
"$(dir.sep)config.properties");
}
/**
* Override the collect methods as needed. Incidentally, this
* Plugin does not dictate the collect(...) methods be
* overridden. The implementation of this method is actually
* the same as the parent implementation. However, for clarity
* of the example the collect(...) methods are shown.
*/
@Override
public Map<String, Object> collect(final long callNumber,
final long maxAgeInDays, final long minAgeInDays,
final String pathTimeStamp) {
/*
* Here we call the collectData(...) method of AbstractPlugin
* which will handle the actual collecting of the file
* specified as the first parameter. Here the first parameter
* is returned by the getFileName() method of the parent
* class.
*
* The collectData method sets up and hides all the complexity
* of using the collection framework and allows a developer to
* avoid implementing a specific collect(...) implementation.
*
* However, there are instances when certain Plugins require
* specific collect(...) method behavior, these instances
* are usually reserved for very advanced Plugins.
*/
return collectData(getFileName(), callNumber, maxAgeInDays,
minAgeInDays, pathTimeStamp);
}
@Override
public Map<String, Object> collect(final String topicIdentifier,
final long maxAgeInDays, final long minAgeInDays,
final String pathTimeStamp) {
return collectData(getFileName(), topicIdentifier,
maxAgeInDays, minAgeInDays, pathTimeStamp);
}
}
Examples of Usage in Windchill Code
com.ptc.customersupport.mbeans package
This Java package contains the AbstractPlugin and PluginMBean interface. It also contains additional utility classes that can be utilized in creating plugins such as the PluginType.java and PluginUtilities.java.
com.ptc.customersupport.mbeans.plugin package
This Java package contains many of the abstract classes seen in the UML diagram and discussed in Solution Elements. This package includes
com.ptc.customersupport.mbeans.plugin.logs package
This Java package contains concrete plugin implementations that are of PluginType logs. These plugins are generally responsible for collecting log files. Log4jFilesPlugin, WindchillLogsPlugin and WDSLogsPlugin are examples that vary in complexity.
com.ptc.customersupport.mbeans.plugin.misc package
This Java package contains concrete plugin implementations that are of PluginType misc. These plugins are generally responsible for collecting miscellaneous information. UpgradeFolderPlugin, MigrationReportsFolderPlugin and WtSafeAreaFolderPlugin are examples that vary in complexity.
com.ptc.customersupport.mbeans.plugin.properties package
This Java package contains concrete plugin implementations that are of PluginType properties. These plugins are generally responsible for collecting properties files. DbPropertiesPlugin, DeclarationsXconfPlugin and IePropertiesPlugin are examples that vary in complexity.
com.ptc.customersupport.mbeans.plugin.third package
This Java package contains concrete plugin implementations that are of PluginType third. These plugins are generally responsible for collecting third party product information. ApacheConfFolderPlugin, ApacheLogsPlugin and CogStartupXmlPlugin are examples that vary in complexity.
com.ptc.customersupport.mbeans.plugin.util package
This Java package contains concrete plugin implementations that are of PluginType util. These plugins are generally responsible for collecting utility information. GatherInfoScriptPlugin, MBeanDumpPlugin and WDSMBeanDumpPlugin are examples that vary in complexity.
MBeanDumpPlugin.java is a complex plugin that does not rely on the collect(…) method implementations of the Abstract classes. This plugin can be examined to provide greater insight into creating complex plugins that implement the collect(…) methods in a manner that doesn’t make use of the Abstract classes implementations of collect(…) methods or the collection framework directly.
Packaged Samples
PTC provides packaged plugin sample code at <Windchill>/utilities/SystemConfigurationCollector/plugins/examples. This directory contains packaged samples that maintain the com.ptc.customersupport.* packaging in a sample subdirectory. These can be examined to see how one can make use of SQL, QML, Java commands, etc inside a plugin.
Additional Resources
Related Package/Class Javadoc
com.ptc.customersupport.mbeans* package(s) contain the Java plugin interfaces, numerous abstract plugins that provide common functionality, and concrete plugin implementations.
Related Windchill Documentation
For more information regarding the System Configuration Collector and its uses see
System Configuration Collector.
Related Websites
• Java Jar Service Provider Specification :
• Java Jar File Specification :
• Java MBean Object Names :
• Java Compiling :
• Java Jar Command :