How to provide a new complex data type
The use case
The product list data provider needs to provide a new sub-data type that delivers items that are linked to products via a third entity "LinkEntity":
Implementing the data type
At first, you need to implement a new export data type that provides fields of "Article" and "LinkEntity" entities.
The best way is to subclass CompositeDataType:
public class DataTypeItemsLinkedToProducts extends CompositeDataType{ public static final String IDENTIFIER = "DataTypeItemsLinkedToProducts"; //$NON-NLS-1$ @Override protected void init() { super.init(); this.identifier = IDENTIFIER; this.name = Messages.getString( "DataTypeItemsLinkedToProducts.name" ); //$NON-NLS-1$ this.description = Messages.getString( "DataTypeItemsLinkedToProducts.description" ); //$NON-NLS-1$ this.isListType = true; this.entityIdentifier = StringUtils.EMPTY; this.entityName = this.name; this.entityTypeIdentifier = StringUtils.EMPTY; this.isInitialized = false; } @Override protected void postInitDataTypes() { DataTypeUtils.addSubDatatype( this.dataTypes, DataTypeUtils.getSubDatatypePart( "LinkEntity" ) ); DataTypeUtils.addSubDatatype( this.dataTypes, DataTypeUtils.getSubDatatypePart( "Article" ) ); super.postInitDataTypes(); }}There're some useful utility methods:
com.heiler.ppm.texttemplate.core.dataprovider.util.DataTypeUtils.addSubDatatype( List subDataTypesList, DataType newDataType );com.heiler.ppm.texttemplate.core.dataprovider.util.DataTypeUtils.getSubDatatypePart( String entityIdentifier );Don't forget to contribute the new data type at the com.heiler.ppm.texttemplate.core.dataTypes extension point:
<extension point="com.heiler.ppm.texttemplate.core.dataTypes"> <dataType class="com.heiler.ppm.custom.product2g.export.core.datatype.DataTypeItemsLinkedToProducts" name="ItemsLinkedToProducts"> </dataType> </extension>Adding the data type as sub-data type of a data provider
Subclass the standard data provider (not a base impl!), e.g. com.heiler.ppm.product2g.export.core.internal.dataprovider.ProductListDataProvider and overwrite the getSubDataTypes() method.
public class MyProductListDataProvider extends ProductListDataProvider{ private final static String IDENTIFIER = "MyProductListDataProvider.new"; //$NON-NLS-1$ // ... @Override public List getSubDataTypes() { List subDataTypes = super.getSubDataTypes(); // add my own sub-data type DataTypeUtils.addSubDatatype( subDataTypes, DataTypeItemsLinkedToProducts.IDENTIFIER ); return subDataTypes ; } // ...}Consider the utility methods:
com.heiler.ppm.texttemplate.core.dataprovider.util.DataTypeUtils.addSubDatatype( List subDataTypesList, String newDataTypeIdentifier );Contribute the new data provider at the com.heiler.ppm.texttemplate.core.dataProviders extension point.
<extension point="com.heiler.ppm.texttemplate.core.dataProviders"> <dataProviders class="com.heiler.ppm.custom.product2g.export.core.dataprovider.MyProductListDataProvider" name="ProductList with linked items"> </dataProviders> </extension>Extending the data provider to can query data for the new data type
Now you have to implement the algorithm for list model retrieving.
Call your own query method
Ensure your own query method will be called:
public class MyProductListDataProvider extends ProductListDataProvider{ // ... @Override public ListModel queryData( DataType dataType, Pair[] fields, ExecutionContext executionContext, int maxCount ) throws CoreException { if ( dataType.equals( DataTypeItemsLinkedToProducts.IDENTIFIER ) ) { return queryLinkedItems( dataType, fields, executionContext ); } return super.queryData( dataType, fields, executionContext, maxCount ); } // ...}The idea
The idea is to get a list model for each entity, and then merge the list models into one resulting list model. It's important that the resulting list model contains all requested fields and a column with the product ids. That id column is used to map the entries of the sub-list model to the entries of the main list model containing products.
The challenge here is to get list models for two further root entities. That's why you cannot use the query and the report result of the product list data provider directly.
The general pattern
Here's the pattern for the queryLinkedItems() implementation:
Get a report of LinkEntity objects that are containing the products to be exported
Use the search framework (or some other mechanism you've implemented already)
Get the search result as ReportResult
Get the LinkEntity list model
Use super.queryData( dataType, fields, executionContext, maxCount );
Switch off list model sorting, storing, mandatory fields checks
Get a report of Article objects that are mapped to the products via the LinkEntity entity
Loop through the LinkEntity list model and collect the ArticleId respectively ArticleProxy values
Get the Article list model
Use super.queryData( dataType, fields, executionContext, maxCount );
Switch off list model sorting, storing, mandatory fields checks
Merge LinkEntity list model and Article list model
Use ListModelMerger
prepare the list models as described in the corresponding merge method: sort by id column, set row identifying column, ...
Do list model sorting, validation, mandatory fields checks, storing
call postPrepareListModel( result, dataType, fields, executionContext );
You'll need helper
Here are some useful helper methods and patterns:
Splitting fields and logical key filter values
You have to ensure the list of requested fields matches the respective entity, that's why you need to split the list of fields and logical key filter values.
com.heiler.ppm.texttemplate.core.datatype.CompositeDataType.splitFields( Pair[] fields );com.heiler.ppm.texttemplate.core.datatype.CompositeDataType.splitLogicalKeyValueFilterMap( Map< String, Object > logicalKeyValueFilterMap );protected ListModel queryLinkedItems( DataType dataType, Pair[] fields, ExecutionContext executionContext ) throws CoreException{ // split fields and additional info Map< String, Pair[] > splittedFields = ( ( CompositeDataType ) dataType ).splitFields( localFields ); Map< String, Map< String, Object > > splittedAdditionalInfo = ( ( CompositeDataType ) dataType ).splitLogicalKeyValueFilterMap( executionContext.getAdditionalInfoMap() ); // query the LinkEntity list model ... // ... prepare fields and logical key filter values Entity linkEntity = getLinkEntity(); DataType linkEntityDataType = DataTypeUtils.getSubDatatypePart( linkEntity.getIdentifier ); Pair[] linkEntityFields = splittedFields.get( linkEntity.getIdentifier); Map< String, Object > linkEntityAddInfo = splittedAdditionalInfo.get( linkEntity.getIdentifier); // ... load the list model // ... // query the Article list model... // ... prepare fields and logical key filter values Entity articleEntity = getArticleEntity(); DataType articleEntityDataType = DataTypeUtils.getSubDatatypePart( articleEntity.getIdentifier ); Pair[] articleEntityFields = splittedFields.get( articleEntity.getIdentifier); Map< String, Object > articleEntityAddInfo = splittedAdditionalInfo.get( articleEntity.getIdentifier); // ... load the list model // ... // ...}Add more fields to the fields list
Often, you need to request some more list model columns; or you have to ensure certain columns are in the field's list.
com.heiler.ppm.texttemplate.core.dataprovider.util.DataProviderListModelUtils.checkAndAddField( Pair[] fields, String fieldIdentifier );com.heiler.ppm.texttemplate.core.dataprovider.util.DataProviderListModelUtils.checkAndAddField( Pair[] fields, Field newField );protected ListModel queryLinkedItems( DataType dataType, Pair[] fields, ExecutionContext executionContext ) throws CoreException{ // ... // query the LinkEntity list model ... // ... prepare fields and logical key value filter valuers // ... // ... product id and item id is needed Field productIdField = getRepositoryField( "LinkEntity.Product" ); linkEntityFields = DataProviderListModelUtils.checkAndAddField( linkEntityFields, productIdField ); Field itemIdField = getRepositoryField( "LinkEntity.Item" ); linkEntityFields = DataProviderListModelUtils.checkAndAddField( linkEntityFields, itemIdField ); // ...}Query a list model for a different root entity
The queryDataInternal( ) method uses the query member of the data provider. You need to prepare your data provider instance before calling this method. Afterwards, you should reset that query member.
com.heiler.ppm.texttemplate.core.dataprovider.util.DataProviderContextUtils.buildLocalExecutionContext( ExecutionContext executionContext, boolean storeSublistModel, boolean checkMandatoryFields, Map< String, Object > additionalInfo )protected ListModel queryLinkedItems( DataType dataType, Pair[] fields, ExecutionContext executionContext ) throws CoreException{ // ... // get the LinkEntity report result ReportResult linkEntityObjects = getLinkObjects(); // query the LinkEntity list model using the LinkEntity report result ReportListModelQueryBaseImpl originalQuery = this.query; AnyListByReportResult tmpQuery = new AnyListByReportResult( linkEntity.getEntityType().getIdentifier(), linkEntityObjects ); this.query = tmpQuery; // do not sort, do not store the list model ExecutionContext tmpContext = DataProviderContextUtils.buildLocalExecutionContext( executionContext, false, false, linkEntityAddInfo ); ListModel linkListModel = super.queryDataInternal( linkEntityDataType, linkEntityFields, tmpContext, -1 ); // restore original query this.query = originalQuery; // query the Article list model // ... // merge both list models // ... return result;}Check the list model's columns, merge list models
If you want to merge list models you have to ensure having that columns in your list model.
Furthermore, the list models have to be sorted by the columns used as merge criteria.
com.heiler.ppm.texttemplate.core.dataprovider.util.DataProviderListModelUtils.getColumnIndexByFieldId( ListModel listModel, String fieldId );com.heiler.ppm.texttemplate.core.dataprovider.util.DataProviderListModelUtils.sortListModel( ListModel listModel, int col1Index, int col2Index );com.heiler.ppm.texttemplate.core.dataprovider.util.DataProviderListModelUtils.setListModelRowIdentifyingColumn( ListModel listModel, int columnIndex );com.heiler.ppm.std.core.list.merger.ListModelMerger.merge*;protected ListModel queryLinkedItems( DataType dataType, Pair[] fields, ExecutionContext executionContext ) throws CoreException{ // ... // query the LinkEntity list model ListModel linkListModel = ...; // query the Article list model ListModel itemListModel = ...; // merge both list models by the item id column int linkItemIdColumnIndex = DataProviderListModelUtils.getColumnIndexByFieldId( linkListModel, itemIdField ); // list models of root entities always contain a column for the identifying field type int itemIdColumnIndex = DataProviderListModelUtils.getColumnIndexByFieldTypeId( itemListModel, "ArticleType.Id" ) DataProviderListModelUtils.sortListModel( linkListModel, linkItemIdColumnIndex, -1 ); DataProviderListModelUtils.sortListModel( itemListModel, itemIdColumnIndex , -1 ); ListModelMerger listModelMerger = new ListModelMerger(); listModelMerger.mergeNxM( linkListModel, itemListModel, linkItemIdColumnIndex, itemIdColumnIndex ); // ...}Store the resulting sub-list model
If a sub-list model has to be stored, it's grouped by the "master ids". That's why each sub-list model needs a column that can be used for this. Usually, the identifier column has the identifying field path of the data provider's root entity. If this is not so, you need to take care of it yourself.
protected ListModel queryLinkedItems( DataType dataType, Pair[] fields, ExecutionContext executionContext ) throws CoreException{ ListModel result = null; // query the LinkEntity list model // ... // query the Article list model // ... // merge both list models // ... // product proxy is identifying field to get sub-entries from this list model this.identifyingFieldIdentifier = productIdField.getInitialIdentifier(); // sort, validate, store the listmodel now postPrepareListModel( result, dataType, fields, executionContext ); return result;}