ControlTier Inc. > CTL
 
Font size:      

CTL Objects

This document introduces how to apply CTL's object oriented programming (OOP) features. Why bother with OOP you might ask? The normal reasons come to mind (inheritance, abstraction, encapsulation, polymorphism) of course but there are some practical reasons, too.

While you can get quite far with a solution that is based on a set of domain specific modules with well parameterized commands, you will eventually face new challenges as you try to scale up your solution. These challenges emanate from several areas:

CTL supports several object oriented features that together help meet these challenges and allows you to scale up your CTL solutions.

Overview

While object-oriented principles are pretty mainstream, there are differences when it comes to implementations and terminology. CTL offers several object-oriented features but it is not an object oriented langauge, nor is it a complete object oriented runtime. That said, it does offer some fundamental OOP features.

Concepts

The following table relates conventional object-orientation terms to the analogous CTL concept:

Terminology
Object oriention conceptCTL conceptNotes
ClassTypeA CTL type defines commands (the things it can do) and characteristics (attributes and properties). A module is the software artifact produced from the type definition. CTL establishes the Managed-Entity base type.
ObjectObjectAn object is a particular instance of a type. Objects are initialized with the type's attributes and properties but can override them with their own. CTL requires that each object have a unique name. The word "entity" is also used to refer to objects that are instances of Managed-Entity.
MethodCommandA type's behavior is comprised by its commands. The software artifact of a command is called its handler.
Message passingDispatchCTL objects invoke each other via an internal mechanism called the "dispatcher" that forwards actions from the sender to the receiver.
InheritanceSupertype / subtypeTypes are arranged in a class hierarchy. A subtype is a specialization of its supertype. CTL allows you to declare your type as a subtype of another type. Subtypes inherit all the commands of its super type. CTL currently supports a single-inheritance model.
Abstract classType with role="abstract"You can declare your types as "abstract" or "concrete". An abstract type is one that cannot have object instantiation while concrete types can. The "concrete" role is default.
Static (class) methodStatic commandA command can be set to be "static" and signifies it can work outside of an object context. Normally, this is done for utility commands.
Class fieldsAttributesType characteristics can be described in terms of "attributes". This is done using the attribute tags in the type.xml

Mechanisms

As described in the documentation's concepts page, CTL is a framework of modules. A module is a self contained component in the CTL system, responsible for its own data, its own activities, and the integrity of its actions.

dispatching

Module data is in the form of properties, key/value pairs, while its operations are defined in terms of commands. Modules can inherit commands from their "supertype", override their commands or add new commands.

inheritance

Users can create instances of types, called objects and call commands in the scope of their instance data. It is the framework's dispatcher that does the lookup for the named command specified by the user, resolve it to a command implementation and object, prepare a data context, and execute the implementation code.

Examples

The following few sections show how to utilize CTL's object oriented features.

Inheritance

Defining a supertype/subtype inheritance hierarchy is easy. Simply refer to the module name that you want to make your supertype. Commands defined in the supertype are inherited by the subtype and do not need to be re-defined in the subtype's type.xml.

The example below shows a type named "poly" that declares a command "salute". To its right is another type, "greeter", that references "poly" as its supertype. The greeter type declares a command of its own called, "greet". The "poly" type is a subtype of Managed-Entity, the CTL basetype that establishes the ability to execute commands.

SupertypeSubtype
<type name="poly" role="concrete" 
        uniqueInstances="true">

 <description>
   Says hello.
   Offers a salute command.
 </description>

 <supertype>
      <typereference name="Managed-Entity"/>
 </supertype>
 <commands>
  <command name="salute" description="offers a salutation"
           command-type="BsfCommand">
   <script language="jython">print "hi there"</script>
  </command>
 </commands>
</type>
	
<type name="greeter" role="concrete" 
        uniqueInstances="true">

 <description>
   Inherits poly's salute command and adds its own
   called greet.
 </description>

 <supertype>
      <typereference name="poly"/>
 </supertype>
 <commands>
  <command name="greet" description="acknowledge a salute "
           command-type="BsfCommand">
   <script language="ruby">
    print 'nice to meet you', "\n"
   </script>
  </command>
 </commands>
</type>
	

As you probably assume, supertypes should be defined before their subtypes. If you build and deploy a subtype before its supertype, you will encounter errors.

From looking at the examples, you might notice how you can mix command-types, with commands defined in the supertype using one command-type (eg, BsfCommand in jython) and commands in the subtype defined using their own implementation (eg, BsfCommand in ruby). There are several choices for command-type (Shell, Ant, BSF, Workflow).

Overriding

A subtype can override commands inherited from its supertype. To override a command, simply declare the same named command in the subtype's type.xml.

The example below shows a a new type, "barker" overriding the "greet" command.

<type name="barker" role="concrete" 
        uniqueInstances="true">

 <description>
   Inherits greeter's salute and greet commands
   but overrides greet.
 </description>

 <supertype>
      <typereference name="greeter"/>
 </supertype>
 <commands>
  <!-- 
   ** override the greet command 
    -->
  <command name="greet" description="acknowledge a salute "
           command-type="BsfCommand">
   <script language="groovy"><![CDATA[
    println "CHEERS, GROOVY BABY";
   ]]></script>
  </command>
 </commands>
</type>
	

CTL does not require that you preserve the same option set but it is considered best practice not to change semantics nor subtract any options.

