Triton meets RxJava
CAIS report
Credit
Account
Information
Sharing
CAIS report
Credit
Account
Information
Sharing
report
database
convert
database
format
map
fetch
write
report
convert
format
map
fetch
write
report
triton-enterprise
batch
cais
database
report
triton-enterprise
database
convert
format
map
fetch
write
batch
cais
Module dependency
batch
cais
triton-enterprise
Module dependency
batch
cais
triton-enterprise
framework-common

Generate the report
public static void generateCaisReport() {
try (final Writer writer = new FileWriter("CAIS-report.txt")) {
final List<CaisDTO> caisDTOs = CaisDAO.fetchAllData();
CaisReport.writeReport(caisDTOs, writer);
} catch (IOException e) {
throw new ApplicationException("Error writing CAIS report file.", e);
}
}public static List<CaisDTO> fetchAllData() {
return new QueryExecutor(new Query()
.select(
"Product.number AS accountNumber",
"Person.relationNumber",
"PP1.isMainProductHolder",
"PP2.personID AS otherPersonID",
"BaProduct.iban",
"Product.activeBeginDate",
"Product.activeEndDate",
"Private.prefixTitle",
"Private.forenames",
"Private.initials",
"Person.fullSurname",
"Private.birthDate",
"Address.coLine",
"Address.subBuilding",
"Address.houseName",
"Address.houseNumber",
"Address.street",
"Address.place",
"Address.locality",
"Address.district",
"Address.region",
"Address.postcode",
"BaProductOverdraftTerms.dtLimitAmount",
"BaProductMgtInfo.balance",
"BaProductMgtInfo.lastDebitTxDate",
"BaProductMgtInfo.lastCreditTxDate"
.from("Product")
.join("ProductType ON ProductType.productTypeID = Product.productTypeID")
.join("BaProduct ON BaProduct.productID = Product.productID")
.join("PersonProduct PP1 ON PP1.productID = Product.productID")
.leftJoin("PersonProduct PP2 ON PP2.productID = PP1.productID AND PP2.productRoleID = PP1.productRoleID AND PP2.personID <> PP1.personID")
.join("Person ON Person.personID = PP1.personID")
.join("Private ON Private.personID = Person.personID")
.join("Address ON Address.personID = Person.personID")
.leftJoin("BaProductOverdraftTerms ON BaProductOverdraftTerms.productID = Product.productID")
.join("BaProductMgtInfo ON BaProductMgtInfo.productID = Product.productID")
.where("Product.activeBeginDate <= ?", getToDate())
.and("Product.activeEndDate IS NULL OR (Product.activeEndDate >= ? AND Product.activeEndDate <= ?)", getFromDate(), getToDate())
.and("ProductType.marketID = " + ID_PERSONAL)
.and("ProductType.productGroupID = " + ID_CURRENTACCOUNT)
.and("ProductType.administrationID = " + ID_TRIODOS_BANK_UK)
.and("BaProduct.signatureCard = " + RECEIVED.getKey())
.and("PP1.productRoleID = " + ID_ACCOUNTHOLDER)
.and("Address.addressTypeID = " + ID_OFFICIAL)
.and("Address.isActive = 'T'")
.and("BaProductMgtInfo.monthRunYearNumber = ?", getFromDate().getYear())
.and("BaProductMgtInfo.monthRunMonthNumber = ?", getFromDate().getMonth())
.getObjects(CaisDTO.class);
}Fetch the data
public static List<CaisDTO> fetchAllData() {
return new QueryExecutor(new Query()
.select(
"Product.number AS accountNumber",
"Person.relationNumber",
...
.from("Product")
.join("PersonProduct ON PersonProduct.productID = Product.productID")
.join("Person ON Person.personID = PersonProduct.personID")
...
.where("PersonProduct.productRoleID = " + ID_ACCOUNTHOLDER)
...
.getObjects(CaisDTO.class);
}Fetch the data
public static void writeReport(final List<CaisDTO> caisDTOs, final Writer writer) {
writeHeaderRecord(writer);
caisDTOs.stream()
.filter(new CaisValidator()::validateDTO)
.map(CaisMapper::mapToRecord)
.forEach(recordData -> writeAccountRecord(recordData, writer));
writeTrailerRecord(caisDTOs.size(), writer);
}WRITE THE REPORT
public static void writeReport(final List<CaisDTO> caisDTOs, final Writer writer) {
writeHeaderRecord(writer);
caisDTOs.stream()
.filter(new CaisValidator()::validateDTO)
.map(CaisMapper::mapToRecord)
.forEach(recordData -> writeAccountRecord(recordData, writer));
writeTrailerRecord(caisDTOs.size(), writer);
}WRITE THE REPORT



Gold-plate the generator
public static void generateCaisReport() {
try (final Writer writer = new FileWriter("CAIS-report.txt")) {
final List<CaisDTO> caisDTOs = CaisDAO.fetchAllData();
CaisReport.writeReport(caisDTOs, writer);
} catch (IOException e) {
throw new ApplicationException("Error writing CAIS report file.", e);
}
}public static void generateCaisReport() {
try (final Writer writer = new FileWriter("CAIS-report.txt")) {
final Stream<CaisDTO> dtoStream = CaisDAO.fetchDataAsStream();
CaisReport.writeReport(dtoStream, writer);
} catch (IOException e) {
throw new ApplicationException("Error writing CAIS report file.", e);
}
}Gold-plate the generator
public static void writeReport(final Stream<CaisDTO> caisDTOs, final Writer writer) {
writeHeaderRecord(writer);
int numRecords = 0;
caisDTOs
.filter(new CaisValidator()::validateDTO)
.map(CaisMapper::mapToRecord)
.forEach(recordData -> {
writeAccountRecord(recordData, writer);
numRecords++;
});
writeTrailerRecord(numRecords, writer);
}Gold-plate the writer
Gold-plate Fetching data
public static Stream<CaisDTO> fetchDataAsStream() {
return new QueryExecutor(new Query()
.select(
"Product.number AS accountNumber",
"Person.relationNumber",
...
.from("Product")
.join("PersonProduct ON PersonProduct.productID = Product.productID")
.join("Person ON Person.personID = PersonProduct.personID")
...
.where("PersonProduct.productRoleID = " + ID_ACCOUNTHOLDER)
...
.getObjects(CaisDTO.class)
.stream();
}Gold-plate Fetching data

public static Stream<CaisDTO> fetchDataAsStream() {
return new QueryExecutor(new Query()
.select(
"Product.number AS accountNumber",
"Person.relationNumber",
...
.from("Product")
.join("PersonProduct ON PersonProduct.productID = Product.productID")
.join("Person ON Person.personID = PersonProduct.personID")
...
.where("PersonProduct.productRoleID = " + ID_ACCOUNTHOLDER)
...
.getObjects(CaisDTO.class)
.stream();
}
public static Stream<CaisDTO> fetchDataAsStream() {
return new QueryExecutor(new Query()
.select(
"Product.number AS accountNumber",
"Person.relationNumber",
...
.from("Product")
.join("PersonProduct ON PersonProduct.productID = Product.productID")
.join("Person ON Person.personID = PersonProduct.personID")
...
.where("PersonProduct.productRoleID = " + ID_ACCOUNTHOLDER)
...
.getObjects(CaisDTO.class)
.stream();
}Gold-plate Fetching data
// class QueryExecutor
public <T> List<T> getObjects(@NotNull final Class<T> clazz) {
List<Object[]> rows = getResults(Page.ALL_ROWS_ON_ONE_PAGE);
RowConverter<T> converter = ReflectionBasedRowConverter.create(clazz,
resultSetColumnNames);
return convert(rows, converter);
}Gold-plate Fetching data
// class MyStreamingQueryExecutor
public <T> List<T> getObjects(final Class<T> clazz) {
List<T> fetchedDTOs = new ArrayList<>();
...
String sql = "SELECT * FROM ...";
Statement statement = jdbcConnection.createStatement(sql);
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
T dto = clazz.newInstance();
doSomeReflectionMagic(resultSet, dto);
fetchedDTOs.add(dto);
}
return fetchedDTOs;
}Gold-plate Fetching data
// class MyStreamingQueryExecutor
public <T> void writeObjects(Class<T> clazz, CaisValidator validator, Writer writer) {
...
String sql = "SELECT * FROM ...";
Statement statement = jdbcConnection.createStatement(sql);
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
T dto = clazz.newInstance();
doSomeReflectionMagic(resultSet, dto);
if (dto instanceof CaisDTO) {
CaisDTO caisDTO = (CaisDTO) dto;
if (validator.validateDTO(caisDTO)) {
final RecordData recordData = CaisMapper.mapToRecord(caisDTO);
CaisReport.writeAccountRecord(recordData, writer);
}
}
}
}Gold-plate Fetching data
// class MyStreamingQueryExecutor
public <T> void processObjects(Class<T> clazz, Consumer<T> processFunc) {
...
String sql = "SELECT * FROM ...";
Statement statement = jdbcConnection.createStatement(sql);
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
T dto = clazz.newInstance();
doSomeReflectionMagic(resultSet, dto);
processFunc.accept(dto);
}
}
// (in cais module)
public void processDTO(CaisDTO dto) {
if (validator.validateDTO(dto)) { // validator?
final RecordData recordData = CaisMapper.mapToRecord(dto);
CaisReport.writeAccountRecord(recordData, writer); // writer?
}
}Gold-plate Fetching data
public static void fetchAndProcess() {
new MyStreamingQueryExecutor(new Query()
.select(
"Product.number AS accountNumber",
"Person.relationNumber",
...
.from("Product")
.join("PersonProduct ON PersonProduct.productID = Product.productID")
.join("Person ON Person.personID = PersonProduct.personID")
...
.where("PersonProduct.productRoleID = " + ID_ACCOUNTHOLDER)
...
.processObjects(CaisDTO.class, CaisReport::processDTO);
}Gold-plate Fetching data
public static void generateCaisReport() {
try (final Writer writer = new FileWriter("CAIS-report.txt")) {
writeHeaderRecord(writer);
fetchAndProcess();
writeTrailerRecord(writer);
} catch (IOException e) {
throw new ApplicationException("Error writing CAIS report file.", e);
}
}Gold-plate the Report
public static void generateCaisReport() {
try (final Writer writer = new FileWriter("CAIS-report.txt")) {
writeHeaderRecord(writer);
fetchAndProcess(); // TODO - count records and use validator and writer
writeTrailerRecord(writer); // TODO - use numAccountRecords
} catch (IOException e) {
throw new ApplicationException("Error writing CAIS report file.", e);
}
}Gold-plate the Report


Javadoc of JDBC Driver for DB2:
By default the driver collects all the results for the query at once. This can be inconvenient for large data sets so the JDBC driver provides a means of basing a ResultSet on a database cursor and only fetching a small number of rows. The fetchSize property differs from the queryDataSize property. fetchSize affects the number of rows that are returned, and queryDataSize affects the number of bytes that are returned.
fetchSize property
Gives the JDBC driver a hint as to the number of rows that should be fetched from the database when more rows are needed for ResultSet objects generated by this Statement. If the value specified is zero, then the hint is ignored. The default value is zero.

RxJava
to the rescue!
Observable pattern
public interface Observer<T> {
void onNext(T item);
void onCompleted();
void onError(Throwable t);
}public interface Observable<T> {
void subscribe(Observer<T> observer);
}Observable pattern
public interface Observable<T> {
void subscribe(Observer<T> observer);
Observable<T> filter(Predicate<T> filterFunc);
<R> Observable<R> map(Function<T, R> mapFunc);
Observable<T> delay(long delay, TimeUnit unit);
Observable<Integer> count();
...
}public interface Observer<T> {
void onNext(T item);
void onCompleted();
void onError(Throwable t);
}// class CaisDAO
public static List<CaisDTO> fetchAllData() {
final List<CaisDTO> fetchedDTOs = new QueryExecutor(new Query()
.select(
"Product.number AS accountNumber",
"Person.relationNumber",
...
.from("Product")
.join("PersonProduct ON PersonProduct.productID = Product.productID")
.join("Person ON Person.personID = PersonProduct.personID")
...
.where("PersonProduct.productRoleID = " + ID_ACCOUNTHOLDER)
...
.getObjects(CaisDTO.class);
return fetchedDTOs;
}Gold-plating (continued)
// class CaisDAO
public static List<CaisDTO> fetchNextPage() {
final List<CaisDTO> fetchedDTOs = new QueryExecutor(new Query()
.select(
"Product.number AS accountNumber",
"Person.relationNumber",
...
.from("Product")
.join("PersonProduct ON PersonProduct.productID = Product.productID")
.join("Person ON Person.personID = PersonProduct.personID")
...
.where("PersonProduct.productRoleID = " + ID_ACCOUNTHOLDER)
...
.getObjects(CaisDTO.class, new Page(FIRST_PAGE, MAX_ROWS));
return fetchedDTOs;
}Gold-plating (continued)
// class CaisDAO
public static List<CaisDTO> fetchNextPage(final FetchContext fetchContext) {
final List<CaisDTO> fetchedDTOs = new QueryExecutor(new Query()
.select(
"Product.number AS accountNumber",
"Person.relationNumber",
...
.from("Product")
.join("PersonProduct ON PersonProduct.productID = Product.productID")
.join("Person ON Person.personID = PersonProduct.personID")
...
.where("PersonProduct.productRoleID = " + ID_ACCOUNTHOLDER)
...
.and("Product.number > ?", fetchContext.getLastFetchedAccountNumber())
.orderBy("Product.number ASC")
.getObjects(CaisDTO.class, new Page(FIRST_PAGE, MAX_ROWS));
return fetchedDTOs;
}Gold-plating (continued)
// class CaisDAO
public static List<CaisDTO> fetchNextPage(final FetchContext fetchContext) {
final List<CaisDTO> fetchedDTOs = new QueryExecutor(new Query()
.select(
"Product.number AS accountNumber",
"Person.relationNumber",
...
.from("Product")
.join("PersonProduct ON PersonProduct.productID = Product.productID")
.join("Person ON Person.personID = PersonProduct.personID")
...
.where("PersonProduct.productRoleID = " + ID_ACCOUNTHOLDER)
...
.and("Product.number > ?", fetchContext.getLastFetchedAccountNumber())
.orderBy("Product.number ASC")
.getObjects(CaisDTO.class, new Page(FIRST_PAGE, MAX_ROWS));
if (!fetchedDTOs.isEmpty()) {
final CaisDTO lastDTO = fetchedDTOs.get(fetchedDTOs.size() - 1);
fetchContext.setLastFetchedAccountNumber(lastDTO.getAccountNumber());
}
return fetchedDTOs;
}Gold-plating (continued)
Create an Observable

public static Observable<CaisDTO> toObservable(
final FetchContext fetchContext,
final Function<FetchContext, List<CaisDTO>> fetchNextPageFunc) {
return Observable.create(
);
}Create an Observable
public static Observable<CaisDTO> toObservable(
final FetchContext fetchContext,
final Function<FetchContext, List<CaisDTO>> fetchNextPageFunc) {
return Observable.create(
observer -> {
});
}Create an Observable
public static Observable<CaisDTO> toObservable(
final FetchContext fetchContext,
final Function<FetchContext, List<CaisDTO>> fetchNextPageFunc) {
return Observable.create(
observer -> {
fetchNextPageFunc.apply(fetchContext) ... // page of List<CaisDTO>
});
}Create an Observable
public static Observable<CaisDTO> toObservable(
final FetchContext fetchContext,
final Function<FetchContext, List<CaisDTO>> fetchNextPageFunc) {
return Observable.create(
observer -> {
fetchNextPageFunc.apply(fetchContext).forEach(observer::onNext);
});
}Create an Observable
public static Observable<CaisDTO> toObservable(
final FetchContext fetchContext,
final Function<FetchContext, List<CaisDTO>> fetchNextPageFunc) {
return Observable.create(
observer -> {
while (fetchContext.isDataAvailable()) {
fetchNextPageFunc.apply(fetchContext).forEach(observer::onNext);
}
});
}Create an Observable
public static Observable<CaisDTO> toObservable(
final FetchContext fetchContext,
final Function<FetchContext, List<CaisDTO>> fetchNextPageFunc) {
return Observable.create(
observer -> {
while (fetchContext.isDataAvailable()) {
fetchNextPageFunc.apply(fetchContext).forEach(observer::onNext);
}
observer.onCompleted();
});
}Create an Observable
public static Observable<CaisDTO> toObservable(
final FetchContext fetchContext,
final Function<FetchContext, List<CaisDTO>> fetchNextPageFunc) {
return Observable.create(
observer -> {
try {
while (fetchContext.isDataAvailable()) {
fetchNextPageFunc.apply(fetchContext).forEach(observer::onNext);
}
observer.onCompleted();
} catch (Exception e) {
observer.onError(e);
}
});
}Create an Observable
public static <F extends FetchContext, R> Observable<R> toObservable(
final F fetchContext,
final Function<F, List<R>> fetchNextPageFunc) {
return Observable.create(
observer -> {
try {
while (fetchContext.isDataAvailable()) {
fetchNextPageFunc.apply(fetchContext).forEach(observer::onNext);
}
observer.onCompleted();
} catch (Exception e) {
observer.onError(e);
}
});
}Create an Observable

public static generateCaisReport() {
try (final Writer writer = new FileWriter("CAIS-report.txt")) {
final List<CaisDTO> dtoList
= CaisDAO.fetchAllData();
CaisReport.writeReport(dtoList, writer);
} catch (IOException e) {
throw new ApplicationException("Error writing CAIS report file.", e);
}
}All-at-once report
public static generateCaisReport() {
try (final Writer writer = new FileWriter("CAIS-report.txt")) {
final Observable<CaisDTO> dtoObservable
= toObservable(new FetchContext(), CaisDAO::fetchNextPage));
CaisReport.writeReport(dtoObservable, writer);
} catch (IOException e) {
throw new ApplicationException("Error writing CAIS report file.", e);
}
}Streaming report
public static void writeReport(
final List<CaisDTO> caisDTOs,
final Writer writer) {
writeHeaderRecord(writer);
caisDTOs.stream()
.filter(new CaisValidator()::validateDTO)
.map(CaisMapper::mapToRecord)
.forEach(recordData -> writeAccountRecord(recordData, writer));
writeTrailerRecord(caisDTOs.size(), writer); // bug!
}All-at-once report
public static void writeReport(
final Observable<CaisDTO> caisDTOs,
final Writer writer) {
caisDTOs
.doOnSubscribe(() -> writeHeaderRecord(writer))
.filter( new CaisValidator()::validateDTOs)
.map( CaisMapper::mapToRecordData)
.doOnNext(recordData -> writeAccountRecord(recordData, writer))
.count()
.subscribe(numRecords -> writeTrailerRecord(numRecords, writer),
LOG::error);
}Streaming report

Triton meets RxJava
By Rob Bosman
Triton meets RxJava
- 71