Package org.epics.ioc.db


EPICS JavaIoc: Database Access
package: org.epics.ioc.dbAcess
2007.01.09

See:
          Description

Interface Summary
DBData The base interface for accessing a field of a record instance.
DBDataCreate Create DBData field implementations.
DBLink Interface for a IOC database link.
DBListener DB listener interface.
DBRecord Interface for a record instance.
IOCDB IOCDB (Input/Output Controller Database).
IOCDBMergeListener A listener to call after and IOCDB has been merged into the master IOCDB.
RecordListener This is an interface used for communication between DBRecordBase and derived classes.
 

Class Summary
AbstractDBArray Abstract base class for any DBArray field.
AbstractDBData Abstract class for implementing scalar DB fields.
DBDataFactory Factory to create default implementations for field data
DBEnumBase Abstract base class for DBEnum
DBLinkBase Base class for PVLink.
DBMenuBase Abstract base class for DBMenu.
DBRecordBase Abstract base class for a record instance.
DBStructureBase  
IOCDBFactory Factory for creating an IOCDB.
XMLToIOCDBFactory Factory to convert an xml file to an IOCDatabase and put it in the database.
 

Package org.epics.ioc.db Description


EPICS JavaIoc: Database Access
package: org.epics.ioc.dbAcess
2007.01.09

CONTENTS

Overview


An EPICS IOC contains a memory resident real time database. The real time database has a set of "smart" records. Each record is an instance on a record of a particular type. This package describes the database that holds record instances. The following is discussed:

  1. Record Instance Syntax
    The XML syntax for defining record instances.
  2. An XML to database converter.
    Parses xml record instance files, creates record instances, and puts them in an IOC database.
  3. Record Instance Access
    Interfaces for accessing data in record instances.
  4. Data Factory
    A factory for creating support for database fields. It can create support for all types of fields.
  5. Abstract and Base Classes
    A set of abstract and base classes for implementing PVData interfaces.
  6. IOC Database
    Interface and factory for a database containing the record instances
  7. Listen Support
    A description of the support for monitoring changes to the data in database instances fields.

Record Instance Syntax


A Record Instance file must be an XML file with a root tag of IOCDatabase:
    <?xml version="1.0" ?>
    <IOCDatabase>
      <?-- valid Record Instance Definitions -->
    </IOCDatabase>

General Statements

namespace

At this time namespaces are not used.

Include

The XML file can include other files also containing Record Instance Definitions. An included file must also be a valid XML Record Instance Definitions. Included files can also include other files. The syntax is:
    
<include addPath = "path" removePath = "path" href = "filename" />

Where

href
The filename, which must be a valid XML Record Instance file, is processed. If any addPaths have been defined the last one specified is prefixed to the filename.
addPath
Add a path.
removePath
Remove a path.

Macro Substitution

Macro substitution replaces a string of the form "${from}" with some other text. The syntax is:

    <substitute from = "fromString" to = "toString" fromTo = "from=to,from=to,..."/>

Where:

from
fromString is the string that appears in ${from}. If from is specified then to must also be specified.
to
toString replaces ${from}
toFrom
The attribute value is a series of "from=to" pairs separated by commas.

Macro substitution can be performed on the foillowing:

  1. Any attribute value in any element definition.
  2. The content of any element definition.

Example Include and Macro Substitution

The following is a template file:

<?xml version="1.0" ?>
<IOCDatabase>
<record name = "ai${recordExtension}Record" type = "aiRecord">
    <aiInput structureName = "aiLinear" >
        <aiRaw>
            <input supportName = "inputLink">
                <configure structureName = "inputLink">
                    <pvname>${pvname}</pvname>
                    <wait>true</wait>
                </configure>
            </input>
        </aiRaw>
        <units>volts</units>
        <displayLimit>
            <low>${displayLow}</low>
            <high>${displayHigh}</high>
        </displayLimit>
        <linearConvert>
            <engUnitsLow>${engUnitsLow}</engUnitsLow>
            <engUnitsHigh>${engUnitsHigh}</engUnitsHigh>
        </linearConvert>
    </aiInput>
    <priority>medium</priority>
</record>
</IOCDatabase>

The following creates two instance files from the template:

