Working with Custom Resources

Documentation home

What is a Custom Resource? 1

Writing the implementing Java class 1

Creating a Custom Resource in the Ebase Designer 6

Custom Resource toolbar 10

Programming Considerations 11

Field types 11

Maintaining state. 11

Serializability. 11

Transactionality. 12

Coding script commands to access the resource. 12

Bindings 12

Ebase supplied Custom Resources 13

XML Custom Resource. 13

CSV Custom Resource. 13

XML Example Custom Resource. 13

                       

See also:       How Resources Work

Supported Field Types

Table Concepts

Working with CSV Resources

Transaction Support

 

  If you are considering writing a Custom Resource, you should consider using Javascript as an alternative. A Javascript script can access Java classes directly and can in nearly all circumstances achieve the same result as a Custom Resource with much less effort. Click here for details.

 

What is a Custom Resource?

 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 added to a form, its fields can be imported 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.

 

Writing the implementing Java class

 

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:

 

·         CustomResourceInterface

·         ResourceRequestInterface

·         TableDataInterface

·         TableRow

·         TableCell

·         TableRowChangeStatus

·         SelectValue

 

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 );

  

Creating a Custom Resource in the Ebase Designer

 

Create a custom resource by right clicking in the designer tree and selecting New > Custom Resource.

 

 

 

Enter the fully qualified name of the java class implementing 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 panel below is displayed:

 

          

 

 

Resource Description: 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: name of the implementing Java class

 

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.

 

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).

 

Custom Resource toolbar

 

 

* 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.

* Maintain documentation for this resource.

* Show information: dates for creation, last update and import of this Email Resource.

* Shows this help page.

 

 

Programming Considerations

 

Field types

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.

 

Maintaining state

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.

 

Serializability

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.

 

Transactionality

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)

 

Coding script commands to access the resource

 

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.

 

Bindings

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");

 

Ebase supplied Custom Resources

 

The following are all implementations of custom resource and have been created as described above.

 

XML Custom Resource

 

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)

 

XML Example Custom Resource

 

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>