Entity Reporting
Low-Level Reporting
When being executed, a report gathers a list of root object ids based on its fixed internal semantics, which can be defined by stored procedures, search expressions or an existing array of ids. Subsequently, this list is persisted in the storage table, along with the report's id, allowing an N:M mapping between reports and root entity objects. The purpose of this approach is to have fast reusable access on an entity set in list models, where usually thousands of records are to be loaded and displayed at once, and all other occasions when dealing with lists of entity items. Therefore, only the root entities' ids are loaded and stored internally whereby the actual entity data fields (the select dimension of the query - which is defined by utilizing FieldPaths) are loaded separately via SQL joins and thereby reusing the report's result. Thus, only the requested data needs to be transferred to the caller, omitting unnecessary column data.
Reports carry the following information:
unique id of the report
data source: MAIN/MASTER/SUPPLIER
purpose: TEMPORARY (report is going to be deleted after a specific amount of time) or PERMANENT
type: root entity type (e.g. ArticleType)
result table: Name of the storage table containing the report's object ids. Either ReportStoreTempA[0..n] / ReportStoreTempB[0..n] if temporary or ReportStore if permanent report.
count: number of ids this report consists of
Depending on its purpose, the relevant mappings between root entity ids and reports are either stored in the ReportStoreTempA[0..n] / ReportStoreTempB[0..n] (for temporary reports - this is most often the case) or ReportStore[0..n] table (permanent reports).
For performance reasons, each table ReportStoreTempA, ReportStoreTempB and ReportStore exists n times, where n is configurable (default is 10). Reports are respectively stored in these n table instances following the round robin algorithm.
Temporary reports are rotationally stored in either ReportStoreTempA[0..n] tables or ReportStoreTempB[0..n] tables with a cleanup job continuously switching between those tables and executing a TRUNCATE statement of the table to be used henceforth, as well as deleting the report header data in Report table associated with this table.
In order to reuse and work with reports, they support additional operations like subtract, intersect, copy and delete via the ReportService.
ReportQuery: The ReportQuery class represents the input for a stored procedure based report. This implies the name of the data source, the EntityType of the item's to be reported, the query method (stored procedure or search expression), the purpose of the report, the origin of the report, etc.
Reporting stored procedures
Note that reporting stored procedures are deprecated, since they are database dependant and thus maintaining them is very expensive. Instead search expressions should be used. Nonetheless, reporting stored procedures must fulfill a special API contract so they can be used in the framework. Reporting stored procedures must have an OUT parameter called FCA_ReportSQL which holds the select statement returning the distinct IDs of the query. No order-by clause is allowed, since this is being done by the reporting framework. No DISTINCT is needed, because duplicates are removed by the reporting framework.
ReportResult: A report result is the consequence of an executed report query. It represents an immutable report proxy, referring to a report which has been executed and stored in the database. It holds the name of the data source (MAIN / MASTER / SUPPLIER) where the report has been executed and stored, the persisted report's database-internal unique id, the report's type and purpose, the name of the report's result table name (ReportStoreTempA / ReportStoreTempB / ReportStore), as well as current amount of reported items.
ReportUtils: This deprecated utility class provides methods for executing report queries and creating reports based on given item ids. It also includes a method for deleting non-temporary reports. Note: Since version 6.0 ReportService shall be used instead.
ReportCleanerJob: This job runs every hour and deletes temporary reports from the database.
Entity Report
In order to provide the means for generic report contributions, a new report approach has been elaborated which enhances the possibilities of the Low-Level reporting, thus it's not a replacement, it's an extension.
EntityReport: The EntityReport represents an extension/contribution to the com.heiler.ppm.entity.core.entityReports extension point. It incorporates the needed information for providing generic reports:
Typed parameters, which can have default values in case not mandatory
Entity of the repository this report stores ids of
The datasource provider for specifying which db is targeted (MAIN/MASTER/SUPPLIER)
An EntityReportExecutor which basically contains the logic/semantic of the report query. The StoredProcExecutor can be used for wrapping the old ReportQuery (with parameter mapping, stored procedure name, ...). As a second executor, the Product Manager search expressions can be used in order to build HQL based queries.
EntityReportQuery: The query keeps a reference to the report plus the necessary information for a report to be executed, mainly the actual values for the report parameters.
EntityReportResult: The actual entity result report contains a reference to the executed query and represents an immutable result information of a persisted report, including the same attributes as in ReportResult (e.g. id, datasource, count, purpose)
EntityReportRegistry: This is the central point for registering reports to be contributed and accessing by alias etc
Custom entity reports can be contributed through the com.heiler.ppm.entity.core.entityReports extension point. To be able to access them through the report service they need to have an alias name defined.
Example
When contributing new entity reports, the following properties are most important. Note that a detailed property description is also available via mouse hover tool tips in the Eclipse extension menu.
Property Name |
Purpose |
id |
unique id for the entity report |
item-entity |
entity type to be returned |
alias |
report alias to be used in service api as well as in report service |
data source |
data source the report has to be executed against either FixDataSourceProvider (MAIN/MASTER/SUPPLIER) or com.heiler.ppm.catalog.core.report.DataSourceByCatalogProvider for having the data source determined dynamically by parent catalog |
supports-service-api |
if on true, this report can be called from service api |
As mentioned above, entity reports need an executor for the report's semantics, which can be specified in the executor node. Here, either add a class implementing EntityReportExecutor, or add a sp-executor-spec child node, where the name of the stored procedure to be called has to be specified. Entity report parameters can be added as additional nodes to the report node, with the most important properties being:
Property Name |
Purpose |
id |
unique id of the parameter |
name |
parameter name, which can be externalized for i18n use |
alias |
service api alias for the parameter |
visible |
if set to true (default), parameter is visible in the ui. If set to false, parameter should be set to non-mandatory (see below) or must have a default value |
mandatory |
parameter mandatory for report execution or not |
enum |
repository based enum providing the possible values |
collection |
if set to true, a collection of values is expected. By default is false. |
Additionally, a showForEntity key-value node can be added to the report node, which is used to decide whether this report shall be displayed in the UI context menu and if so, for which entity the entry shall appear (e.g. MasterCatalog or SupplierCatalog). This key-value is cappable of taking more than just one entity, just separate the entities with a comma. (e. g. MasterCatalog,SupplierCatalog)
Contributed reports can be utilized/examined in the Product Manager client by calling predefined report, like in our example show-> items by status reached:
All the information, including the different status values for the respective parameter in our example, are pulled generically from the contribution. All changes taken within the contribution's plugin.xml are immediately reflected in the UI.
As you can see, such entity report queries can also be edited by pressing "Edit query" in the table view menu, where e.g. a different status can be selected as a parameter.
Programmatic execution of entity reports
The following test code shows the example from above (MasterArticlesByStatus) to be called in programmatic manner via the ListModelLoadService, thereby executing the report implicitly and loading the relevant matching items in one shot.
package
com.heiler.ppm.article.core.internal.characteristic.compare;
import
org.eclipse.core.runtime.CoreException;
import
org.eclipse.core.runtime.NullProgressMonitor;
import
com.heiler.ppm.catalog.core.CatalogConst;
import
com.heiler.ppm.entity.core.report.EntityReport;
import
com.heiler.ppm.entity.core.report.EntityReportQueryBuilder;
import
com.heiler.ppm.entity.core.report.EntityReportQueryItemList;
import
com.heiler.ppm.entity.core.report.EntityReportRegistry;
import
com.heiler.ppm.repository.EntityType;
import
com.heiler.ppm.repository.path.FieldPath;
import
com.heiler.ppm.repository.util.RepositoryUtils;
import
com.heiler.ppm.std.core.entity.EntityProxy;
import
com.heiler.ppm.std.core.entity.EntityProxyFactory;
import
com.heiler.ppm.std.core.list.ListEntry;
import
com.heiler.ppm.std.core.list.ListModel;
import
com.heiler.ppm.std.core.list.ListModelLoadParams;
import
com.heiler.ppm.std.core.list.ListModelLoadService;
@SuppressWarnings
(
"nls"
)
public
class
ArticlesByStatusReportExecutor
{
private
EntityReportRegistry entityReportRegistry;
private
EntityProxyFactory entityProxyFactory;
private
ListModelLoadService listModelLoadService;
private
EntityType catalogEntityType;
public
ArticlesByStatusReportExecutor( EntityReportRegistry entityReportRegistry,
EntityProxyFactory entityProxyFactory,
ListModelLoadService listModelLoadService )
{
this
.entityReportRegistry = entityReportRegistry;
this
.entityProxyFactory = entityProxyFactory;
this
.listModelLoadService = listModelLoadService;
this
.catalogEntityType = RepositoryUtils.getRepositoryRootEntityType(
"CatalogType"
);
}
public
void
executeArticlesByStatusReport()
throws
CoreException, InterruptedException
{
//Obtain report from registry
EntityReport report =
this
.entityReportRegistry.getReport(
"com.heiler.ppm.article.core.MasterArticlesByStatus"
,
false
);
//Create query builder
EntityReportQueryBuilder queryBuilder =
new
EntityReportQueryBuilder( report );
// set parameters:
// obtain proxy for master catalog
EntityProxy catalogProxy =
this
.entityProxyFactory.createEntityProxy(
this
.catalogEntityType,
null
,
CatalogConst.MASTER_ID );
// Items from master catalog
queryBuilder.setParamValue(
"Catalog"
, catalogProxy );
// All items with status "new" -> key 100
queryBuilder.setParamValue(
"Status"
,
100
);
// Entity item list for specifying list model's row results
EntityReportQueryItemList itemList =
new
EntityReportQueryItemList( queryBuilder.toQuery() );
//Get List Model
// We want the ean field to be loaded for the list model
FieldPath[] fieldPaths =
new
FieldPath[] {
new
FieldPath(
"ArticleType.Ean"
) };
// Parameters for specifying list model's result. Rows by entity item list, columns by field paths
ListModelLoadParams loadParams =
new
ListModelLoadParams( itemList, fieldPaths );
// Load the list model, internally calls the report query executor for running and persisting the entity report
ListModel lm =
this
.listModelLoadService.loadListModel(
new
NullProgressMonitor(), loadParams );
// Obtain the list model's entries
for
( ListEntry entry : lm )
{
String ean = ( String ) entry.getFieldValue(
0
);
System.out.println(
"item ean = "
+ ean );
}
}
}
A corresponding test executing this report code could look like the following:
@Test
public
void
runArticlesByStatusReportExecutor()
throws
CoreException, InterruptedException
{
EntityReportRegistry entityReportRegistry = EntityReportComponent.getReportRegistry();
EntityProxyFactory entityProxyFactory = EntityManagementComponent.getEntityProxyFactory();
ListModelLoadService listModelLoadService = EntityManagementComponent.getListModelLoadService();
ArticlesByStatusReportExecutor articlesByStatusReportExecutor =
new
ArticlesByStatusReportExecutor(
entityReportRegistry, entityProxyFactory, listModelLoadService );
articlesByStatusReportExecutor.executeArticlesByStatusReport();
}