This chapter describes the usage of the Oracle Virtual Assembly Builder introspection API through a set of code tutorials for the development of an Oracle Virtual Assembly Builder introspector plug-in. This chapter contains the following sections:
The goal of this document is to provide a step-by-step tutorial for building a plug-in. The tutorial source for the examples in this cookbook are available in the SDK in a 'tutorials' directory.This document is not be written for any particular development environment, and assumes you understand how to perform the given steps in your development environment.
The purpose of this tutorial is to create the most simple plug-in. The goal of this tutorial is to show you how to register a plug-in with the Oracle Virtual Assembly Builder architecture. This tutorial is the basis of all subsequent tutorials.
This tutorial covers:
The IntrospectorPlugin interface
The ServiceActivator interface
Registering the IntrospectorPlugin
Building the tutorials
Installing the IntrospectorPlugin
Verifying the plug-in was deployed
To start, create an empty Java project called 'SamplePlugin'. You must use JDK 1.6 for these tutorials since Oracle Virtual Assembly Builder is not compatible with earlier versions of Java.
Add the following jar files from the SDK to your project:
jlib/oracle.as.assemblybuilder.spif_0.1.0.jar
jlib/introspector/oracle.as.assemblybuilder.introspector.plugin.api_0.1.0.jar
jlib/core/oracle.as.assemblybuilder.external.common_0.1.0.jar
Create a plug-in. The interface 'oracle.as.assemblybuilder.introspector.plugin.IntrospectorPlugin' defines a plug-in. Create the class 'sample.plugin.internal.SamplePlugin' that implements the Introspector Plugin interface. In this case 'internal' implies that any classes here are not for others to use.
You should now have a class that has the following methods: getName()
, getJobParameters()
, initialize()
, getDehydrator()
, and getRehydrator()
. For now, all you need to do is change the return value of getName()
to be 'SamplePlugin' and make getJobParameters()
return an empty list. The rest of the changes to the plug-in implementation are covered in the subsequent tutorials. The class appears as follows:
package sample.plugin.internal; import java.util.ArrayList; import java.util.List; import oracle.as.assemblybuilder.introspector.job.JobParameter; import oracle.as.assemblybuilder.introspector.job.JobParameterBuilder; import oracle.as.assemblybuilder.introspector.plugin.IntrospectorPlugin; import oracle.as.assemblybuilder.introspector.plugin.dehydrate.Dehydrator; import oracle.as.assemblybuilder.introspector.plugin.rehydrate.Rehydrator; public class SamplePlugin implements IntrospectorPlugin { @Override public Dehydrator createDehydrator() { // TODO Auto-generated method stub return null; } @Override public Rehydrator createRehydrator() { // TODO Auto-generated method stub return null; } @Override public List<JobParameter> getJobParameters() { List<JobParameter> jobParameters = new ArrayList<JobParameter>(); return jobParameters; } @Override public String getName() { return "SamplePlugin_Tutorial1"; } @Override public void initialize(JobParameterBuilder jobParameterBuilder){ // TODO Auto-generated method stub } }
To allow the plug-in to be seen by the Oracle Virtual Assembly Builder framework, start by creating a Service Activator. A Service Activator is part of the SPIF framework. If you have not done so already, read about SPIF in Chapter 5, "Preparing to Develop using the Oracle Virtual Assembly Builder Plug-in SDK".
Create a new class 'sample.plugin.internal.spif.PluginActivator' that implements 'oracle.as.assemblybuilder.spif.ServiceActivator'.
To get the service activator to be called, you need to register it. Create a folder in the root project directory called 'resources/jar/META-INF/services'. In that folder, create a text file called 'oracle.as.assemblybuilder.spif.ServiceActivator'. In that file put a single line of text that is the name of the Service Activator implementation, making sure to put a new-line at the end of the line. In this case the line should be 'sample.plugin.internal.spif.PluginActivator'. You also need to make sure that this file gets added to your built jar file in the 'META-INF/services' directory. Once this is done, the start
method on the Service Activator is invoked when one of the Oracle Virtual Assembly Builder user interfaces start-up and the stop
method is invoked when they are shut-down.
At this point we have a Service Activator that gets called, but the Service Activator does not yet register the sample plug-in as an Introspector plug-in. To complete the activation, change the PluginActivator
as follows:
package sample.plugin.internal.spif; import oracle.as.assemblybuilder.introspector.plugin.IntrospectorPlugin; import oracle.as.assemblybuilder.spif.ServiceActivator; import oracle.as.assemblybuilder.spif.ServiceContext; import oracle.as.assemblybuilder.spif.ServiceRegistration; import sample.plugin.internal.SamplePlugin; public class PluginActivator implements ServiceActivator { private ServiceRegistration registration = null; @Override public void start(ServiceContext serviceContext) throws Exception { IntrospectorPlugin plugin = new SamplePlugin(); this.registration = serviceContext.registerService( IntrospectorPlugin.class.getCanonicalName(), plugin); } @Override public void stop(ServiceContext serviceContext) throws Exception { if (this.registration != null) { this.registration.unregister(); } } }
In the start
method, you create an instance of the plug-in, then we register it as an IntrospectorPlugin
. After the registerService
call, the Introspector Manager is notified that there is a new plug-in and you can then invoke the plug-in from the Oracle Virtual Assembly Builder user interfaces. Of course, at this point we have not implemented job parameters, the dehydrator, nor the rehydrator so nothing interesting will occur.
In the stop
method, we make sure to unregister the service we registered.
Before you can install and use your plug-in you need to create a jar file for your plug-in classes. Most development environments provide a way to export a project to a JAR file. Make sure the META-INF/services/oracle.as.assemblybuilder.spif.ServiceActivator file is added to this JAR along with the Java classes.
You can also build the tutorials and sample plug-ins provided with the Oracle Virtual Assembly Builder SDK using ant. Navigate to the tutorial or sample directory and enter 'ant dist'. This command builds a jar containing the classes and the appropriate ServiceActivator file and places it in the necessary directory structure for discovery and installation.
Once you have a jar file, you need to create a mock ORACLE_HOME directory structure along with a plugin.config file.
In Oracle Virtual Assembly Builder, plug-ins must be installed using the 'installPlugins' command. For this command to work, you need to create a mock ORACLE_HOME, which we'll call 'ab_home', that has a 'plugins/ovab/introspector/<plugin-name>' directory, where <plugin-name> is replaced by the value you return from the 'getName()' method of the plug-in. In this case, the directory would be 'ab_home/plugins/ovab/introspector/SamplePlugin_Tutorial1'.
Inside that directory, you need to place your plug-in JAR file along with a 'plugin.config' file. The 'plugin.config' file requires the following contents:
plugin.name=SamplePlugin plugin.version=1.0.0 product.version.min=1.0.0 product.version.max=2.0.0 plugin.description=My sample plugin. ovabsdk.version=3.0
With the jar and plugin.config file in place, you're now ready to install the plug-in into an Oracle Virtual Assembly Builder installation. This is done using the 'installPlugins' abctl
command. For example:
./abctl installPlugins -productRoot /path/to/ab_home
Describes executing an introspector plug-in using abctl
or Studio.
Once you install a plug-in, the CLI automatically registers a command with the name introspectplug-in name
. For example, if you have a Plug-In called 'SamplePlugin', the command you see when you run 'abctl help' shows 'introspectSamplePlugin'.
Once installed, then you can run the introspection for the plug-in.
You can change into the AB_INSTANCE/bin directory and run './abctl help introspection'. The result is output similar to the following:
> ./abctl help introspection Usage: abctl command [options] Command Description ------------------------------------------------------------------------------ captureFileSets Creates file sets for specified appliance or assembly. describePlugins Lists the installed plugins. disablePlugin Disables a plugin or extension. enablePlugin Enables a plugin or extension. findPlugins Finds plugins to install. installPlugins Installs 1 or more plugins. introspectCoherenceWeb Alias for command "introspectWLS". introspectGenericProd Examines GenericProd configuration and captures metadata. introspectOHS Examines OHS configuration and captures metadata. introspectSamplePlugin Examines SamplePlugin configuration and captures metadata introspectWLS Examines WLS configuration and captures metadata. removePlugin Removes a plugin or extension. Try "abctl help name" for detailed help of a specific command.
The plug-in gets its own introspect command automatically, just by registering it as a plug-in. You can see it above in bold. If you were to type './abctl help introspectSamplePlugin' you would get help about all of the parameters this plug-in supports. Even though we have not defined any JobParameters, a number of parameters will be listed. The CLI command that launches the plug-in has its own parameters and all plug-ins will have these as well. Any JobParameters you add to the plug-in are added to this list. You can also type './abctl introspectSamplePlugin' to initiate a dehydration job. However, since getDehydrator()
returns null, you get the following error:
Launching introspection of component 'SamplePlugin' ... Task is done: DehydrateJob failed with error: null Error: OAB-7105: Introspection failed
Now that you have a plug-in created and installed, you need to make it do something useful. Start by looking at dehydration. Dehydration is the process of collecting the metadata for a component so that it can be rehydrated later.
Start with the project from Tutorial 1. Before you can start creating the Dehydrator implementation, you need to add the Progress module to the project. Add the oracle.as.assemblybuilder.progress.api_0.1.0.jar file from the SDK jlib/core directory to the project's build path.
Next, create an implementation of the oracle.as.assemblybuilder.introspector.Dehydrator interface called 'sample.plugin.internal.DehydratorImpl'. This interface only has two methods: initialize()
and dehydrate()
. The dehydrate()
method is called when a Dehydration Job is started through either the introspect<name> command of the CLI, or the from within the GUI. The private initializeAppliance()
method is used to set the minimum amount of metadata required for a successful Dehydrate Job to be executed.
The DehydratorImpl class should appear as follows:
package sample.plugin.internal; import java.util.ArrayList; import java.util.Collection; import oracle.as.assemblybuilder.introspector.metadata.appliance.Appliance; import oracle.as.assemblybuilder.introspector.plugin.dehydrate.DehydrateContext; import oracle.as.assemblybuilder.introspector.plugin.dehydrate.Dehydrator; import oracle.as.assemblybuilder.progress.ProgressManagerFactory; public class DehydratorImpl implements Dehydrator { DehydratorImpl() { } @Override public void initialize(ProgressManagerFactory progressManagerFactory) { // TODO Auto-generated method stub } @Override public void dehydrate(DehydrateContext context) throws Exception { Appliance appliance = context.getOrCreateRootAppliance(); // initialize the appliance initializeAppliance(appliance); //TODO: This is where you will make the changes to the Appliance } /** * Set the minimum amount needed to be a valid appliance. * * @param appliance the appliance to update */ private void initializeAppliance(Appliance appliance) { appliance.setVersion(1, 0, 0); appliance.setScalabilityInfo(1, 1, 99, 200); builder.addResourceRequirement(ResourceEnum.MEMORY_MB, 256); builder.addResourceRequirement(ResourceEnum.NUMBER_CPUS, 1); } }
Update the plug-in to return a new instance of this class in the createDehydrator()
method. Make sure to return a new instance as this greatly simplifies handling concurrent Dehydrate Jobs. Also, avoid using static variables to hold anything that is instance specific. Doing so may lead to problems with concurrent execution that are difficult to diagnose.
Your plug-in class should now appear as follows:
package sample.plugin.internal; import java.util.ArrayList; import java.util.List; import oracle.as.assemblybuilder.introspector.job.JobParameter; import oracle.as.assemblybuilder.introspector.job.JobParameterBuilder; import oracle.as.assemblybuilder.introspector.plugin.IntrospectorPlugin; import oracle.as.assemblybuilder.introspector.plugin.dehydrate.Dehydrator; import oracle.as.assemblybuilder.introspector.plugin.rehydrate.Rehydrator; public class SamplePlugin implements IntrospectorPlugin { @Override public Dehydrator createDehydrator() { return new DehydratorImpl(); } @Override public Rehydrator createRehydrator() { // TODO Auto-generated method stub return null; } @Override public List<JobParameter> getJobParameters() { List<JobParameter> jobParameters = new ArrayList<JobParameter>(); return jobParameters; } @Override public String getName() { return "SamplePlugin_Tutorial2"; } @Override public void initialize(JobParameterBuilder jobParameterBuilder) { } }
This example uses an appliance rather than an assembly, but to create an assembly rather than an appliance change the single line that calls getOrCreateRootAppliance()
to getOrCreateRootAssembly()
.
Once you build your plug-in JAR, you then need to install the plug-in as described in Section 6.2, "Tutorial 1: Oracle Virtual Assembly Builder Integration".
Adding a Rehydrator implementation is a matter of implementing the Rehydrator interface and having the plug-in create a new instance of it when createRehydrator()
is called.
Create a class called RehydratorImpl in the sample.plugin.internal
package:
package sample.plugin.internal; import oracle.as.assemblybuilder.introspector.plugin.rehydrate.RehydrateContext; import oracle.as.assemblybuilder.introspector.plugin.rehydrate.Rehydrator; import oracle.as.assemblybuilder.progress.ProgressManagerFactory; public class RehydratorImpl implements Rehydrator { @Override public void initialize(ProgressManagerFactory progressManagerFactory) { // TODO Auto-generated method stub } @Override public boolean ping(RehydrateContext rehydrateContext) throws Exception { // TODO Auto-generated method stub return false; } @Override public void rehydrate(RehydrateContext rehydrateContext) throws Exception { // TODO Auto-generated method stub } @Override public void start(RehydrateContext rehydrateContext) throws Exception { // TODO Auto-generated method stub } @Override public void stop(RehydrateContext rehydrateContext) throws Exception { // TODO Auto-generated method stub } }
The RehydrateContext provides access to the metadata as you created it in the Dehydrator implementation, with any changes the user made to the data either in the Oracle Virtual Assembly Builder editor or through a deployment plan. Then you must add the implementation to mutate the configuration as needed.
The RehydrateContext also provides a method for determining what sort of stop is taking place when the Rehydrator stop()
method is called. Plug-ins can take different actions depending on whether this is a regular stop, a stop as part of a scale down operation (implying the component is involved in clustering of some kind), or a stop as part of an undeploy operation.
Any files you added in the Package Definition have already been put in place prior to the rehydrate()
method being called.
During Dehydration, it is important to implement Progress so that the user who is waiting for Dehydration to complete will know that it is still executing. There are two types of Progress: structured and ad-hoc.
Structured progress is used to describe steps that you know will always occur during processing. The value of structured progress is that it allows the user to see all of the steps that will occur during execution of the Dehydrate Job and which step is currently executing. The downside is that you can not include specific information in your messages relating to the instance being Dehydrated. Structured progress has a tree-like structure. A top-level progress event can have children. The nesting can be as many levels deep as needed.
Ad-hoc progress is progress that is used in cases where the message content cannot be known until runtime. Ad-hoc progress is always related to a Structured Progress Event. Ad-hoc progress cannot have children.
Let's make up an example so we can make some sense out of the two different types. Let's assume that the sample plug-in has four different top-level steps: OUI processing, OPMN processing, config parsing, and config processing. OUI processing and OPMN processing are distinct events, but config parsing and config processing have sub-events. Let's assume that config processing consists of processing an unknown number of files, and we want to send a progress message for every file found. Doing this will require Ad-hoc progress. However, the config processing consists of four distinct processors. So, for config processing we will be able to utilize Structured Progress.
For this example, we need to build two separate ProgressResourceBundles. One for the top level list, we'll call this TopLevelProgressBundle. One for the config processing list, we'll call this ConfigProcessingProgressBundle. For these Structured Progress lists, we must extend the ProgressResourceBundle class, as they maintain order and order is important. The order in which you list the events in the bundles is the order in which the events are expected to be sent. Any events that get skipped will be automatically set to 'done' when you send a later event.
To handle the Ad-hoc events within the config parser, we must extend a standard ListResourceBundle.
package sample.plugin.resources; import oracle.as.assemblybuilder.progress.ProgressResourceBundle; public static class TopLevelProgressBundle extends ProgressResourceBundle { private static final long serialVersionUID = -2753027497921148834L; static public final String PROGRESS_START = "SPI-001"; static public final String OUI_PROCESSING = "SPI-002"; static public final String OPMN_PROCESSING = "SPI-003"; static public final String CONFIG_PARSING = "SPI-004"; static public final String CONFIG_PROCESSING = "SPI-005"; static public final String PROGRESS_COMPLETE = "SPI-006"; /** * Contents of key/value progress messages */ static final Object[][] contents = { { PROGRESS_START, "dehydration starting" }, { OUI_PROCESSING, "OUI processing completed" }, { OPMN_PROCESSING, "OPMN processing completed" }, { CONFIG_PARSING, "config parsing completed" }, { CONFIG_PROCESSING, "config processing completed" }, { PROGRESS_COMPLETE, "dehydration complete" } }; @Override protected Object[][] getContents() { return contents; } } package sample.plugin.resources; import oracle.as.assemblybuilder.progress.ProgressResourceBundle; public static class ConfigProcessingProgressBundle extends ProgressResourceBundle { private static final long serialVersionUID = -2105678590437947371L; static public final String PROCESS_STEP_1 = "SPI-101"; static public final String PROCESS_STEP_2 = "SPI-102"; static public final String PROCESS_STEP_3 = "SPI-103"; static public final String PROCESS_STEP_4 = "SPI-104"; /** * Contents of key/value progress messages */ static final Object[][] contents = { { PROCESS_STEP_1, "step 1 completed" }, { PROCESS_STEP_2, "step 2 completed" }, { PROCESS_STEP_3, "step 3 completed" }, { PROCESS_STEP_4, "step 4 completed" } }; @Override protected Object[][] getContents() { return contents; } } package sample.plugin.resources; import java.util.ListResourceBundle; public static class ConfigParsingAdHocProgressBundle extends ListResourceBundle { static public final String PROCESSING_FILE = "SPI-201"; /** * Contents of key/value progress messages */ static final Object[][] contents = { { PROCESSING_FILE, "processing file {0}" } }; @Override protected Object[][] getContents() { return contents; } }
The rest of the work will be within the DehydratorImpl class created previously. In the initialize()
method, save a reference to the progressManagerFactory into an instance variable.
In the following example, pay particular attention to the initializeProgress()
method. We will discuss that method after the example.
package sample.plugin.internal; import java.util.ArrayList; import java.util.Collection; import oracle.as.assemblybuilder.introspector.metadata.appliance.Appliance; import oracle.as.assemblybuilder.introspector.metadata.attributes.ResourceRequirement.ResourceEnum; import oracle.as.assemblybuilder.introspector.plugin.dehydrate.DehydrateContext; import oracle.as.assemblybuilder.introspector.plugin.dehydrate.Dehydrator; import oracle.as.assemblybuilder.progress.AdHocMarker; import oracle.as.assemblybuilder.progress.ProgressListener; import oracle.as.assemblybuilder.progress.ProgressManager; import oracle.as.assemblybuilder.progress.ProgressManagerFactory; import oracle.as.assemblybuilder.progress.ProgressProxy; import sample.plugin.resources.Resources; public class DehydratorImpl implements Dehydrator { private ProgressManagerFactory progressManagerFactory = null; private AdHocMarker configParsingAdHocMarker = null; DehydratorImpl() { } @Override public void initialize(ProgressManagerFactory progressManagerFactory) { this.progressManagerFactory = progressManagerFactory; } @Override public DehydrateResult dehydrate(DehydrateContext context) throws Exception { Appliance appliance = context.getOrCreateRootAppliance(); //configure all the progress proxies ProgressProxy progress = initializeProgress(context.getProgressListener()); progress.onProgress(Resources.TopLevelProgressBundle.PROGRESS_START); //initialize the appliance initializeAppliance(appliance); //TODO: Do OUI stuff progress.onProgress(Resources.TopLevelProgressBundle.OUI_PROCESSING); //TODO: Do OPMN stuff progress.onProgress(Resources.TopLevelProgressBundle.OPMN_PROCESSING); //Just for fun, create a list of files to parse String[] files = { "file.1", "file.2", "file.3", "file.4" }; for (String file : files) { //TODO: do something with the files //generate ad-hoc messages progress.onProgress(this.configParsingAdHocMarker, Resources.ConfigParsingAdHocProgressBundle.PROCESSING_FILE, file); } //Now send the processing events progress.onProgress(Resources.ConfigProcessingProgressBundle.PROCESS_STEP_1); progress.onProgress(Resources.ConfigProcessingProgressBundle.PROCESS_STEP_2); progress.onProgress(Resources.ConfigProcessingProgressBundle.PROCESS_STEP_3); progress.onProgress(Resources.ConfigProcessingProgressBundle.PROCESS_STEP_4); //all done progress.onProgress(Resources.TopLevelProgressBundle.PROGRESS_COMPLETE); } private ProgressProxy initializeProgress(ProgressListener progressListener) { ProgressManager progressManager = this.progressManagerFactory.createManager(progressListener); //assign the top level events to this manager progressManager.registerBundle(new Resources.TopLevelProgressBundle()); ProgressManager childProgressManager = null; //create a child progress manager for the config parsing event // and make it ad-hoc, we need to save the adHocMarker when // sending ad-hoc progress events childProgressManager = progressManager.createChildManager( Resources.TopLevelProgressBundle.CONFIG_PARSING); this.configParsingAdHocMarker = childProgressManager.registerAdHoc( new Resources.ConfigParsingAdHocProgressBundle()); //create the child-progress events for the config processing event ProgressManager configProcessingProgressManager = progressManager.createChildManager( Resources.TopLevelProgressBundle.CONFIG_PROCESSING); configProcessingProgressManager.registerBundle( new Resources.ConfigProcessingProgressBundle()); //at this point, the progress tree is configured //now we generate the proxy that is used to send events return progressManager.generateProxy(); } /** * Set the minimum amount needed to be a valid appliance. * * @param appliance the appliance to update */ private void initializeApplianceBuilder(Appliance appliance) { appliance.setScalabilityInfo(1, 1, 99, Integer.MAX_VALUE); appliance.addResourceRequirement(ResourceEnum.MEMORY_MB, 256); appliance.addResourceRequirement(ResourceEnum.NUMBER_CPUS, 1); } }
The interesting parts for Progress are in the initializeProgress()
method. It contains all of the logic to build the progress tree as described above. It starts by creating a ProgressManager instance that represents the top-level of progress. It then sets the ProgressResourceBundle that contains the list of events for the top-level progress events.
The next step is to create a child ProgressManager for the Config Parsing tree, using the key from the top-level ProgressResourceBundle. This attaches this child ProgressManager to that top-level Progress event. Since the Config Parsing child ProgressManager is handling Ad-Hoc progress event, it calls the registerAdHoc() message passing in the ListResourceBundle that contains the Progress events it will send. This also returns an AdHocMarker which will be used later when triggering these Ad-Hoc Progress events.
Next up is creating the child ProgressManager for the Config Processing tree. Since this child ProgressManager is a Structured ProgressManager, it calls registerBundle()
and not registerAdHoc()
. You must make sure that the key's for all levels of the progress tree are unique. Having duplicates can cause unexpected consequences.
The last step in the process is generating a ProgressProxy after the entire Progress tree has been configured. The ProgressProxy is then used to trigger Progress Events, both Structured and Ad-Hoc. However, for Ad-Hoc you must pass in the AdHocMarker instance so that the ProgressProxy knows where in the tree the event needs to be added.
Export the project and deploy it to your ORACLE_HOME. Then run './abctl introspectSamplePlugin'. The result appears similar to the following:
> ./abctl introspectSamplePlugin Launching introspection of component 'SamplePlugin' ... Step 1 of 6: dehydration starting Step 2 of 6: OUI processing completed Step 3 of 6: OPMN processing completed Step 1: processing file file.1 Step 2: processing file file.2 Step 3: processing file file.3 Step 4: processing file file.4 Step 1 of 4: step 1 completed Step 2 of 4: step 2 completed Step 3 of 4: step 3 completed Step 4 of 4: step 4 completed Step 6 of 6: dehydration complete Task is done: DehydrateJob completed Introspection complete Storing result in catalog: '/Users/cooluser/Documents/Perforce/my-depot/drm/src/dist/ab_home/catalog' ... Introspection stored as 'SamplePlugin-1276728646099' in the catalog
That concludes the Tutorial on Progress. In this tutorial, we covered initializing a Progress tree using both Structured and Ad-Hoc progress, Child ProgressManagers, and triggering ProgressMessages. Users of your plug-in have the information they need to know that the execution of the plug-in is progressing as expected.
The introspector plug-in may collect additional information from the user during its invocation, through parameters specified in the abctl
command-line or text field entries in Studio. When the introspector plug-in registers itself, it can provide a list of Job Parameters to the framework, which the framework takes as its cue to collect the specified information from the user.
This example of the sample plug-in configures two Job Parameters, one that is a generic string and one that is a password:
@Override public List<JobParameter> getJobParameters() { JobParameter jobParameter; List<JobParameter> jobParameters = new ArrayList<JobParameter>(); jobParameter = jobParameterBuilder.setName(Resources.STRING_PARAM_NAME) .setValueType(ValueType.NAME).setRequired(false) .setDefaultValue(Resources.STRING_PARAM_DESC).build(); jobParameters.add(jobParameter); jobParameter = jobParameterBuilder.setName(Resources.PASSWORD_PARAM_NAME) .setValueType(ValueType.PASSWORD).setRequired(false) .setDescription(Resources.PASSWORD_PARAM_DESC).build(); jobParameters.add(jobParameter); return jobParameters; }
Note that in previous samples this method was returning an empty Job Parameter list. The Resources class has been created just to contain the strings being used here for parameter names and descriptions:
package sample.plugin.resources; public class Resources { public static final String STRING_PARAM_NAME = "stringParam"; public static final String STRING_PARAM_DESC = "just a generic string parameter"; public static final String PASSWORD_PARAM_NAME = "samplePassword"; // This ends up being the password prompt: public static final String PASSWORD_PARAM_DESC = "Administrator password"; }
These parameters may be given by the user on the abctl command-line (and must be given if they are marked as being required). They are automatically shown in the abctl help for the plug-in, along with their descriptions. In Oracle Virtual Assembly Builder Studio, the introspection wizard gives you the opportunity to enter parameter values in text-entry boxes, and allows you to proceed to the next step of the introspection wizard only after values have been entered for all required parameters.
The Oracle Virtual Assembly Builder framework generates an abbreviation of the parameter names for use in abctl. For example the parameter named "samplePassword" can be specified using either "abctl introspectSamplePlugin -samplePassword" or "abctl introspectSamplePlugin -sp". The framework picks parameter abbreviations by examining the camelCase long parameter names, and complains if there are duplicate abbreviations.
The password-type job parameter is somewhat different from parameters of other types in that due to security considerations, passwords may not be entered as command-line arguments. (If they were given in plain-text on the command line they would be visible through the Unix "ps" command.) Instead, the framework prompts you for the password before dehydration begins; the short description is used as the password prompt. Also due to security considerations, passwords are kept as arrays of char rather than as String; they should not be converted to String and the character array should be cleared after the password is no longer needed.
You can retrieve the user-specified parameter values during dehydration as follows:
Map<String, JobParameter> jobParameterMap = context.getJobParameterMap(); JobParameter stringParameter = jobParameterMap.get(Resources.STRING_PARAM_NAME); String stringParameterValue = stringParameter.getValue();
The framework provides several data types (NAME, ADDRESS, PATH, NUMERIC, BOOLEAN, PASSWORD) for parameters, but with the exception of PASSWORD, the parameter values are given to the plug-in as strings -- so for instance in the case of a BOOLEAN parameter, the plug-in must convert the strings "true" and "false" to actual boolean values.
Boolean parameters have a couple of other notable unique aspects: the abctl command creates these as optional flags, such that if the flag is specified the parameter value is "true". Therefore it doesn't make sense to have a required boolean parameter, and there's no need to specify a default value -- the default is always "false".
A sample plug-in is included along with the tutorials. This sample has examples of many of the SDK features not covered in the tutorials. It can be built and installed just like the tutorials and will produce a deployable appliance if used for introspection.
A sample plug-in extension is also provided for reference. This sample extends the full sample plug-in. This sample shows how to access the appliance that was already created by the base plug-in and how to update a file set definition created by the base plug-in, among other things.