<?xml version="1.0" ?>
<IOCDatabase>
<include addPath = "src/org/epics/ioc/dbAccess/example" />
<substitute from = "recordExtension" to = "01" />
<substitute from = "pvname" to = "nameFor01" />
<substitute from = "displayLow" to = "0.0" />
<substitute from = "displayHigh" to = "10.0" />
<substitute from = "engUnitsLow" to = "0.0" />
<substitute from = "engUnitsHigh" to = "9.0" />
<include href = "protoAiDB.xml" />
<substitute fromTo = "recordExtension=02,pvname=nameFor02" />
<include href = "protoAiDB.xml" />
</IOCDatabase>

record

The syntax for a record instance is:

<record type = "recordType" name = "recordName" supportName = "supportName">
    fieldAssignment
</record>

recordName is a string with a combination of the following characters:

supportName is the name of support. If not given the supportName specified in the record type definition is used. In either case a support definition with the supportName must exist. Package org.epics.ioc.dbProcess provides support that creates support for each record instance. See that package for details. If the same record instance appears multiple times then then last supportName is used.

A fieldAssignment has the format:

    <fieldName supportName = "supportName" >
        <!-- initializer -->
    </fieldName>

fieldName must be a name definied in the record type definition.

Any field can optionally have associated support. If a supportName is given with a field instance then it overides any support defined in the record type field definition. Thus support is only used if the record support for the record instance calls the support methods. If a field is initialized multiple times than the last supportName definition determines the support.

If multiple field instance definitions appear then the last instance determines how the field is initialized.

The syntax for the initializer depends on the field type.

configure

If a supportName is defined for a record instance of for a field on a record instance and the support definition specifies a configuration structure then a configure XML element can be used to initialize the fields of the configuration structure. This configure element MUST appear immediately after the record instance or field instance definition.

For example assume the following Database Definitions:

    <structure name = "dummy">
        <field name = "xxx" type = "string" />
        <field name = "yyy" type = "int" />
    </structure>
    
    
    <support name = "exampleSupport" configurationStructureName = "dummy"
        factoryName = "org.epics.ioc.support.Dummy" />

Then an instance of field link can be initialized as follows:

 <value supportName = "exampleSupport">
        <configure structureName = "dummy">
            <xxx>xxxxxx</xxx>
            <yyy>5</yyy>
        </configure>
        This is a string value
    </value>

This example was for a field that has type string but similar definitions can be given for any field that has associated support.

If the support is for a structure field then the configuration must be given before any fields of the structure. If the support is for a record type, the configuration must be given before any fields of the record instance.

Primitive Types

For primitive types the initializer has the same format as the Java constants for the type. For example if the type for field value is double:

    <value supportName = "supportName" >.98</value>
Notes:

string

For string types the initializer is a valid Java string constant. For example:

    <units>voltage</units>

structure

Structure fields are initialized via a recursive definition of field.

Assume the following structure and recordType definitions:

    <structure name = "displayLimit">
        <field name = "low"><double/></field>
        <field name = "high"><double /></field>
   </structure>
   ...
   <recordType name = "ai">
   ...
       <field name = "displayLimit>
            <structure name = "displayLimit" />
       <field/>
   ...

displayLimit is initilized as follows:.

    <displayLimit>
         <low>0.0</low>
         <high>10.0</high>
    </displayLimit>

Note that supportName can optionally be defined

It is permissible to define a field to be a structure without providing a structure name. In this case the structureName must be provided when the field is being created. The syntax is:

    <fieldName structureName = "structureName" >

array

The syntax for an array initializer is:
    <fieldName capacity = "capacity" supportName = "supportName" >
        <value offset = "offset">value</value>
        <value offset = "offset">valueList</value>
        ...
    </fieldName>

where

supportName
Optional support.
capacity
The amount of storage to allocate for the array. This is optional and the capacity will be equal to the number of elements initialized.
offset
offset for the next value. If not specified it starts at 0 and is incremented by one as each new value is defined.
value
assigns a value to the array. If the array is an array of structures then the value is actually a set of field definitions. If the array is an array of array the value is another array initialization.
valueList
A list of values, which is a comma separated set of values. This is only supported for primitive and string types.

Assume the following are part of a recordType definition:

    <field name = "intArray">
        <array>
            <type><int /></type>
        </array>
    </field>
    <field name = "structArray">
        <type>
            <array>
                <structure name = "DisplayLimit />
            </array>
        </type>
    </field>

