7 Developing an Introspection Plug-in

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:

7.1 Introduction

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.

7.2 Tutorial 1: Oracle Virtual Assembly Builder Integration

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

7.2.1 Creating a Project

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

7.2.2 Creating an IntrospectorPlugin

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
  }
}

7.2.3 SPIF Service Activation

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 6, "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.

7.2.4 Building the Tutorials

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.

7.2.5 Installing the Plug-in

Place a jar containing a plug-in implementation in its own directory under ORACLE_HOME/jlib/introspector/plugin (place your extension jar under jlib/introspector/plugin/<plugin name>).

7.2.6 Executing an Introspector Plug-In

Describes executing an introspector plug-in using abctl or Studio.

7.2.6.1 abctl Command-Line User Interface

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

7.2.6.2 Oracle Virtual Assembly Builder Studio

Installed plug-ins automatically appear within the graphical user interface as an additional plug-in. You can execute it as you would any other plug-in. For more information, see Using Oracle Virtual Assembly Builder.

7.2.7 Conclusion

That concludes this tutorial. You created an empty implementation of a plug-in, registered the plug-in with Oracle Virtual Assembly Builder, installed the plug-in, and confirmed it is visible from the abctl CLI user interface.

7.3 Tutorial 2: Dehydration

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 7.2, "Tutorial 1: Oracle Virtual Assembly Builder Integration".

7.4 Tutorial 3: Rehydration

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.

7.5 Tutorial 4: Progress

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.

7.6 Tutorial 5: Job Parameters

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".