Working with Custom Resources
Writing
the implementing Java class
Creating
a custom resource in the Ebase Designer
Coding
script commands to access the resource.
Ebase
supplied Custom resources
See also: Understanding
Ebase Integration
If you are
considering writing a custom resource, you might also consider using an API based
scripting language (e.g. Javscript) to access Java classes directly. Click here for details.
A custom
resource represents a connection to an external system which is implemented by
a customer written Java class. This gives the ability to connect to any
external system that has an API that can be addressed by Java. Once created,
the custom resource appears to the Ebase designer exactly the same as any other
resource i.e. it can be assigned to a business view, its fields can be imported
into a form and mapped, script statements can be written to interchange data
between a form and the external system. A custom resource can be thought of as
a plug-in to add integration to a specific external system.
The Java
class implementing a custom resource is instantiated the first time the
resource is invoked via a script statement and the same instance is used for
subsequent calls for the duration of the form. It is passed the invoking script
command and an interface allowing the class to access the resource fields - to
check characteristics and to get and set values.
A custom
resource can process both tabular and non-tabular data.
There are two steps to creating a custom resource - write the implementing Java class and then create the resource in the Ebase Designer.
The Java
class must implement interface com.ebasetech.ufs.mapping.CustomResourceInterface
which is included in the UFS.jar file. This interface contains the
following methods:
·
execute() is
the primary point of contact for Ebase with the resource and is called each
time a script command is issued against the resource. This method receives two
parameters: a ResourceRequestInterface object and a String containing the
issued script command. The ResourceRequestInterface is a 'callback' interface
and can be used to access the resource fields to get and set values, check
types and lengths etc, and perform many other functions.
·
fetchTable()
and updateTable() are called when the fetchtable or updatetable FPL
commands or API Table.fetchTable() or
Table updateTable() methods are
issued against a table which is backed by the custom resource. These can be
implemented as empty methods if the custom resource does not provide support
for tables.
·
getParameterNames()
is called by the Ebase Designer to find out which parameters need to be supplied
to the resource by the forms designer.
·
getCommandNames()
is called by the Ebase Designer to find out which script commands are supported
by the resource. Any commands defined here may be used in scripts against this
resource. A list of eligible commands are supplied as
constants in ResourceRequestInterface eg. COMMAND_FETCH, COMMAND_UPDATE etc.
Customer specific commands can also be used.
Please see
the following javadoc for details:
·
TableRow
ResourceRequestInterface
also contains a number of static constants for the possible script commands and
field types.
Here is a
simple example of a custom resource Java class that supports the READ and WRITE
script commands in it's execute() method and does not support tabular data:
//
These imports are required
import com.ebasetech.ufs.mapping.CustomResourceInterface;
import com.ebasetech.ufs.mapping.ResourceRequestInterface;
import com.ebasetech.ufs.kernel.FormException;
import com.ebasetech.ufs.validation.CommandStatus;
public class ResourceTest implements CustomResourceInterface {
public String execute(ResourceRequestInterface resourceInterface, String
command) throws FormException {
// READ command
if (
command.equals(ResourceRequestInterface.COMMAND_READ) ) {
// code to set the resource fields from the external system goes here e.g.
resourceInterface.setFieldValue("FIELD1",
"data for field 1");
resourceInterface.setFieldValue("FIELD2",
"data for field 2");
}
// WRITE command
else if (
command.equals(ResourceRequestInterface.COMMAND_WRITE) ) {
// code to read the resource fields and write to the external system goes here
e.g.
Object a = resourceInterface.getFieldValue2("FIELD1");
Object b = resourceInterface.getFieldValue2("FIELD2");
}
return CommandStatus.STATUS_OK;
}
public void fetchTable( ResourceRequestInterface
resourceRequest, String fieldId ) throws FormException
{
}
public void updateTable( ResourceRequestInterface
resourceRequest, String fieldId ) throws FormException
{
}
public Collection getParameterNames()
{
ArrayList names = new ArrayList();
names.add( "PARM1" );
return names;
}
public Collection getCommandNames()
{
ArrayList names = new ArrayList();
names.add( ResourceRequestInterface.COMMAND_READ );
names.add( ResourceRequestInterface.COMMAND_WRITE );
return names;
}
}
The execute() method must return a string representing the
status of the script command. Ebase takes no action based on this status and
simply returns it to the script where it can be interrogated. The static
constants in class CommandStatus can be used as status codes as shown in the
example above, or additional codes can be added as
required.
e.g.
return "ERR1";
This can be
checked by a script:
FPL: |
API based language
(Javascript): |
read CUSTOM_RESOURCE1; if [ $COMMAND_STATUS = 'ERR1' ]
message 'Custom resource has returned error status ERR1'; endif |
var result =
resources.CUSTOM_RESOURCE1.execute(CustomResource.COMMAND_READ); if (result == "ERR1") {
event.owner.addErrorMessage("Custom resource has returned error
status ERR1"); } |
If a
condition occurs which should cause termination of the form, the method should
throw a FormException. This will result in the exception's error message being
written to the server log file, being displayed as an HTML page and written to
the Ebase Designer execution log.
e.g.
throw new FormException("Custom resource
fatal error - cannot contact external system");
Resources that
support tables must also implement the fetchTable() and updateTable()
methods. These are called explicitly when the fetchtable or updatetable
FPL script commands or API methods Table.fetchTable() or
Table.updateTable() are issued
against a table and the table is backed by a custom resource. The name of the
resource field which is mapped to the table is passed as a parameter. (See Table Concepts for more information)
The fetchTable() method should do the following:
·
Get an empty TableData object using resourceRequest.createNewTableData().
·
Populate this TableData object with rows from
the resource table. It can ask the resourceRequest object which source fields
are being used using resourceRequest.getFieldNames().
·
Set the newly populated TableData back on the
resourceRequest object using resourceRequest.setTableData().
The updateTable() method should do the following:
·
Get the TableData from the resourceRequest using
resourceRequest.getTableData().
·
Interrogate the TableData to find out which rows
have been inserted/deleted/updated.
·
Apply these changes to the resource.
Here are
simple examples of implementation for both of these methods:
//
These imports are required in addition to those listed
above
import com.ebasetech.ufs.mapping.TableDataInterface;
import com.ebasetech.ufs.mapping.TableRow;
import com.ebasetech.ufs.mapping.TableCell;
import com.ebasetech.ufs.mapping.TableRowChangeStatus;
public void fetchTable( ResourceRequestInterface
resourceRequest, String fieldId ) throws FormException
{
Collection fieldNames = resourceRequest.getFieldNames();
TableDataInterface tdi = resourceRequest.createNewTableData();
for each row in the table:
{
TableRow row = tdi.createNewRow();
for( Iterator iter = fieldNames.iterator(); iter.hasNext(); )
{
String fieldName = (String) iter.next();
row.addCell( fieldName, "data for field" );
}
tdi.addRow( row );
}
resourceRequest.setTableData(fieldId, tdi );
}
public void updateTable( ResourceRequestInterface
resourceRequest, String fieldId ) throws FormException
{
TableDataInterface tdi = resourceRequest.getTableData(
fieldId );
for( Iterator rowIter = tdi.getChangedRows(); rowIter.hasNext(); )
{
TableRow row = (TableRow) rowIter.next();
int rowChangeStatus = row.getRowChangeStatus();
switch( rowChangeStatus )
{
case TableRowChangeStatus.DELETED:
// get the row key and delete it from this
resource
Object rowKey = row.getCell(
"key source field name" );
// use the row key to find the row in this
resource and delete it
break;
case TableRowChangeStatus.INSERTED:
// step through the row cells and collect
their values
for( Iterator cellIter = row.getTableCells();
cellIter.hasNext(); )
{
TableCell cell = (TableCell) cellIter.next();
String fieldName = cell.getSourceFieldId();
Object cellValue = cell.getCurrentValue2();
// use this to build up the row to be added.
}
// add the row to this resource
break;
case TableRowChangeStatus.UPDATED:
// as above but use the values to update the existing
row in the resource.
break;
}
}
}
The getParameterNames() and getCommandNames() methods should
just return a simple collection of strings, for example:
private static final String RES_PARAM_PROTOCOL_TYPE =
"ProtocolType";
private static final String RES_PARAM_PROTOCOL_STRING =
"ProtcolString";
private static final String RES_PARAM_FILENAME_PREFIX =
"FilenamePrefix";
private static final String COMMAND_SEND = "Send";
public Collection getParameterNames()
{
ArrayList names = new ArrayList();
names.add( RES_PARAM_PROTOCOL_TYPE );
names.add( RES_PARAM_PROTOCOL_STRING );
names.add( RES_PARAM_FILENAME_PREFIX );
return names;
}
public Collection getCommandNames()
{
ArrayList names = new ArrayList();
names.add( ResourceRequestInterface.COMMAND_READ );
names.add( ResourceRequestInterface.COMMAND_WRITE );
return names;
}
Then, for
example, whenever the resource implementation needs to fetch the value of one
of one of the declared parameters for a specific instance of that resource, it
should use:
resourceRequest.getParameterValue( RES_PARAM_FILENAME_PREFIX );
Open the
custom resource editor by either clicking on an existing custom resource in the
hierarchy tree panel (IT Elements -> External Resource -> Custom
Resource) or in the file menu (File -> New -> External Resource
-> Custom Resource)
When
prompted, enter the fully qualified name of the java class implementing the
CustomResourceInterface. This class must be on the classpath of the Ebase
Server web application (place jar file in ..../ufs/WEB-INF/lib or class
files in .../ufs/WEB-INF/classes). If the class is found and implements
CustomResourceInterface, the dialog box below is displayed:
Resource
Description allows you to provide a description of this custom resource.
The debug
checkbox can be checked by an implementing java class using method isDebug() on ResourceRequestInterface.
Implementing
Java class is supplied on creation of the custom resource and cannot be
changed.
Resource
parameters: Parameters are defined by the CustomResourceInterface
implementation of the getParameterNames() method. This
method should return a collection of the parameter names to be populated for
each instance of the custom resource. These parameters can contain any
information that is required to interface with the external system. Typically,
these fields provide information that uniquely identifies this specific custom
resource. Examples of this are file name and type, transaction name, external
system options etc.
(Note: for
backward compatibility, if the CustomResourceInterface.getParameterNames()
method returns null, it will be assumed that this is an old implementation and
that the original 4 pre-defined parameters are still being used. The getParameterN() methods will still operate as expected but
it is recommended that the implementation be migrated to explicitly declaring
the parameters as soon as possible.)
Implemented
script commands specifies a display only list of the script commands which
are implemented by the custom resource. If a script command is issued which is
not in this list, a runtime error will occur. Descriptions can be added here
for these commands.
Import
from XML schema – (See Import from XML schema)
The Resource
fields section allows you to specify the individual fields supported
by the resource. These can be added and deleted using the icons on the resource
fields toolbar or by right clicking on an existing
field.
The fields
are displayed as a tree that supports a hierarchical structure. This is
primarily to facilitate the support of XML document structures by a custom
resource. Root is always displayed as the first entry - this is a dummy
field that does not actually exist and is not saved in the repository database
- it serves the role of parent for all top level fields. A number of methods
are supplied in ResourceRequestInterface
to enable navigation through the hierarchy, e.g.
getChildren()
getParent()
getChildCount()
hasChildren()
hasParent()
getTopLevelFieldNames()
If a
hierarchical structure is not required by a custom resource implementation, all
fields should be added as children of Root as shown in the example
below.
Field
Name supplies the name of the resource field and must be unique i.e. two
fields cannot have the same name. The field name can be changed by triple
clicking on the name.
External
Name can be used to represent the name of the field as it is known to the external
resource. External names do not need to be unique, however they should be
unique within any given parent otherwise it is not possible to locate a given
resource field using an external name. Two methods in ResourceRequestInterface
provide support for external name:
getFieldExternalName(String fieldName)
getFieldNameForExternalName( String externalName,
String parentFieldName
)
The length
attribute is used during import of a resource field into a form to set the
maximum allowable length for data entry. It is not used for any other purpose.
This can be checked by the custom resource using ResourceRequestInterface
method getFieldLength().
The decimal
digits attribute is used during import of a resource field into a form to
set the maximum allowable decimal digits for data entry. It is not used for any
other purpose. This can be checked by the custom resource using
ResourceRequestInterface method getFieldDecimalDigits().
The Repeats
attribute indicates that the field and its children can occur more than once. Only
fields with this indicator set can be mapped to tables. This can be checked
by the custom resource using ResourceRequestInterface method isFieldRepeatable().
The Key
attribute is not used by Ebase. It is intended to represent structural
information that can be used by a custom resource Java class, for example to
navigate through an XML document. This can be checked by the custom resource
using ResourceRequestInterface method isFieldKey().
Attribute
is only applicable for XML documents and indicates that the field is
represented by an attribute in the XML document. When unchecked, all fields are
represented by elements in the XML document. This can be checked by the custom
resource using ResourceRequestInterface method isFieldAttribute().
The field types
can be any of the Ebase supported field types (See Supported Field Types).
Save: saves the Custom Resource.
This icon is only shown for the Ebase provided
custom resources for XML and Web Services (now deprecated). It provides the
ability to automatically create a resource field hierarchy as specified in an
XML Schema (.xsd file) or WSDL (.wsdl file). When clicked, a dialog is
displayed allowing navigation to an xsd via either URL or local file browser.
The resource field attributes are set based on information in the schema. Where
possible, resource field names will be set the same as an imported element or
attribute. This is not possible when an element references a complex type, and
in this instance the field name will be prefixed with parent name. After the
import, the resource field names can be changed if required, by double clicking
on the name field. In all cases the external name will be set the same as the
imported element or attribute.
Add the resource to one or more Business Views. Supports
both adding to one or more existing Business Views and the creation of a new
Business View. Existing Business Views can only be changed when they are
not already open.
Show information: shows userid and dates for creation,
last update and import of this Custom Resource.
Shows this help page.
The field types supported by a custom resource and how these map to
Java objects is shown in the following table.
Resource field type |
Object returned from getFieldValue2() or TableCell.getCurrentValue2() |
Objects accepted for setFieldValue() or TableCell.setValue() |
CHAR |
String |
String, Integer, Long, Double, BigDecimal, Float, Date, Time, TimeStamp |
NUMBER |
BigDecimal |
String, Integer, Long, Double, BigDecimal, Float |
CURRENCY |
BigDecimal |
String, Integer, Long, Double, BigDecimal, Float |
INTEGER |
BigInteger |
String, Integer, Long, Double, BigDecimal, Float, BigInteger |
BOOLEAN |
Boolean |
Boolean, String |
DATE |
Long - suitable for constructing a java.util.Date (time element is set to midnight). |
Date, Time, TimeStamp, String |
TIME |
Long - suitable
for constructing a java.util.Date (date element set to January 1, 1970) |
Date, Time, TimeStamp, String, Long, Number |
DATETIME |
Long - representing milliseconds since the epoch, January
1, 1970. Suitable for constructing a java.util.Date. |
Date, Time, TimeStamp, String, Long, Number |
If DATE fields
are set from a String, the String must be in the format specified by parameter
Ufs.dateFormat in file UFSSetup.properties.
If BOOLEAN
fields are set from a String, the String must contain either "Y" or
"N".
NOTE:
Fields that are used in tables will be returned in the
TableData in the form of the native Ebase data types. They will NOT be
converted back to the type native to the resource.
The custom
resource Java class is instantiated on the first call (i.e. as a result of the
first script command) from a form, and the same instance of the class is used
for all subsequent calls. Therefore, the class can maintain its own state, if
required. In addition:
· The custom
resource can get and set session context variables by obtaining the Http
session context with method getSessionContext() of ResourceRequestInterface.
· The custom
resource can share state variables with other forms by accessing the system
scratchpad area with methods addScratchPadObject(),
getScratchPadObject(), removeScratchPadObject() of ResourceRequestInterface.
The system scratchpad area is stored in the web application context and is
therefore available for the life of the application server.
All custom
resource Java classes must be serializable. The state is serialized and
deserialized in two circumstances:
·
When the user clicks either the save or restore
buttons
·
By the application server when operating in a
clustered environment
Therefore,
all class level variables should also be serializable (must implement java.io.Serializable).
If this is not possible for any reason, then any non-serializable objects
should be marked as transient and the class should be written so that
any such objects can be re-instantiated if necessary. If this condition is not
met, failures will occur in either of the two circumstances given above.
Ebase
maintains a transaction for each interaction with the end-user. Any
updates that are made by custom resources will be included within this
transactional context providing that the updates are made using the standard
facilities of the J2EE application server - in particular, that the resources
are accessed via the application server's JNDI naming context. The getInitialContext() method of ResourceRequestInterface can
be used to get the JNDI root context for addressing the application server. All
resources accessed in this way will be enlisted in to the transaction by the
J2EE application server.
In
addition, the existing Ebase transaction can be committed or rolled back using methods
commitTransaction() or rollbackTransaction() on ResourceRequestInterface.
In both cases a new transaction is started to contain any additional
processing.
(See Transaction Support for more information)
Using FPL:
This is
exactly the same as for other external resources, e.g. databases. Any FPL
script command that is supported by the custom resource can be used.
e.g.
read RESOURCE1;
write RESOURCE2;
update RESOURCE3;
When issued, all such resource commands will result in a
call to the custom resource's execute()
method.
In addition, the fetchtable and updatetable FPL
commands can be used for tables that are backed by a custom resource. These
will result calls to the custom resource's fetchtable()
and updatetable() methods.
All resource commands can specify a binding as an
optional parameter, e.g.
write BOOKING_XML systemx;
where systemx is a binding. This information is intended to provide the custom resource implementing class with additional information on the specific command being processed. Ebase takes no action based on the binding - it is simply passed through to the resource where it can be accessed by method getBinding() of ResourceRequestInterface.
Using API based language:
Use one of
the following two methods on CustomResource:
String execute(String
command);
String execute(String
command, String binding);
Where the second method allows additional
information to be passed into the resource as a binding. Both methods return a status String.
e.g.
var status1 =
resources.CUSTOM_RESOURCE1.execute(CustomResource.COMMAND_READ);
var status2 =
resources.CUSTOM_RESOURCE2.execute("Search", "Search
string");
The
following are all implementations of custom resource and have been created as
described above.
Represents a connection to an XML resource. The use of this class is now deprecated and has been replaced with XML resources.
CSV Custom resource
Allows writing form data to a .csv file.
(See Working with CSV resources for more information)
The use of this class is now deprecated and has been replaced with XML resources.
Java class XMLCustomResourceExample is supplied as
an example together with an example form. This class provides an example of
generic support for XML document structures. The supplied sample form and XML
document contain two repeating structures mapped to two tables. XPath is used
to navigate within the XML document.
The example
consists of:
·
Form XML_EXAMPLE in project SAMPLES.
·
Custom Resource XML_EXAMPLE
·
Java class XMLCustomResourceExample.
·
XML file flights.xml as shown below.
The Java class source and XML document
can be found in ufs\samples.
<Schedule>
<TDate>10
Nov 2004</TDate>
<Location>Cambridge</Location>
<Flights>
<Flight>
<FlightNo>XA123</FlightNo>
<Departure>08:00</Departure>
<Passengers>
<Passenger>
<Name>Fred Bloggs</Name>
<SpecialNeeds>Veggie</SpecialNeeds>
</Passenger>
<Passenger>
<Name>Anton Bloggs</Name>
<SpecialNeeds></SpecialNeeds>
</Passenger>
</Passengers>
</Flight>
<Flight>
<FlightNo>XA127</FlightNo>
<Departure>10:30</Departure>
<Passengers>
<Passenger>
<Name>Jo Cole</Name>
<SpecialNeeds></SpecialNeeds>
</Passenger>
<Passenger>
<Name>Michael Howard</Name>
<SpecialNeeds>Wheelchair</SpecialNeeds>
</Passenger>
</Passengers>
</Flight>
</Flights>
</Schedule>