Then the following all perform the same initialization:

    <intArray capacity = "3" >
        <value>0.0, 1.0, 2.0</value>
    </intArray>
    <intArray capacity = "3" offset = "1">
        <value>1.0, 2.0</value>
    </intArray>
    <intArray>
        <value>0.0</value>
        <value>1.0</value>
        <value>2.0</value>
    </intArray>
         

The following initializes a structArray

    <structArray capacity = "2">
        <value>
            <low>0.0</low>
            <high>10.0</high>
        </value>
        <value>
            <low>-10.0</low>
            <high>10.0</high>
        </value>
    </structArray>

A menu field is initialized as follows:

    <fieldName supportName = "supportName" >stringValue</fieldName>
where stringValue is one of the allowed values for the menu.

For example:

    <scan>periodic</scan>

Again supportName is optional.

enumerated

An enumerated field is initialized as follows:

    <fieldName supportName = "supportName" >
        <choice>choice</choice>
        stringValue
        ...
    </fieldName>

where

supportName
Optional support
choice
A choice string. One of these must be specified for each choice.
stringValue
one of the allowed values for the enum.

For example:

    <enumExample>
         <choice>zeroState</choice>
         <choics>oneState</choice>
         oneState
    </enumExample>
A link field is initialized as follows:
    <fieldName supportName = "supportName" >
        <configure structureName = "supportStructureName">
            <fieldName>fieldValue</fieldName>
            ...
        </configure>
    </fieldName>

where

supportName
The name of one of the support definitions
supportName
The name of the support structure given with the support definition
fieldValue
The value for the structure field.

XML to Database Converter


NOTE : To create definitions for an IOC, use org.epics.ioc.util.IOCFactory.initDatabase instead of directly calling XMLToIOCDBFactory. initDatabase calls XMLToIOCDBFactory and also initializes all support. Database instances should not be added to the master IOCDB unless all support initializes and starts correctly.

The following reads an xml file containing record instance definitions and puts them into an IOCDB.

    public class XMLToIOCDBFactory {
        public static void convert(DBD dbd, IOCDB iocdb, String fileName,
            Requestor requestor);
        public static IOCDB convert(String iocdbName,String fileName,
            Requestor requestor);
    }
  
convert(DBD dbd, IOCDB iocdb, String fileName, Requestor requestor)
Parse an xml file conaining record instance definitions and put the resulting record instances into an IOC database.
convert(String iocdbName,String fileName, Requestor requestor)
Create an IOC Database (IOCDB) with name iocdbName and populate it with definitions from an XML file. The definitions are not added to the master IOCDB but the caller can call IOCDB.mergeIntoMaster to add them to master. The DBD database is named master. Attempting to add definitions for a record instance that is already in master is an error.

The method arguments are:

dbd
The Database Definition Database.
iocdb
The database that holds the record instances.
fileName
The filename relative to the current working directory that contains the Database Definitions.
requestor
A listener for all messages generated while the method is execuiting.
iocdbName
The name of the IOCDB into which definitions are created.

For an IOC convert should be used to create record instances. This can be done during IOC initialization or after an IOC is running, i.e. on-line add of new record instances is supported. Only one user at a time is allowed to call convert or convert. If XMLToIOCDBFactory is busy when another call is made an error is generated and nothing is done. Both methods are thread safe.


Database Access


This section describes interfaces for acessing fields of record instances. These interfaces are of interest to the database, record support, link support, Channel Access, etc.

All IOC database fields are extended version of the PVData interfaces described in package org.epics.ioc.pv. The differenct is that every PVData field is also a DBData and every PVRecord is also a DBRecord.

DBData provides the following features:

DBRecord provides the following features:

Listener Interface

DBListener

DBListener is an interface that must be implemented by code that calls DBdata.addListener, which is a request to be notified whenever a field changes value. A request can be made for any field including structure fields or even a record instance. If the request is made for a structure field then the requester will be notified whenever any field of the structure changes value.

