Working with Custom Resources
Writing the
implementing Java class
Creating a
custom resource in the Ebase Designer4
Coding FPL
script commands to access the resource. 7
Ebase
supplied custom resources
See also:
Understanding Ebase Integration
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, FPL script commands 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 command 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 commands
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 FPL 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 FPL 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 an FPL script:
read CUSTOM_RESOURCE1;
if [ $COMMAND_STATUS = 'ERR1' ]
message 'Custom
resource has returned error status ERR1';
endif
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 script
commands 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:
if the code in the implementing Java class is changed, closing and re-opening
the resource in the Ebase Designer will display the changes
(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
FPL script commands specifies a display only list of the FPL 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.
Note:
if the implementing Java class is changed, closing and re-opening the resource
in the Ebase Designer will display the changes
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).
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 triple clicking on the name field. In all cases the external name
will be set the same as the imported element or attribute.
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 FPL 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)
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 systemxis 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.
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 XMLCustomResourceExample.
(See Working with XML resources for more
information)
CSV Custom resource
Allows writing form data to a .csv file.
(See Working with CSV resources for more
information)
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>
<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>