Tutorial 2 - Using resources and services

Files:

Next, we'll talk more about Opticks Resource and Service objects. A Resource is an auto pointer which handles creation and cleaup of a variety of objects. Resource implements the "resource acquisition is initialization" (RAII) design pattern. They help manage the lifecycle of message log entries, model elements, and other objects. There are many kinds of resources available and you can create your own. Look at the reference documentation for more information. A Service is a type of Resource which obtains a singleton pointer. We've already seen a Service used in Tutorial 1. Look at the Service reference documentation for a list of available services. In this tutorial we'll explore a couple of the more often used resource types, message log resources and plug-in resources.

If you would like to run this tutorial as you follow along with the code, see Running Tutorials With Opticks. If you would like to build and experiment with this tutorial as you follow along with the code, see Building and Running Tutorial plug-ins. If you are running this tutorial in Opticks, you will need to select "Tutorial 2" from the Tutorial toolbar to execute it.

Message Log Resources

The Opticks message log can contain two types of entries, steps and messages. A message is often used for stand alone error reporting and debugging. A message has a short description and zero or more properties. A property is a key value pair associated with a message. An example of a message would be a warning thrown by an XML parser. The message description would contain a short version of the warning message. The message properties would contain a longer warning message, the name of the file where the warning occured, the line number of the offending statement, and the column number of the offending statement.

Steps are similar to messages as they also have a short description and zero or more properties. Steps also have a result (success, failure, or aborted) and a failure reason. Steps may contain sub-steps. Managing a complex hierarchy of message log steps can be difficult, especially when your plug-in calls other plug-ins. StepResource and MessageResource simplify the process. When you create a StepResource or MessageResource, it will be added as a sub-step or sub-message to the currently active step. The active step is the deepest step in the hierarchy which has not been finalized. This allows you to create complex step hierarchies without keeping track of where you are in the hierarchy. Lets look at some of the code for the Tutorial2 plug-in.

#ifdef WIN_API
#include <windows.h>
#else
#include <unistd.h>
#endif
We'll be using the system sleep function later on. Unfortunately, Solaris and Windows have slightly different sleep functions in different system header files. The AppConfig.h header file provides some useful platform configuration information. Here, we are using the WIN_API #define to determine if the platform is Windows or another platform. (currently just Solaris, but more generally, a POSIX system)
   allowMultipleInstances(true);
This tutorial creates a plug-in which recursively calls itself a number of times. The default behavior of an ExecutableShell is to disallow multiple, simultaneous calls to the plug-in. This statement enables the recursive behavior.
   pInArgList->addArg<int>("Count", 5, "How many times should the plug-in recurse?");
   pInArgList->addArg<int>("Depth", 1, "This is the recursive depth. Not usually set by the initial caller.");
We added two new input arguments. One declares how many recursive calls we want, and the second is the current recursion depth.

   StepResource pStep("Tutorial 2", "app", "A8FEFCB3-5D08-4670-B47E-CC533A932737");
First, we create the StepResource. We specify a short description. The next two arguments are a component and key. These uniquely identify the message log step. The component is usually the name of your plug-in or plug-in suite. It acts as a namespace for the step. The key is an identifier which is unique within the component namespace. This is often a UUID. The component and key should remain the same as long as the general meaning of the step does not change. This means if the step description changes but the step still represents the same piece of a process, the component and key should not change. These are used to uniquely identify steps in a process across multiple versions of a plug-in. They may be used, for example, by a plug-in which lists an expected workflow for an analyst and automatically tracks progress through a workflow by watching for a specific execution order in the message log.

   int count;
   int depth;
   pInArgList->getPlugInArgValue("Count", count);
   pInArgList->getPlugInArgValue("Depth", depth);
   if (count < 1 || count >= 10 || depth < 1 || depth >= 10)
   {
      std::string msg = "Count and depth must be between 1 and 9 inclusive.";
      pStep->finalize(Message::Failure, msg);
      if (pProgress != NULL)
      {
         pProgress->updateProgress(msg, 0, ERRORS);
      }

      return false;
   }
   if (depth > count)
   {
      std::string msg = "Depth must be <= count";
      pStep->finalize(Message::Failure, msg);
      if (pProgress != NULL)
      {
         pProgress->updateProgress(msg, 0, ERRORS);
      }

      return false;
   }
   pStep->addProperty("Depth", depth);
We extract the values of the new input arguments and do a little sanity checking. The Step::finalize() call indicates that an error occured and sets an appropriate error message. If there is a Progress object, we also display the error there. If this seems like a lot of work just to set an error message, take a look at ProgressTracker. It combines step and progress message reporting.

   if (pProgress != NULL)
   {
      pProgress->updateProgress("Tutorial 2: Count " + StringUtilities::toDisplayString(count)
         + " Depth " + StringUtilities::toDisplayString(depth), depth * 100 / count, NORMAL);
   }
This section displays progress to the user. When the maximum recursion depth is reached, this will show 100%.
   // sleep to simulate some work
#ifdef WIN_API
   _sleep(1000);
#else
   sleep(1);
#endif
This pauses execution for one second. Different function calls need to be made on different platforms so we use the WIN_API #define again.

   if (depth < count)
   {
      ExecutableResource pSubCall("Tutorial 2", std::string(), pProgress, false);
      VERIFY(pSubCall->getPlugIn() != NULL);
      pSubCall->getInArgList().setPlugInArgValue("Count", &count);
      pSubCall->getInArgList().setPlugInArgValue("Depth", &++depth);
This sets up the recursive call. The recursive call is made using ExecutableResource which is a type of plug-in resource which accesses the Executable interface. The getPlugIn() method returns a pointer to the plug-in object. If the plug-in does not exist, can not be created, or does not implement Executable, this will be NULL. Since we are creating another instance of the same plug-in and have the multiple instance option enabled, this should never be NULL. If you are calling another plug-in this will usually not be a VERIFY() but should present a better error message to the user indicating that the requested plug-in is not available. We access the input argument list and set the values of the Count and Depth arguments. We don't need to set the progress argument this way since we specified a Progress object in the ExecutableResource constructor. If we don't supply a Progress object and the plug-in is called in interactive mode, the ExecutableResource will create a new Progress dialog and pass that to the plug-in.
      if (!pSubCall->execute())
      {
         // sub-call has already posted an error message
         return false;
      }
Now we call the plug-in's execute() method. A failure is propagated up through the recursion chain. We don't supply an error message since it is assumed that the execute() method has already supplied an appropriate error message.

   pStep->finalize();
   return true;
Finally, we finalize the StepResource with the default Success status. The finalize() method must be called when a Step has completed successfully otherwise the StepResource will finalize the step with a Failure status when the resource goes out of scope.

Experiments

You can open the wizard builder and add the Tutorial2 plug-in. This will allow you to change the Count and Depth arguments. Try different combinations of values and see how the various error checks work.

Software Development Kit - Opticks 4.8.0 Build 15482