DBListener has the definition:

    public interface DBListener {
        void dataPut(DBData dbData);
        void enumIndexPut(PVEnum pvEnum);
        void enumChoicesPut(PVEnum pvEnum);
        void supportNamePut(DBData dbData);
        void configurationStructurePut(PVLink pvLink);
        void beginPut(PVStructure pvStructure);
        void endPut(PVStructure pvStructure);
        void dataPut(PVStructure pvStructure,DBData dbData);
        void enumIndexPut(PVStructure pvStructure,PVEnum pvEnum);
        void enumChoicesPut(PVStructure pvStructure,PVEnum pvEnum);
        void supportNamePut(PVStructure pvStructure,DBData dbData);
        void configurationStructurePut(PVStructure pvStructure,PVLink pvLink);
        void beginProcess();
        void endProcess();
        void unlisten(RecordListener listener);
    }
where
dataPut
A data put has occured. This will not be called for enum, menu, or link fields.
enumIndexPut
A put to the index field of an enum or menu has occured.
enumChoicesPut
The put to the choices of an enum has occured.
supportNamePut
The support name has been changed.
configurationStructurePut
The configuration structure of a link field has been changed.
beginPut
A series of puts to a structure is starting.
endPut
A series of puts to a structure has completed.
beginProcess
The record is starting processing.
endProcess
The record has completed processing.
unlisten
The data source is undergoing destruction or major structural changes. The DBData.addListener call is no longer valid and the user should null the object reference returned by addListener.

For dataPut,...,configurationStructurePut two versions are provided. The first is called if the listener attaches directly the the field that is being modified. The second version is called if the caller attaches to a structure that has the field located somewhere in it. The PVStructure argument is the structure to which the listener attached. Not in particular that the listener can listen to all changes in a record instance by attaching to the record instance itself, because it is also a PVStructure.

Assume that a field named value has properties status and severity. Then when the record is processed and field value is written then the following calls might occur:

RecordListener

RecordListener is an interface that is implemented by the code that implements DBRecord. This is normally class DBRecordBase. DBRecord provides a method to create a RecordListener. See class FieldDataFactory and DBRecordBase for details. The definition of RecordListener is:

    public interface RecordListener {
        DBListener getDBListener();
    }

At the end of this document is a example implementation of DBListener.

DBData

The following is the base interface for accessing a field of a record instance.

    public interface DBData extends PVData {
        DBRecord getDBRecord();
        void addListener(RecordListener listener);
        void removeListener(RecordListener listener);
        void postPut();
        Support getSupport();
        void setSupport(Support support);
    }

where

getDBRecord
Return the DBRecord interface for the record instance.
addListener
Add a monitor listener. The RecordListener interface must be created by calling DBRecord.createRecordListener. If the DBData is a structure field the listener will be called when any field in the structure is modified.
removeListener
Remove a listener.
getSupport
Get the support for this field. This returns null if no support exists.
setSupport
Set the support for the field. It can be null.

A link is a field with no data but can have an associated configuration structure.

    public interface DBLink extends DBData,PVLink {
        String newSupport(DBDLinkSupport linkSupport,DBD dbd);
    }

The single method is used to set the support.

DBRecord

    interface DBRecord extends DBData, PVRecord {
        void lock();
        void unlock();
        void lockOtherRecord(DBRecord otherRecord);
        RecordProcess getRecordProcess();
        boolean setRecordProcess(RecordProcess recordProcess);
        int getRecordID();
        void beginProcess();
        void endProcess();
        RecordListener createRecordListener(DBListener listener);
        void removeRecordListener(RecordListener listener);
        void removeRecordListeners();
        void addListenerSource(AbstractDBData dbData);
        DBD getDBD();
        void setDBD(DBD dbd);
        IOCDB getIOCDB();
        void setIOCDB(IOCDB iocdb);
    }

where

lock
Lock the record. The record must be locked for record processing and whenever the data of any field in a record is accessed.
unlock
Unlock the record.
lockOtherRecord
While holding the lock for this record lock another record. The lock for the cxurrent record might be unlocked while this call is active but upon retirn both record will be locked. The caller must unlock both records when the caller is done. Normally it unlocks the other record, via it's unlock method, first.
getRecordProcess
Get the interface of the RecordProcess for this instance.
setRecordProcess
Set the RecordProcess inyerface for this record instance.
getRecordID
Normally only called by DBRecord.lock. Each record instance is assigned a unique id.
createRecordListener
Create a RecordListener. The listener is called when record processing starts and when it completes. The record listener can also be used to call DBData.addListener.
removeRecordListener
Remove a record listener. This will also call removeListener for all DBDatas that have this listener attached.
removeRecordListeners
Remove all listeners. This is called if a record insance is being removed or if it's structure is being changed.
addListenerSource
Add a listener source. Used for communication between DBRecordBase.java and AbstractDBData. AbstractDBData calls this the first time DBData.addListener is called. It should not be called by other code.
getDBD
Get the DBD that this record instance uses. See package org.epics.ioc.dbDefinition for an explaination of a DBD.
setDBD
Set the DBD that this record instance uses.
getIOCDB
Get the IOCDB that holds this record instance.
setIOCDB
Set the IOCDB that holds this record instance. Note that this changes after record initialization.