It's worth understanding what happens internally at the CTL dispatcher level when it comes to overriding. Roughly these are the steps taken by the dispatcher:

  1. Dispatcher receives a command request with the context params (either specified via the ctl shell tool or via Java API.
  2. The dispatcher resolves the module. If the command is scoped to an object, it looks up the object's type and looks or an installed module by that name. If it is not an object scoped command, then the module name will have been specified already.
  3. The dispatcher resolves the command. The dispatcher reads the module's metadata to understand how the command is declared. Overriden commands have extra metadata that tell the dispatcher what module contains it. If it is overriden, it will look in the implementing module for a command of that name.
  4. The dispatcher loads the command handler file. At this time two properties are declared in the execution context: module.dir (the directory of the implementing module), module.name (the name of the implementing module).
  5. The dispatcher executes the command handler.

Dispatching

Via dispatching, objects can call commands on themselves or commands on other objects. This is done via CTL's "controller" Java API which has access to the internal CTL dispatcher. The API is exposed as an Ant task and is simple to utilize via an AntCommand command-type.

The dispatcher sets various properties describing the sender. This information is accessible in the reciever's context. The table below shows them.

PropertyDescription
dispatch.caller.command.nameName of the calling command
dispatch.caller.module.nameModule of calling command
dispatch.caller.cmd.lineCommand line arg string of calling command
dispatch.caller.context.nameSending object's name
dispatch.caller.context.typeSending object's type
dispatch.caller.context.depotSending object's project

Instance data

You may recall there is a base type in CTL called "Managed-Entity". This base type establishes the idea of controllers, commands and properties. CTL provides a workspace for each object in a unique directory in the project depot accessible via the property, ${entity.instance.dir}.

Managed-Entity establishes a standard file location where this data can be mainained. You can get the path to this file via the property, ${entity.properties.file}. Both the Get-Properties and Properties commands update and print the content of the ${entity.properties.file} respectively. Since your commands consume and interpret the content of the property file it can actually be in any format you wish. That said, CTL uses the Java properties format and supplies its own data to an object's commands as such.

Take a look at the entity.properties reference for a complete listing of CTL supplied properties.

You can declare your own set of standard object attributes in your type.xml file using the attribute-default tags. After you build the type, the module will contain a file called ${module.dir}/type.properties containing a set of key/value pairs that correspond to your attribute-defaults. They will be in the form of:

entity.attribute.attributeName=typeDefaultValue

Your objects can use these attribute-defaults but also define their own values. The ${module.dir}/type.properties file is read after ${entity.properties.file}. Therefore, if your object declares values for the attributes the ones from the type are ignored.

You have some choice on how to maintain and distribute instance data for your object. The Get-Properties command can be overridden, too, so you can access data from any source you wish. If that is your intention, make sure all your modules derive from that one so they inherit your standard.

You can use Managed-Entity's implementation of Get-Properties command. Its implementation assumes you are maintaining all the object's entity.properties file on a web server and does simple GET HTTP requests to pull it whenever Get-Properties is invoked (typically via Install).

Calling a supertype command

Sometimes you want to add some additional steps to a command defined in a supertype. The name of your type's supertype is accessible via the property: ${type.supertype} defined in your module's type.properties file.

In the example that follows you will see the "greeter" type is defined to override "poly" type, calling its "greet" command and then continuing with some of its own actions.

<type name="greeter">

 <description>
   Inherits poly's salute command but overrides
   poly's.
 </description>

 <supertype>
      <typereference name="poly"/>
 </supertype>
 <commands>
  <command name="greet" description="offers a salutation"
           command-type="AntCommand">
   <implementation>
    <controller>
     <execute>
       <context depot="${context.depot}"
                entityClass="${context.type}"
                entityName="${context.name}"/>
       <command name="salute" module="${type.supertype}"/>
     </execute>
    </controller>
    <!--
     ** greeters specific implementation
    -->
    <tstamp/> <!-- generates a timestamp, TSTAMP -->
    <!-- log the greeting to a file -->
    <echo message="said hello: ${TSTAMP}" 
          file="${entity.instance.dir}/var/greet.log" append="true"/>
   </implementation>
  </command>

 </commands>
</type>
	

Some consider calling super an anti-pattern (see [Fowler]) recommending instead the use of a template method.

Template method pattern

CTL allows you to employ the template method design pattern. Wikipedia uses the following description:

Template method
"A template method defines the program skeleton of an algorithm. The algorithm itself is made abstract, and the subclasses override the abstract methods to provide concrete behavior." (link)

As the figure below shows, an AbstractClass declares a method which structures a series of other method calls in that class. The methods that are called can be overridden in sub classes.

template method diagram

For CTL, you don't need to make your type abstract. Carry out the steps below to implement the template method design pattern:

Steps
  StepWhere
1. Define primitive operationsSupertype
2. Define workflow to act as the template method. The workflow should invoke the primitive operations in the order desired.Supertype
3. Override one or more of the primitive operations.Subtype

After these steps are done and the modues built and deployed, you can call the workflow on the Subtype. You will find that the workflow command is inherited by the Supertype (and executed from within the Supertype's module) but the overriden commands of the Subtype are called.

Running commands

Running a command against an object is similar to running a static command. The ctl parameters change a bit to name the object you want to target. The general usage below shows the needed paramaters. Note the -t and -o options.

General usage:

ctl -p project -t type -o object -c command [--[commandargs]]

The -t type option is similar to the -m module one but this lets CTL's dispatcher know how to locate the object and invoke the needed command.