Hi Rakesh,
If you use ORM, you actually do not need neither initializing of a DataSet nor extracting field names from templates nor using of reflection on his own, to implement your scenario. Here is an example of how this can be done. Let’s pretend, you have the following domain entities:
public class Product {
Product(String name) {
mName = name;
}
public String getName() {
return mName;
}
private final String mName;
}
public class Contract {
Contract(String customer, double price, Iterable<Product> products) {
mCustomer = customer;
mPrice = price;
mProducts = products;
}
public String getCustomer() {
return mCustomer;
}
public double getPrice() {
return mPrice;
}
public Iterable<Product> getProducts() {
return mProducts;
}
private final String mCustomer;
private final double mPrice;
private final Iterable<Product> mProducts;
}
Then, to satisfy your requirements, the following class can be used to represent a data source object passed to the reporting engine:
public class ReportData {
ReportData(int managerId) {
// Pass an identifier used to query a data collection here.
// It can be of any type, int is used as an example.
mManagerId = managerId;
}
public Date getCreateDate() {
// Return a field value. It can be extracted from a GUI element,
// fetched from a web-service or a database, etc.
// If obtaining of a particular field is an expensive operation,
// lazy initialization may be applied.
// The same approach can be applied to all Field1, Field2, etc.
return new Date();
}
public String getAuthor() {
// The same comments as for getCreateDate.
return "John Doe";
}
public Iterable<Contract> getContracts() {
// Assuming that fetching of a data collection is an expensive operation,
// lazily initialize and cache the corresponding field.
if (mContracts == null) {
mContracts = fetchContracts();
}
return mContracts;
}
private Iterable<Contract> fetchContracts(){
// Here mManagerId should be used, but we return just an example collection.
return Arrays.asList(
new Contract("Customer A", 500, Arrays.asList(new Product("Product A1"), new Product("Product A2"))),
new Contract("Customer B", 300, Arrays.asList(new Product("Product B1"), new Product("Product B2")))
);
}
private final int mManagerId;
private Iterable<Contract> mContracts;
}
The following code snippet creates an example report using the above class:
// Create a template document.
DocumentBuilder builder = new DocumentBuilder();
builder.writeln("Create date: <<[getCreateDate()]>>");
builder.writeln("Author: <<[getAuthor()]>>");
builder.writeln("Contracts: <<foreach [in getContracts()]>>");
builder.writeln(" Customer: <<[getCustomer()]>>");
builder.writeln(" Contract price: <<[getPrice()]>>");
builder.writeln(" Products: <<foreach [in getProducts()]>>");
builder.writeln(" - <<[getName()]>><</foreach>><</foreach>>");
// Build a report.
ReportingEngine engine = new ReportingEngine();
engine.buildReport(builder.getDocument(), new ReportData(123));
// Print the report's content.
System.out.println(builder.getDocument().toTxt());
Notes to the example:
-
Members of domain entities (as well as of custom types like ReportData) can be referenced directly in templates. Thus, there is no need to wrap a collection of objects with a DataSet.
-
Once the reporting engine encounters a member invocation, it invokes the member and that’s it. Thus, simply to provide field values to the engine, there is no need to preliminarily analyze a template and extract names of fields (once again, template expressions are not just fields), calculate corresponding values through reflection, and add them to a map.
-
A drawback is, ReportData should contain members corresponding to all fields (i.e. “field1”, “field2”, etc.). Since the field set is huge, you may consider this as too hard to implement. But these fields are defined somewhere in code anyway. So in fact, you only need to move those definitions to this single ReportData class.
Hope, this helps.
Best regards,