DBDataCreate


A factory is available that implements the interfaces for record instance fields. The methods are called by XMLToIOCDBFactory when it is reading instance definitions.

    interface DBDataCreate {
        DBData createData(DBData parent,Field field);
        DBData createEnumData(DBData parent,Field field, String[] choice);
        PVArray createArrayData(DBData parent,Field field,
            int capacity,boolean capacityMutable);
        DBRecord createRecord(String recordName, DBDRecordType dbdRecordType);
    }

    public class DBDataFactory {
        public static DBDataCreate getDBDataCreate();
    }
where
createData
Creates an instance of any type of field except enum, array fields, or a record instance itself.
createEnumData
Create an enum field instance.
createArrayData
Create an array field instance.
createRecord
Create a record instance.

Abstract and Base Classes


This section describes abstract classes for implementing the data interface for database fields. These classes are used by DBDataFactory and can also be used by code that wants to provide special implementations of database fields. The only abstract classes are AbstractDBdata and AbstractDBArray. The remaining classes are base classes that provide complete implementations but can be extended. Any code that wants to provide its own implementation should extend AbstractDBdata ot the appropriate base class.

AbstractDBData

Abstract base class for a scalar field. Any code that implements scalar fields should extend this class since it implements almost all methods except the methods to actually get or put data.

    public abstract class AbstractDBData extends AbstractPVData implements DBData{

        protected AbstractDBData(DBData parent, Field field);
        protected void replaceField(Field field);
        protected void setRecord(DBRecord record);
        protected void removeRecordListeners();

        public DBRecord getDBRecord();
        public void replacePVData(PVData newPVData); // override PVData
        public void addListener(RecordListener listener);
        public void removeListener(RecordListener listener);
        public final void postPut();
        public String setSupportName(String name);
        public Support getSupport();
    }

AbstractDBArray

Abstract base class for implementing PVArray. Any code that implements a PVArray field for an IOC database should extend this class.

    public abstract class AbstractDBArray extends AbstractDBData implements PVArray{
        protected int length = 0;
        protected int capacity;
        protected boolean capacityMutable = true;
        abstract public void setCapacity(int capacity);

        public AbstractDBArray(DBData parent,Array array,
            int capacity,boolean capacityMutable);
        public boolean isCapacityMutable();
        public int getCapacity();
        public int getLength();
        public void setLength(int len);
    }

DBEnumBase

Base class for an enumerated field. Any code that implements DBEnum should extend this class.

    public class DBEnumBase extends AbstractDBData implements PVEnum {
        public AbstractDBEnum(DBData parent,Enum enumField, String[]choice);
        public int getIndex();
        public void setIndex(int index);
        public String[] getChoices();
        public boolean setChoices(String[] choice);
        public String toString();
        public String toString(int indentLevel);
    }

DBMenuBase

Base class for a menu field. Any code that implements DBMenu should extend this class.

    public class DBMenuBase extends DBEnumBase implements PVMenu
    {
        public DBMenuBase(DBData parent,Menu menu,String[] choice)
    }

DBStructureBase

Abstract base class for a structure field. Any code that implements DBStructure should extend this class.

    public class DBStructureBase extends AbstractDBData implements PVStructure
    {
        // constructor for structure fields
        public DBStructureBase(DBData parent, Structure structure);
        // constructor for record instances
        protected DBStructureBase(DBDRecordType dbdRecordType)
        // called by record instance constructor
        protected void createFields(DBRecord record);
        public boolean replaceStructureField(String fieldName, String structureName);
        public PVData[] getFieldPVDatas();
        public void beginPut();
        public void endPut();
        public String toString();
        public String toString(int indentLevel);
        protected String toString(String prefix,int indentLevel)
    }

