Appboard/old/advanced data adapter: Difference between revisions

imported>Jay.barr
imported>Jay.barr
Line 106: Line 106:
== Incomplete Records / findById() ==
== Incomplete Records / findById() ==


Incomplete records are any GenericRecords that have been marked with <tt>record.setComplete(false);</tt>, which informs the client that when accessing the details of that record, the client will need to first request the rest of the record. This is done automatically by the client through the <tt>findById()</tt> method.
Incomplete records are any GenericRecords that have been marked with <tt>record.setComplete(false)</tt>, which informs the client that when accessing the details of that record, the client will need to first request the rest of the record. This is done automatically by the client through the <tt>findById()</tt> method.


If records are marked as incomplete in the <tt>fetchRecords()</tt> method, <tt>findById()</tt> must be implemented to get the missing data to the client and to prevent the client from asking the server for an update every time any attribute of the record is access.  This is because the retrieval is triggered by a check of the record on the client on every access to any attribute value, and if the record is incomplete, <tt>findById()</tt> is called with the intention of getting a complete record.
If records are marked as incomplete in the <tt>fetchRecords()</tt> method, <tt>findById()</tt> must be implemented to get the missing data to the client and to prevent the client from asking the server for an update every time any attribute of the record is access.  This is because the retrieval is triggered by a check of the record on the client on every access to any attribute value, and if the record is incomplete, <tt>findById()</tt> is called with the intention of getting a complete record.

Revision as of 22:22, 1 September 2011

Prerequisites

These examples use the same system configuration as the Creating An AppBoard Data Adapter examples. Review the required environment and verify it using the steps on that page before continuing.

Advanced Data Adapter Concepts

The previous examples demonstrate how to develop the necessary callback methods for the AppBoard system to interrogate an adapter for its Entities and related settings. It also demonstrated the use of fetchRecords() to retrieve all the content for a given Entity. While this approach is sufficient for many adapters, the advanced topics in this walkthrough will illustrate how to be more efficient in memory and network bandwidth usage through the use of other methods to retrieve the data.

These examples will demonstrate the following concepts:

  1. Partial record retrieval -- Instead of retrieving all the attributes for each record, the adapter can return only some of the attributes, and when needed, the client will request the remainder
  2. Custom query implementation -- Default query mechanism relies on the server having all records for an entity. If the adapter's data source permits filtered retrieval, the adapter can intercept incoming queries and handle them more efficiently.
  3. (Future) More discrete cache control

Refactoring fetchRecords()

To make the comparison between the previous fetchRecords() implementation and the new methods that will return the data records, the original adapter class was refactored. The new method is:

[java,N] public void fetchRecords(ServiceRequestFacade request, ServiceResponseFacade response) throws Exception { logger.debug("fetchRecords('"+request.getEntity().getNamespace().getFQNamespace()+"') starting");

List<GenericRecord> recordList = new ArrayList<GenericRecord>();

int duration = getSettingAsInt(SETTING_DURATION, 600); int dataInterval = getSettingAsInt(SETTING_DATA_INTERVAL, 10);

long currentMs = System.currentTimeMillis(); long currentSec = currentMs/1000;

long startSec = ((currentSec - duration) / dataInterval) * dataInterval; // align to interval

for (long timeSec = startSec; timeSec <= currentSec; timeSec += dataInterval) { EntityDef entity = request.getEntity(); fillSineRecords(recordList, timeSec, entity, true, "Set in fetchRecords()"); }

response.addGenericRecords(recordList);

logger.debug("fetchRecords('"+request.getEntity().getNamespace().getFQNamespace()+"') completed"); }

The main change is that the GenericRecords are no longer constructed in fetchRecords(), but instead in a method fillSineRecords(). That method, and those it calls, are:

[java,N] private void fillSineRecords(List<GenericRecord> recordList, long timeSec, EntityDef entity, boolean sineComplete, String sineDescription) { int value = getSineValue(timeSec);

Date timestamp = new Date(timeSec * 1000);

boolean isWaveEntity = entity.getNamespace().getEntityName().equals(ENTITY_SINE_WAVE); if (isWaveEntity) { GenericRecord record = getSineRecord(timeSec, entity, value, timestamp, sineComplete, sineDescription); recordList.add(record); } else { int assocOffset = getSettingAsInt(SETTING_ASSOCIATED_DATA_POINTS, 3);

for (int i = -assocOffset; i <= assocOffset; i++) { GenericRecord record = new GenericRecord(entity); record.setAttributeValue(ATTR_TIMESTAMP_OFFSET, Long.toString(timeSec) + ':' + Integer.toString(i)); record.setAttributeValue(ATTR_WAVE_KEY, timeSec); record.setAttributeValue(ATTR_TIMESTAMP, timestamp); record.setAttributeValue(ATTR_OFFSET, i); record.setAttributeValue(ATTR_DESCRIPTION, "Offset " + i + " from value " + value + " is " + (value + i));

recordList.add(record); } } }

private int getSineValue(long timeSec) {

int wavePeriod = getSettingAsInt(SETTING_WAVE_PERIOD, 60); int waveAmp = getSettingAsInt(SETTING_WAVE_AMPLITUDE, 50); int waveOffset = getSettingAsInt(SETTING_WAVE_OFFSET, 50);

double phase = ((double) (timeSec % wavePeriod)) / ((double) wavePeriod) * 2.0d * Math.PI; int value = (int) (Math.sin(phase) * waveAmp + waveOffset); return value; }

private GenericRecord getSineRecord(long timeSec, EntityDef entity, int value, Date timestamp, boolean setComplete, String description) { GenericRecord record = new GenericRecord(entity); record.setAttributeValue(ATTR_KEY, timeSec); record.setAttributeValue(ATTR_TIMESTAMP, timestamp); record.setAttributeValue(ATTR_VALUE, value); if (setComplete) { record.setAttributeValue(ATTR_DESCRIPTION, description); } record.setComplete(setComplete); return record; }

Note the only new method exposed, which is GenericRecord.setComplete(boolean isComplete). This method marks the record as either complete or not, with the default being complete. If the record is complete, the client will not request any missing attribute values, which is the behavior seen so far. The next step will explore using incomplete records.

To verify the refactoring works, preview the Sine Wave Data Source. The preview should look similar to:

Ada-fetchRecords.png

Note that the description field shows "Set in fetchRecords()". This will change after the next step.

Incomplete Records / findById()

Incomplete records are any GenericRecords that have been marked with record.setComplete(false), which informs the client that when accessing the details of that record, the client will need to first request the rest of the record. This is done automatically by the client through the findById() method.

If records are marked as incomplete in the fetchRecords() method, findById() must be implemented to get the missing data to the client and to prevent the client from asking the server for an update every time any attribute of the record is access. This is because the retrieval is triggered by a check of the record on the client on every access to any attribute value, and if the record is incomplete, findById() is called with the intention of getting a complete record.