Developing Servlets
V.3.00
This
document provides an overview of how Thunderbolt Servlets
work and how you can create your own. Depending upon which language bindings
you intend to use for your Servlet development you
should be competent in either:
·
Java
if writing Java Thunderbolt Servlets and/or
·
C++
if developing Thunderbolt Servlets in C++. In this
case familiarity with the Standard Template Library (STL) is also pretty
essential.
When the
complete system is up and running it looks something like this:
If
everything has started correctly the Engine will know about the transaction
definitions you have specified, the node controllers running on each machine will
have started the Servlets required to satisfy the
transactions and these will have been bound to the various transaction specs by
the Engine.
The engine
will then be waiting on the configured port for event requests. Upon receiving
a request it will service it by initiating the requested transaction and
optionally returning the results to the client.
Requests can be made:
Connectors
and engine communicate through TCP/IP secure connections so you can develop and
run a connector on one platform while running other connectors, Engine, Runtime
Monitor and Transaction designer on another system in a truly distributed way.
The system
views Thunderbolt Servlets as collections of
configurable attributes, objects with fields and methods.
Each
Object, field and method is addressable in TML though a standard addressing
convention (SAC):
serverName:ConnectorInstance.ObjectName.FieldName
serverName:ConnectorInstance.ObjectName.MethodName
You will
find this kind of addressing syntax throughout the files defining the
transaction model logic, located in the TransSpec
sub-directory. These codings will generally be
created by the Transaction Designer but can also be created manually if the
Transaction Designer is not available. Refer to the Transaction Modeling
Language examples (TML)
Connectors area a specialization of
Thunderbolt Servlets. They are Servlets
that connect up a target/source system to the middleware infrastructure, thus
enabling it to partake in complex multi-part transactions involving any number
of disparate geographically distributed systems.
Thunderbolt
connectors can be bi-directional thus capable of reading and writing data
to/from the target system. Or single directional thus capable of just reading
or writing data to the target/source system.
The steps
for creating a connector are the following:
1)
Model
the sub-system, to which you want to connect up to, into a series of objects,
fields and methods;
2)
Define
each object and its fields;
3)
Define
the methods which will act on the objects and fields;
4)
Define
the property values for the connector;
5)
Code
each object as a C++ or Java object;
6)
Code
the Servlet unit which will instantiate the Objects
you have defined;
7)
Code the main processing entry point to instantiate your Servlet and a Thunderbolt ServletRunner.
There is
exactly one Servlet unit per connector and one or
more Objects with one or more Fields and one or more Methods per connector.
Transformers are also a specialization of Thunderbolt Servlets.
They are Servlets which can access the transaction data during the evolution
of a transaction and modify, transform, add or delete data from the set.
The steps
for creating a transformer are:
1)
Define
the transformations to be carried out;
2)
Define
the property sheet values for the transformer;
3)
Define
the data keys to be transformed and their target values;
4)
Define
the methods which will carry out the transformations;
There is
exactly one Servlet per transformer.
The Servlet library gives you transparent connectivity to the
Engine by taking care of
all transmission protocols between Engine and your Servlet. As well as making your Servlet
multi-process/multi-threaded it handles issues of persistency, transactionality and recovery. The library also
transparently handles service events sent to your Servlets,
by the Transaction Designer and Runtime Monitor GUIs.
Say you
have a sub-system either on your local intranet or reachable via a WAN and you
want to be able to involve this system in a more or less complex business
process involving other systems. You will have to create a connector Servlet to access this data. You may also need to create a
transformer servlet to modify this data before it can
interact with the rest of your transaction elements.
Generally
in order to create a Thunderbolt Servlet you will
need to define the objects, fields and methods that will make up the Servlet. This true for both C++ or
Java Servlets; bindings to the library are provided
for both.
1)
Each
object must be derived from the appropriate library base class ServletObj if using C++ or BaseServlet
if using java;
2)
Each
object must have a Get, Set and Run method defined in it;
3)
Any
other methods you intend to provide for use in the transaction model;
4)
The
Servlet in which you instantiate your object classes;
5)
The
main which instantiates your Servlet and invokes the ServletRunner to actually run your servlet.
After having
broken down the structure of the information from the sub-system we intend to
target/source into a series of objects, fields and methods we need to create a
code an object for each of the identified object. Within each object we model
the fields and methods we have previously identified.
Each object
must as a bear minimum define a Get a Set and run method. The Get end Set
methods enable i/o access to the whole data set for the Object while the run() method provides routing to each method. Optionally
other methods can also be defined to do practically anything you want.
In the Servlet code you:
1)
Define
an init routine for the connector;
2)
Define
a finish method;
3)
Define
a service method;
4)
Define
the transaction functions, start, commit and roll back;
5)
Define
the structure of your Servlet with its objects,
fields and methods;
6)
Define
the connector attributes which determine certain behaviors
The
following methods enable you to describe the objects, fields and methods which
will make up your connector:
AddConfigParam(string label, int
maxLength, string inputRegExp,
bool echoFlag) –
enables you to define configurable parameters which will be displayed in
the transaction designers and runtime monitor property sheet.
ClearObjects() – clears all known object, fields and methods
from the connector.
AddObject(string ObjectName) – Adds an object to the connector.
AddField(string ObjectName,
string FeldName) – Adds a field to an object. If the object
doesn’t exist it’s also added to the connector knowledge base.
AddMethod(string ObjectName,
string MethodName, ServletObj
*obj) - Adds a method to the object. If the object doesn’t exist it’s also
added to the connector knowledge base. The ServletObject
is the object containing the method implementation.
The
following public variables enable you to define the name you wish to assign to
your servlet and the type of Servlet
that is to be created.
servletName
= "MigrationDB";
servletType
= connector; // connector,
transformer or node
servletState
= running; // running |
halted the initial state of
// the connector when its
instantiated
The main
class simply instantiates a servlet and a ServletRunner finally running the servlet
by calling the ServletRunners run()
method.
The main
method
Within an exception catch
Instanciate a new Servlet
object
Instanciate a ServletRunner
object (Servlet)
Call ServletObject
run method
Catch any exceptions
End of main
You will
need a running engine in order to design transactions and test your connectors.
If you do not have an engine installation but still wish to develop a connector
for later use or perhaps in order to try out the system or for a pilot project
or perhaps even while you are waiting for a license to arrive, you may be able
to use the engine found on softbolts.com.
This engine is usually available for public use for the purpose of testing.
A Java class is required to represent each of the objects to be modelled. Each object class will represent the fields and methods available though it. These objects, fields and methods can be dynamically created as in the TBDB sample JDBC connector which loads up a definition of tables->object and fields->rows on successfully login to a database.
All the
code that follows can be found as part of the engine tar ball sub-directory dev/src/java/com/softbolts/connectors/TBGenericDB and is a good
starting point for creating your own connector Servlet.
Our sample
Java connector consists of just one object:
BillingDB
So what you
need to write is:
1)
A
class for each object you wish your connector to represent with its relevant
fields and methods
2)
A
Servlet class which extends BaseServlet
and instantiates the objects above
3)
A
main class that instantiates an instance of ServletRunner
which will run the servlet you wrote above.
package com.softbolts.connectors.TBGenericDB;
import com.softbolts.lib.*;
import java.io.*;
import java.util.*;
import java.lang.*;
public class BillingDB extends ServletObj implements Serializable
{
String Key1 = "000001"; // In more ralistic case this info
String Name = "Anthony"; // would be retieved from a database
String Surname = "
String Address1 = "Via Delle Farfalle 15";
String Address2 = "
String Address3 = "
String Status = "Young free and
single";
//-----------------------------------------------------------
public int run(NameValueTyp params)
{
String method = null;
if((method
= (String) params.get("sysMethod"))
== null) return -1;
if (method.equals("Get")) return Get(params);
if (method.equals("Set")) return Set(params);
return
0;
}
//-----------------------------------------------------------
public int Set(NameValueTyp params)
{
Key1 = (String) params.get("Key1");
Name = (String) params.get("Name");
Surname = (String) params.get("Surname");
Address1 = (String) params.get("Address1");
Address2 = (String) params.get("Address2");
Address3 = (String) params.get("Address3");
Status = (String) params.get("Status");
return
0;
}
//-----------------------------------------------------------
public int Get(NameValueTyp params)
{
params.put("Key1",
Key1);
params.put("Name",
Name);
params.put("Surname",
Surname);
params.put("Address1",
Address1);
params.put("Address2",
Address2);
params.put("Address3",
Address3);
params.put("Status",
Status);
return
0;
}
}
This is
your servlet implementation. This is where you
describe the objects your Servlet is going to
represent as well as the Servlets properties and
characteristics such as type, initial run state and name by which it will be refferd.
package com.softbolts.connectors.TBGenericDB;
import com.softbolts.lib.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.lang.Throwable;
public class Servlet extends
BaseServlet
{
public
Servlet(String args[])
{
System.out.println("TBGenericDB Connector
Started...");
BillingDB bd = new BillingDB();
servletName = "TBGenericDB";
servletType = SvrTyp.connector;
if
(servletState == RunStatusTyp.running)
System.out.println("TBGenericDB Connector
status is running...");
else
if (servletState == RunStatusTyp.halted)
System.out.println("TBGenericDB Connector
status is halted...");
else
System.out.println("TBGenericDB Connector
status is undefined...");
servletState = RunStatusTyp.running;
AddConfigParam("Login",
20, "^[A-Za-z0-9_]*$", true);
AddConfigParam("Password",
20, "^[A-Za-z0-9_]*$", false);
AddObject("BillingAccount");
AddField("BillingAccount", "Key1");
AddField("BillingAccount", "Name");
AddField("BillingAccount", "Surname");
AddField("BillingAccount", "Address1");
AddField("BillingAccount", "Address2");
AddField("BillingAccount", "Address3");
AddField("BillingAccount", "Status");
AddMethod("BillingAccount", "Get", bd);
AddMethod("BillingAccount", "Set", bd);
AddMethod("BillingAccount", "Create_Account",
bd);
AddMethod("BillingAccount", "Update_Account",
bd);
AddMethod("BillingAccount", "Delete_Account",
bd);
//-----------------------------------
//AddObject("BillingBankDetails");
//AddField("BillingBankDetails", "Key1");
//AddField("BillingBankDetails", "Name");
//AddField("BillingBankDetails", "Surname");
//AddMethod("BillingBankDetails", "Get", b);
//AddMethod("BillingBankDetails", "Set", b);
//AddMethod("BillingBankDetails", "Create_BankDetails",
b);
//AddMethod("BillingBankDetails", "Update_BankDetails",
b);
//AddMethod("BillingBankDetails", "Delete_BankDetails",
b);
}
//-----------------------------------------------------------
public
void service(NameValueTyp params)
throws ThrowableString
{
}
//-----------------------------------------------------------
public void
service(NameValueTyp params)
throws ThrowableString
{
}
//-----------------------------------------------------------
public
void init(NameValueTyp params)
throws ThrowableString
{
//System.out.println("init()");
//throw new ThrowableString("ERR:
init() - TBGenericDB - need to write some code
here!");
}
//-----------------------------------------------------------
public
void finish(NameValueTyp params)
throws ThrowableString
{
System.out.println("TBGenericDB.finish()");
System.out.println("TBGenericDB Connector
Stopped...");
}
//-----------------------------------------------------------
public
void beginTransaction(NameValueTyp
params) throws ThrowableString
{
}
//-----------------------------------------------------------
public
void commitTransaction(NameValueTyp
params) throws ThrowableString
{
}
//-----------------------------------------------------------
public
void rollBackTransaction(NameValueTyp
params) throws ThrowableString
{
}
}
The object
containing the main function This is where the Servlet you have created is instantiated and run by an
instance of ServletRunner.
package com.softbolts.connectors.TBGenericDB;
import com.softbolts.lib.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.text.*;
import java.lang.*;
//-----------------------------------------------------------
public class TBGenericDB
{
static public
void main(String args[])
{
Servlet
svr = new Servlet(args);
try {
ServletRunner sr = new ServletRunner(svr, args);
sr.run();
} catch (ThrowableString
s) {
System.err.println(s.getMessage());
}
}
}
All the following
code is available in the engine tar ball in the sub-directory dev/src/C++/connectors/MigrationDB
Our sample
connector consists of two objects:
1)
Account
2)
BankDetails
Finally you
need to implement the Servlet
#ifndef ACCOUNT_INCLUDED
#define ACCOUNT_INCLUDED
#include "MWTypes.hh"
class Account : public ServletObj
{
public:
Account();
~Account()
{};
int
run(NameValueTyp ¶ms);
int
Get(NameValueTyp ¶ms);
int
Set(NameValueTyp ¶ms);
int
Create(NameValueTyp ¶ms);
int
Update(NameValueTyp ¶ms);
int
Delete(NameValueTyp ¶ms);
private:
string
Key1;
string
Name;
string
Surname;
string
Address1;
string Address2;
string
Address3;
};
#endif // ACCOUNT_INCLUDED
#include "Account.hh"
//----------------------------------------------------
Account::Account()
{
Key1 = "001";
Name = "Anthony";
Surname = "
Address1 = "Via Riccione 6";
Address2 = "Fregene";
Address3 = "
}
//----------------------------------------------------
int Account::run(NameValueTyp ¶ms)
{
if
(params["sysMethod"]
== "Set")
return
Set(params);
else
if (params["sysMethod"]
== "Get")
return
Get(params);
else
if (params["sysMethod"]
== "Create_Account")
return
Create(params);
else
if (params["sysMethod"]
== "Update_Account")
return
Update(params);
else
if (params["sysMethod"]
== "Delete_Account")
return
Delete(params);
params["sysResultString"] = "ERR: Account::run()
- no method with name: " + params["sysMethod"];
return
EXIT_FAILURE;
}
//----------------------------------------------------
int Account::Get(NameValueTyp ¶ms)
{
cerr << "Get
Account--------------------------------------------------------" << endl;
params["Key1"] = Key1;
params["Name"] =
Name;
params["Surname"]
= Surname;
params["Address1"]
= Address1;
params["Address2"]
= Address2;
params["Address3"]
= Address3;
return
EXIT_SUCCESS;
}
//----------------------------------------------------
int Account::Set(NameValueTyp ¶ms)
{
cerr << "Set Account" << endl;
Key1 = params["Key1"];
Name = params["Name"];
Surname = params["Surname"];
Address1 = params["Address1"];
Address2 = params["Address2"];
Address3 = params["Address3"];
return
EXIT_SUCCESS;
}
//----------------------------------------------------
int Account::Create(NameValueTyp
¶ms)
{
cerr << "Create Account" << endl;
return
EXIT_SUCCESS;
}
//----------------------------------------------------
int Account::Update(NameValueTyp
¶ms)
{
cerr << "Create Account" << endl;
return
EXIT_SUCCESS;
}
//----------------------------------------------------
int Account::Delete(NameValueTyp
¶ms)
{
cerr << "Create Account" << endl;
return
EXIT_SUCCESS;
}
#ifndef BANKDETAILS_INCLUDED
#define BANKDETAILS_INCLUDED
#include "MWTypes.hh"
class BankDetails : public
ServletObj
{
public:
BankDetails();
~BankDetails() {};
int
run(NameValueTyp ¶ms);
int
Get(NameValueTyp ¶ms);
int
Set(NameValueTyp ¶ms);
int
Create(NameValueTyp ¶ms);
int Update(NameValueTyp ¶ms);
int
Delete(NameValueTyp ¶ms);
};
#endif // BANKDETAILS_INCLUDED
#include "BankDetails.hh"
//----------------------------------------------------
BankDetails::BankDetails()
{
}
//----------------------------------------------------
int BankDetails::run(NameValueTyp
¶ms)
{
// Get and Set methods are
compulsory for each object
// The
engine will call these to retrieve the objects
if
(params["sysMethod"]
== "Set")
return
Set(params);
else
if (params["sysMethod"]
== "Get")
return
Get(params);
else
if (params["sysMethod"]
== "CreateBankDetails")
return
Create(params);
else if (params["sysMethod"] ==
"UpdateBankDetails")
return
Update(params);
else
if (params["sysMethod"]
== "DeleteBankDetails")
return
Delete(params);
params["sysResultString"] = "ERR: BankDetails::run()
- no method with name: " + params["sysMethod"];
return
EXIT_FAILURE;
}
//----------------------------------------------------
int BankDetails::Get(NameValueTyp
¶ms)
{
// Here you implement the
Get method which
// may consist of a search
on a db
// or perhaps an
implementation using
// third party AIPs
// any mesage
printed out to stderr will
// appear in the log file
// values for each field
declared must be
// retuned in the params map
cerr << "Get BankDetails"
<< endl;
params["Key1"] =
"001";
params["Name"] =
"Anthony";
params["Surname"]
= "
return
EXIT_SUCCESS;
}
//----------------------------------------------------
int BankDetails::Set(NameValueTyp
¶ms)
{
cerr << "Set BankDetails"
<< endl;
return
EXIT_SUCCESS;
}
//----------------------------------------------------
int BankDetails::Create(NameValueTyp
¶ms)
{
cerr << "Create BankDetails"
<< endl;
return
EXIT_SUCCESS;
}
//----------------------------------------------------
int BankDetails::Update(NameValueTyp
¶ms)
{
cerr << "Create BankDetails"
<< endl;
return
EXIT_SUCCESS;
}
//----------------------------------------------------
int BankDetails::Delete(NameValueTyp
¶ms)
{
cerr << "Create BankDetails"
<< endl;
return
EXIT_SUCCESS;
}
This ties
the objects and the connector library together by implementing the initialization,
and finish routines. Note that the specification of the object in the
constructor could just as well be declared in the initialization routine.
#include "Servlet.hh"
#include "Account.hh"
#include "BankDetails.hh"
//----------------------------------------------------
Servlet::Servlet(char **argv)
{
//----------------------------------------------------------
// Instanciate the object you want the
connector to represent
BankDetails
*b = new BankDetails();
Account *a = new Account();
//-----------------------------------
// Set the
connector's specifications
//-----------------------------------
// Alternative is to get a
definition
// in XML and read that in
with a
// utility
//-----------------------------------
servletState = running;
//
-----------------------------------
servletName = "MigrationDB"; //
The Name Identifying this connector
servletType = connector; // The Name
Identifying this connector
//
AddConfigParam(string paramName, int size,
// string allow, bool display);
AddConfigParam("Connection
String", 20, "^[A-Za-z0-9_]*$", true); // Configurable parameters
AddConfigParam("Login",
20, "^[A-Za-z0-9_]*$", true);
AddConfigParam("Password",
20, "^[A-Za-z0-9_]*$", false);
// false =
Don't display string in style sheet
//------------------------------------
//
For object Account
AddObject("Account"); //
The object name
AddField("Account",
"Key1"); //
The fields in this object
AddField("Account",
"Name");
AddField("Account",
"Surname");
AddField("Account",
"Address1");
AddField("Account",
"Address2");
AddField("Account",
"Address3");
//
The methods availale for
this object
AddMethod("Account",
"Get", a);
//
Get and Set Are mandatory for every object
AddMethod("Account",
"Set", a);
AddMethod("Account",
"Create_Account", a);
AddMethod("Account",
"Update_Account", a);
AddMethod("Account",
"Delete_Account", a);
//------------------------------------
AddObject("BankDetails"); //
For object BankDetails
AddField("BankDetails", "Key1");
AddField("BankDetails", "Name");
AddField("BankDetails", "Surname");
AddMethod("BankDetails", "Get", b);
AddMethod("BankDetails", "Set", b);
AddMethod("BankDetails", "Create_BankDetails",
b);
AddMethod("BankDetails", "Update_BankDetails",
b);
AddMethod("BankDetails", "Delete_BankDetails",
b);
}
//----------------------------------------------------
void Servlet::commitTransaction(NameValueTyp ¶ms)
throw(exception)
{
cerr << "commitTransaction
called" << endl;
}
//----------------------------------------------------
void Servlet::beginTransaction(NameValueTyp ¶ms)
throw(exception)
{
cerr << "beginTransaction
called" << endl;
}
//----------------------------------------------------
void Servlet::rollBackTransaction(NameValueTyp ¶ms)
throw(exception)
{
cerr << "rollBackTransaction
called" << endl;
}
//----------------------------------------------------
void Servlet::finish(NameValueTyp ¶ms)
throw(exception)
{
cerr << "finish called" << endl;
}
//----------------------------------------------------
void Servlet::service(NameValueTyp ¶ms)
throw(exception)
{
}
//----------------------------------------------------
void Servlet::init(NameValueTyp ¶ms)
throw(exception)
{
// check
to see if all the parameters we need to be initializes
// are
actually initialized before we have a go at initializing
if
(configParams["Login"].set == true)
{
}
}
#-----------------------------------------------------------
# Defines do the following:
# ------------------------
# DEBUG_LEVELn turns on various levels of
debugging
DEFINES = -DDEBUG_LEVEL3 -DDEBUG_LEVEL2 -LINUX
TARGET_EXE =
../../../bin/MigrationDB
#------------------------------------------------------------
# Your
source here
SOURCES = Servlet.cc Account.cc
BankDetails.cc
#-----------------------------------------------------------
all: $(TARGET_EXE)
#-----------------------------------------------------------
INCLUDE_DIR = -
PROJ_LIBS = -L../../../lib -lthunderbolt
#-----------------------------------------------------------
# the following are special GNU-make variables
#-----------------------------------------------------------
LOADLIBES = $(PROJ_LIBS)
LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
#-----------------------------------------------------------
#.SUFFIXES: .pc
#
#.pc.$(CXX_EXT):
# $(PROC) $(PROCFLAGS) iname=$*
$(PROCPPFLAGS)
#-----------------------------------------------------------
# Force auto-dependency checking using GNU make and g++
# (lifted from the GNU make info pages)
# Make sure the lines below are tabbed out and not spaced out
# or this will not work on some system
%.d: %.$(CXX_EXT)
$(SHELL) -ec 'g++ -MM $(INCLUDE_DIR) $< \
| sed '\''s/\($*\)\.o[ :]*/\1.o
$@ : /g'\'' > $@; \
[ -s $@ ] || rm -f $@'
include $(SOURCES:.$(CXX_EXT)=.d)
#-----------------------------------------------------------
OBJECTS = $(SOURCES:.$(CXX_EXT)=.o)
$(TARGET_EXE): $(OBJECTS) ../../../lib/libthunderbolt.a
g++
-o $(TARGET_EXE) $(OBJECTS) $(PROJ_LIBS) -lgcc -lpthread
clean:
rm *.d *.o $(TARGET_EXE)
export M_WARE_PATH=/home/thunderbolt/dev
export M_WARE_LOGS=$M_WARE_PATH/log
export M_WARE_TRANSSPECDIR=$M_WARE_PATH/TransSpec
export M_WARE_PERSISTDIR=$M_WARE_PATH/Persist
export M_WARE_CONFIGDIR=$M_WARE_PATH/ConConfig
export M_WARE_TMP=$M_WARE_PATH/tmp
export M_WARE_TID=$M_WARE_PATH/tid
export M_WARE_ENGINEHOSTNAME=ganymede
export M_WARE_ENGINESOCKET=4000
export M_WARE_PERSISTENCE=false