DBLinkBase

    public class DBLinkBase extends AbstractDBData implements DBLink
    {
        public DBLinkBase(DBData parent,Field field);
        public String newSupport(DBDLinkSupport linkSupport,DBD dbd);
        public PVStructure getConfigurationStructure();
        public boolean setConfigurationStructure(PVStructure pvStructure);
        public String toString();
        public String toString(int indentLevel);
    }

DBRecordBase

Abstract base class for a record instance.

    public class DBRecordBase extends DBStructureBase implements DBRecord {
        public DBRecordBase(String recordName,DBDRecordType dbdRecordType);
        public void message(String message, MessageType messageType);
        public String getRecordName();
        public void lock();
        public void unlock();
        public void lockOtherRecord(DBRecord otherRecord);
        public RecordProcess getRecordProcess();
        public boolean setRecordProcess(RecordProcess recordProcess);
        public int getRecordID();
        public void beginProcess();
        public void endProcess();
        public RecordListener createRecordListener(DBListener listener);
        public void removeRecordListener(RecordListener listener);
        public void removeRecordListeners();
        public void addListenerSource(AbstractDBData dbData);
        public DBD getDBD();
        public void setDBD(DBD dbd);
        public IOCDB getIOCDB();
        public void setIOCDB(IOCDB iocdb);
        public String toString();
        public String toString(int indentLevel);
    }

IOCDB: IOC Database


An IOCDB is a database for a java IOC. All methods IOCDBFactory are thread safe. The instances of IOCDB created by IOCDBFactory are also thread safe. ReadWrite locks are used to implement thread safety.

    public interface IOCDBMergeListener {
        void merged();
    }

    public interface IOCDB {
        IOCDB getMaster();
        String getName();
        void mergeIntoMaster();
        void addIOCDBMergeListener(IOCDBMergeListener listener);
        DBRecord findRecord(String recordName);
        boolean addRecord(DBRecord record);
        boolean removeRecord(DBRecord record);
        Map<String,DBRecord> getRecordMap();
        void message(String message, MessageType messageType);
        void addRequestor(Requestor requestor);
        void removeRequestor(Requestor requestor);
        String recordList(String regularExpression);
        String recordToString(String regularExpression);
    }

    public class IOCDBFactory {
        public static IOCDB create(String name);
        public static IOCDB getMaster();
    }
 

where

merged
A listener method that is called after the database has been merged into the master IOC database.
getName
The IOC database Name.
getDBD
Get the Database Definition database that this DBD uses.
mergeIntoMaster
Merge the record instances from this database into the master database.
addIOCDBMergeListener
Add a listener to call after this database has been merged into the master IOC database. If this is the master than the listener is called immediately.
findRecord
Return the interface for the record with name recordName or null if the record is not found. A search is made this database. If not found and a master database exists than it is also serched.
addRecord
Add a record instance. (false,true) is returned if the instance (was not,was) added to the database. A record is not added if it already exists in this database or in the master database.
removeRecord
Remove the record. (false,true) is returned if the record (was not,was) removed.
getRecordMap
Get a shallow copy of the map of all record instances in this database. A copy is returned to ensure thread safety.
message
Report a message. If no listeners are registered the messages are sent to System.out. If listeners are registered they are called.
addMessageListener
Add a message listener.
removeMessageListener
Remove a message listener.
createAccess
Create a DBAccess. See below for a description of IOC Database Access.
recordList
Return a string that contains a list if all record names that match the regular expression.
recordToString
Return a dump of all record instances with names that match the regular expression.

IOCDBFactory is a class that manages IOCDB databases.

The methods are:

create
Create an IOCDB or return the master IOCDB if the name is "master".
getMaster
Get the master IOC Database, i.e. the database with name "master".

Listener Support


The classes AbstractDBData and DBRecordBase provide support for implementing database monitors, i.e. support for notifying a client whenever a field of a database instance changes value.

A client must implement the interface DBListener. A client can listen to an arbitrary number of fields in a record. It must first obtain a Listen interface by calling dbRecord..createListener:

    DBRecord dbRecord;
    ...
    DBListener listener = dbRecord.createListener(this);

and then, for each field it wants to monitor makes the call:

    dbData.addListener(listener);

The support provides the following features:

The following is sample code that listens for all changes to a field and all properties of the field.

public class TestListener implements DBListener{
    private RecordListener listener;
    private String pvName = null;
    private boolean verbose;
    private String actualFieldName = null;
    private boolean isProcessing = false;
    private String fullName = null;

    public TestListener(IOCDB iocdb,String recordName,String pvName,
        boolean monitorProperties,boolean verbose)
    {
        this.pvName = pvName;
        this.verbose = verbose;
        PVRecord pvRecord = iocdb.findRecord(recordName);
        if(pvRecord==null) {
            System.out.printf("record %s not found%n",recordName);
            return;
        }
        PVAccess pvAccess = PVAccessFactory.createPVAccess(pvRecord);
        DBData pvData;
        if(pvName==null || pvName.length()==0) {
            pvData = (DBData)pvAccess.getPVRecord();
        } else {
            if(pvAccess.findField(pvName)!=AccessSetResult.thisRecord){
                System.out.printf("name %s not in record %s%n",pvName,recordName);
                System.out.printf("%s\n",pvAccess.getPVRecord().toString());
                return;
            }
            pvData = (DBData)pvAccess.getField();
        }
        actualFieldName = pvData.getField().getFieldName();
        fullName = pvData.getPVRecord().getRecordName() + pvData.getFullFieldName();
        listener = pvData.getDBRecord().createRecordListener(this);
        pvData.addListener(listener);
        if(monitorProperties) {
            if(pvData.getField().getType()!=Type.pvStructure) {
                Property[] property = pvData.getField().getPropertys();
                DBData propertyData;
                for(Property prop : property) {
                    pvAccess.setPVField(pvData);
                    if(pvAccess.findField(prop.getPropertyName())
                    !=AccessSetResult.thisRecord){
                        System.out.printf("name %s not in record %s%n",pvName,recordName);
                        System.out.printf("%s\n",pvAccess.getPVRecord().toString());
                    } else {
                        propertyData = (DBData)pvAccess.getField();
                        propertyData.addListener(listener);
                    }
                }
            }
        }
    }

    public TestListener(IOCDB iocdb,String recordName,String pvName)
    {
        this(iocdb,recordName,pvName,true,true);
    }

    private String putCommon(String message) {
        if(!verbose) {
            return fullName + " ";
        }
        return String.format("%s %s isProcessing %b pvName %s actualFieldName %s%n",
            message,
            fullName,
            isProcessing,
            pvName,
            actualFieldName);
    }
   
    public void beginProcess() {
        isProcessing = true;
        putCommon("beginProcess");
    }
  
    public void endProcess() {
        putCommon("endProcess");
        isProcessing = false;
    }
    
    public void beginPut(PVStructure pvStructure) {
        if(!verbose) return;
        DBData dbData = (DBData)pvStructure;
        String name = dbData.getPVRecord().getRecordName()
            + pvStructure.getFullFieldName();
        System.out.println("beginPut " + name);
    }
 
    public void endPut(PVStructure pvStructure) {
        if(!verbose) return;
        DBData dbData = (DBData)pvStructure;
        String name = dbData.getPVRecord().getRecordName()
            + pvStructure.getFullFieldName();
        System.out.println("endPut " + name);
    }
    
    public void dataPut(DBData dbData) {
        String common = putCommon("dataPut");
        if(!verbose) {
            System.out.println(common + dbData.toString(1));
            return;
        }
        String name = dbData.getPVRecord().getRecordName() + dbData.getFullFieldName();
        if(!name.equals(fullName)) {
            System.out.printf("%s%s NOT_EQUAL %s%n",common,name,fullName);
        }
        System.out.printf("%s    %s = %s%n",
            common,name,dbData.toString(2));
    }
    
    public void enumChoicesPut(PVEnum pvEnum) {
        String common = putCommon("enumChoicesPut");
        if(!verbose) {
            System.out.println(common + pvEnum.toString(1));
            return;
        }
        String name = pvEnum.getPVRecord().getRecordName() + pvEnum.getFullFieldName();
        if(!name.equals(fullName)) {
            System.out.printf("%s %s NOT_EQUAL %s%n",common,name,fullName);
        }
        System.out.printf("%s    %s = %s%n",
            common,name,pvEnum.toString(2));
    }
 
    public void enumIndexPut(PVEnum pvEnum) {
        String common = putCommon("enumChoicesPut");
        if(!verbose) {
            System.out.println(common + pvEnum.toString(1));
            return;
        }
        String name = pvEnum.getPVRecord().getRecordName() + pvEnum.getFullFieldName();
        if(!name.equals(fullName)) {
            System.out.printf("%s %s NOT_EQUAL %s%n",common,name,fullName);
        }
        System.out.printf("%s    %s index = %d%n",
            common,name,pvEnum.getIndex());
    }
   
    public void supportNamePut(DBData dbData) {
        String common = putCommon("supportNamePut");
        String name = dbData.getPVRecord().getRecordName() + dbData.getFullFieldName();
        if(!name.equals(fullName)) {
            System.out.printf("%s %s NOT_EQUAL %s%n",common,name,fullName);
        }
        System.out.printf("%s    %s = %s%n",
            common,name,dbData.getSupportName());
    }
 
    public void configurationStructurePut(PVLink pvLink) {
        String common = putCommon("configurationStructurePut");
        String name = pvLink.getPVRecord().getRecordName() + pvLink.getFullFieldName();
        if(!name.equals(fullName)) {
            System.out.printf("%s %s NOT_EQUAL %s%n",common,name,fullName);
        }
        System.out.printf("%s%n    %s = %s%n",
            common,name,pvLink.getConfigurationStructure().toString(2));
    }
    
    public void dataPut(PVStructure pvStructure, DBData dbData) {
        String structureName =
            pvStructure.getPVRecord().getRecordName()
            + pvStructure.getFullFieldName();
        String common = putCommon(structureName +" dataPut to field "
            + dbData.getFullFieldName());
        System.out.printf("%s    = %s%n",common,dbData.toString(2));
    }
    
    public void enumChoicesPut(PVStructure pvStructure,PVEnum pvEnum) {
        String structureName =
            pvStructure.getPVRecord().getRecordName()
            + pvStructure.getFullFieldName();
        String common = putCommon(structureName +" enumChoicesPut to field "
           + pvEnum.getFullFieldName());
        System.out.printf("%s    = %s%n",common,pvEnum.toString(2));
    }
    
    public void enumIndexPut(PVStructure pvStructure,PVEnum pvEnum) {
        String structureName =
            pvStructure.getPVRecord().getRecordName()
            + pvStructure.getFullFieldName();
        String common = putCommon(structureName + " enumIndexPut to field "
            + pvEnum.getFullFieldName());
        System.out.printf("%s    index = %d%n",common,pvEnum.getIndex());
    }
 
    public void supportNamePut(PVStructure pvStructure,DBData dbData) {
        String structureName =
            pvStructure.getPVRecord().getRecordName()
            + pvStructure.getFullFieldName();
        String common = putCommon(structureName + " supportNamePut to field "
            + dbData.getFullFieldName());
        System.out.printf("%s    = %s%n",common,dbData.getSupportName());
    }
    
    public void configurationStructurePut(PVStructure pvStructure,PVLink pvLink) {
        String structureName =
            pvStructure.getPVRecord().getRecordName()
            + pvStructure.getFullFieldName();
        String common = putCommon(structureName
            + " configurationStructurePut to field "
            + pvLink.getFullFieldName());
        System.out.printf("%s%n    = %s%n",
            common,pvLink.getConfigurationStructure().toString(2));
    }
    
    public void unlisten(RecordListener listener) {
        // Nothing to do.
    }

}

In order for the listen support to work support that implements the DBRecord interface must extend AbstractDBRecord and support that implements any other DB field interface must extend AbstractDBdata. In addition any code that implements a put methods must call

    postPut();

whenever the put method is called. It must call postPut whenever if modifies its internal data for the field. It must call postPut even if the new value is the same as the old value.


Thread Safety


The dbAccess components are designed to be used in an IOC, which is a multithreaded environment. The design includes:
XMLToIOCDBFactory
This is thread safe because only one user at a time can be using it. If a call to XMLToIOCDBFactory.convert is made while it is alreadty busy, a fatal error message is sent to the caller and convert returns.
IOCDB
All methods are thread safe.
Record Instance interfaces.
While accessing fields of a record instance the instance must be locked. This means that DBRecord.lock must be called before accessing fields and DBRecord.unlock must be called after the caller is done. See package org.epics.ioc.dbProcess for more details about record processing.