Giter VIP home page Giter VIP logo

cuba-component-data-import's Introduction

Build Status Download license

CUBA Platform Application Component - Data Import

This application component let's you easily import data into your application from various sources.

Table of Contents

Installation

  1. data-import is available in the CUBA marketplace
  2. Select a version of the add-on which is compatible with the platform version used in your project:
Platform Version Add-on Version
7.2.x 0.12.x - 0.14.x
7.1.x 0.10.x - 0.11.x
7.0.x 0.8.x - 0.9.x
6.10.x 0.7.x
6.9.x 0.5.x - 0.6.x
6.8.x 0.1.x - 0.4.x

The latest version is: Download

Add custom application component to your project:

  • Artifact group: de.diedavids.cuba.dataimport
  • Artifact name: dataimport-global
  • Version: add-on version
dependencies {
  appComponent("de.diedavids.cuba.dataimport:dataimport-global:*addon-version*")
}

CHANGELOG

Information on changes that happen through the different versions of the application component can be found in the CHANGELOG. The Changelog also contains information about breaking changes and tips on how to resolve them.

Supported DBMS

The following databases are supported by this application component:

  • HSQLDB
  • PostgreSQL
  • MySQL

All other DBMS systems are also possible to work with by the fact that CUBA studio generates the corresponding init / update scripts within the application.

Using the application component

The data-import application component helps you import data into your application from different files.

Currently the following file-types are supported:

  • Excel .xlsx
  • comma separated values .csv
  • JSON .json
  • XML .xml

In order to configure various import options, there is a UI based configuration possibility to define

  • which entity will be imported
  • which columns maps to which entity attribute
  • format configuration for dates, boolean values etc.
  • unique configurations and how to deal with these situations
  • custom groovy scripts for attributes to value mapping

There are two modes of using the data-import application component. The first one is an interactive UI wizard, which will guide the user directly through the process of importing the data.

The second mode is, that the import configuration can be pre-defined by a developer / administrator of the system. The end-user of the system can reuse this configurations and just uploads the file that should get imported.

Example usage

To see this application component in action, check out this example: cuba-example-using-data-import.

Import Wizard

The import wizard allows the user to interactively go through the import process and configure the above mentioned settings for the import execution. It can be found in the main menu: Administration > Data Import > Import Wizard

Step 1: Upload File

import-wizard-step-1

Step 2: Configure Entity Mapping

import-wizard-step-2

The second step in the wizard allows the user to configure which columns of the import file will be mapped to which entity attributes. The system makes suggestions based on the similarities of the entity attribute names and the column headers in the import file, but this can be adjusted by the user if needed.

Step 3: Import Configuration

In the import configuration it is possible to define certain Format options as well as the unique configurations for this import. import-wizard-step-3

Step 4: Import Preview

import-wizard-step-4

The last step will preview the data that was received from the import file. With "Start Import" the import process will be triggered. Afterwards the user will see a summary of how many entities were imported correctly.

Step 5: Import Summary

import-wizard-step-5

DataImportAPI

In the core module, there is an API available for programmatic interacting with the data import facilities.

DataImportAPI takes a FileDescriptor together with a ImportConfiguration and imports the content of the file according to the given configuration.

An example usage can be found in the MlbTeamImportService in the example application.

Integrate Import Wizard into screens

Instead of using the full import wizard, it is also possible to integrate the import process directly into screens. This use case is for the second usage mode of this application component (as mentioned above). In this case, there is already an import configuration defined for a particular entity. With that, you as the developer want the user directly to start the import process from the browse screen of your entities.

@WithImport Annotation for browse screens (legacy Screens of CUBA 6 AbstractLookup)

NOTE: The @WithImport annotation should only be used for CUBA 6 based legacy screens that extend AbstractLookup. For CUBA 7 based Screens extenting StandardLookup<T> should use the interface based approch implement WithImportWizard.

To start import from your entity browse screen, you have to add the following annotation to your browse screen controller:

@WithImport(listComponent = "customersTable")
public class CustomerBrowse extends AnnotatableAbstractLookup {
}

For the @WithImport annotation you need to define the list component on which it should add the attachments button. Normally this is the id of the table you defined in your browse screen.

This annotation will create a button in the buttonsPanel of the table and add the Import button after the default CUBA buttons.

The @WithImport annotations can be customized through the following attributes:

  • String listComponent - the id of the list component / table where the button will be added - REQUIRED
  • String buttonId - the id of the newly created button that will be created ("importBtn" by default)
  • String buttonsPanel - the id of the buttons panel where the new button will be added ("buttonsPanel" by default)

When the import button is clicked on the CustomerBrowse, it will check if there are import configuration available for this Entity. In case there are multiple configurations available for this entity, the user has to select a particular import configuration to proceed.

WithImportWizard interface for browse screens (CUBA 7 StandardLookup<T>)

To start import from your entity browse screen, the screen controller has to implement the following interface:

public class CustomerBrowse extends StandardLookup<Customer> implements WithImportWizard {
}

The WithImportWizard interface is a replacement for the previous existing @WithImport annotation. It will create a button in the buttonsPanel of the table and add the Import button after the default CUBA buttons.

WithImportWizard requires to implement certain methods in order to configure the way the import wizard works:

public class CustomerBrowse extends StandardLookup<Customer> implements WithImportWizard {

    @Inject
    protected GroupTable<Customer> customerTable;

    @Inject
    protected CollectionContainer<Customer> customerDc;

    @Inject
    protected ButtonsPanel buttonsPanel;


    @Override
    public ListComponent getListComponent() {
        return customerTable;
    }

    @Override
    public CollectionContainer getCollectionContainer() {
        return customerDc;
    }

    @Override
    public ButtonsPanel getButtonsPanel() {
        return buttonsPanel;
    }

}

Furthermore it has the following optional methods to implement to configure the behavior of the import wizard further:

  • Map<String, Object> getDefaultValues() - defines default values for the entity that will be imported
  • String getButtonId - the button id of the destination button. Will picked up from existing XML or created with this identifier

When the import button is clicked on the CustomerBrowse, it will check if there are import configuration available for this Entity. In case there are multiple configurations available for this entity, the user has to select a particular import configuration to proceed.

Supported File Types

Multiple file types are supported by this application component. Information and requirements for certain file types will be described below.

Example files can be found in the example-data subdirectory.

Excel - .xlsx

For Excel files the first row has to be the column names. Unnamed columns are not supported currently.

Example Excel file:

Name Description
Users This will be the users
Managers The moderators

CSV - .csv

For CSV files the first row has to be the column names. Unnamed columns are not supported currently.

Example CSV file:

"Name","Description"
"Users", "This will be the users"
"Moderators", "The Moderators"

JSON - .json

For JSON files it is required to be a JSON array, where each entry in this array is itself a JSON object, which should get imported as an entity instance.

Example JSON file:

[
  {
    "Name": "Users",
    "Description": "The users of the system"
  },
  {
    "Name": "Moderators",
    "Description": "The mods of the system"
  }
]
Programmatic Access to Nested JSON Structures

It is also possible to have nested structures in the JSON and bind it to a entity attribute. In order to do this, a Custom attribute binding script has to be configured for the desired entity attribute.

An example JSON file for this would be:

[
  {
    "Name": "Mark",
    "Lastname": "Andersson",
    "Address": {
        "street": "Dorfkrug 1",
        "postcode": "51665",
        "city": "Bad Neuendorf"
    },
    "orders": [
        {
            "orderId": 1
        },
        {
            "orderId": 2
        }
    ]
  }
]

In the custom binding script, access to the nested structure can be achieved like this:

return rawValue.Address.street

Or in case of the orders Array it would be:

return rawValue.orders[0].orderId

XML - .xml

For XML files it is required to be a List of XML elements directly under the root XML element which should get imported as an entity instance.

Example XML file:

<roles>
    <role>
        <Name>Users</Name>
        <Description>The users of the system</Description>
    </role>
    <role>
        <Name>Moderators</Name>
        <Description>The mods of the system</Description>
    </role>
</roles>
Programmatic Access to Nested XML Structures

It is also possible to have nested structures in the XML and bind it to a entity attribute. In order to do this, a Custom attribute binding script has to be configured for the desired entity attribute.

An example XML file for this would be:

<root>
   <entry>
       <Name>Users</Name>
       <Description>The users of the system</Description>
       <permission>
           <code>ALLOW_EVERYTHING</code>
           <name>Allow everything</name>
       </permission>
   </entry>
   <entry>
       <Name>Moderators</Name>
       <Description>The mods of the system</Description>
       <permission>
           <code>DENY_ALL</code>
           <name>Nothing is allowed</name>
       </permission>
   </entry>
</root>

In the custom binding script, access to the nested structure can be achieved like this:

return rawValue.permission.code

Import Configuration

The basis for the import wizard is the Import Configuration. It is also available via Administration > Data Import > Import Configuration. The Import Configuration contains all configuration options that are available for a single import process.

Generally the configurations can be saved for later reuse. This is possible within the Import wizard. Alternatively the user can create an import configuration beforehand via the corresponding list.

The base information that are required for an Import configuration are name, entity type as well as an import file where the attributes can be parsed from.

Entity Attribute mapping

An entity attribute mapping defines which column / attribute in the import file should be mapped to a particular attribute of the destination entity.

An attribute mapping contains the following information:

  • column name in the import file
  • column number (only relevant for CSV / Excel)
  • entity attribute
  • column required flag

When column required flag is set to true, addon validates that the column name exists in the imported file. It doesn't check their content, so you still need to validate that additionally.

Auto Detection of Entity Attribute Mappings

When creating an import configuration (directly or via the import wizard), the application component will try to parse the import file and depending on the column names / attribute names, it will try to suggest the most appropriate entity attribute that is available. Since this auto-detection feature has limitations, is it suggested to before executing the import validate that the suggested entity attributes for the mappings are correct.

Custom Attribute Binding Script

Additionally it is possible to configure a custom binding script, that let's the user implement certain parsing logic / default values in case this is not handled by the default binding behavior.

The return value of this script will be set to the corresponding Entity attribute.

If this script is set, it will disable the auto-detection of the import process.

The return value of the script has to be of the correct value and of the correct type that is defined in the corresponding entity attribute

Within the custom binding script, the following variables are injected and available for usage:

  • rawValue: the raw value that should be imported from the import file
  • dataManager: a reference to the DataManager from CUBA
  • dataRow: the complete data row as it is taken from the import file
  • entityAttribute: the current entity attribute
  • importConfiguration: the current import configuration
  • importAttributeMapper: the current import attribute mapper entity

Unique Configuration

Unique configurations in the Import Configuration allow the user to define certain business unique scenarios. Sometimes during the import process it is necessary to define what happens if an entity instance with particular attribute values is already in the database.

Possible results in case of a unique-violation might be to skip this entity instance or to update the existing entity instance that was found in the database.

Within an import configuration it is possible to define multiple unique configurations. for each unique configuration it is possible to define multiple entity attributes which should be taken into consideration.

for every data row the import process will check all unique configurations. if any of those configurations find an entity that match the criteria of the data row, the corresponding unique configuration policy (UniquePolicy) will be executed:

  • Skip if exists - the data row will not get imported and skipped
  • Update existing entity - the values of the data row will update the found entity instance
  • Abort import - the import process will be aborted immediately. Depending on the transaction strategy either the entities up until this point will be written (transaction per entity), or no entity at all (single transaction).

Unique Configuration example

The MlbTeam entity has the following data in the database

Code Name State
BAL "Baltimore Orioles" MD
Example 1: one attribute, SKIP behavior

Now we want to import the following CSV file:

"Code","Name","State"
BAL,"Baltimore New Team",MD

When there is the following unique configuration:

  • unique configuration attributes: code
  • unique policy: Skip if exists

The result in the database:

Code Name State
BAL "Baltimore Orioles" MD
Example 2: two attributes, UPDATE behavior
"Code","Name","State"
BAL,"Baltimore New Team",MD
BAL,"Baltimore Orioles",CA

When there is the following unique configuration:

  • unique configuration attributes: code, name
  • unique policy: Update existing entity

The result in the database:

Code Name State
BAL "Baltimore Orioles" CA

Transaction strategy

The transaction strategy is an option that can be configured within the import configuration. it defines how the system should behave in case one of the entries cannot be stored.

the following options are available:

Single Transaction

All entities will be imported in one transaction. if an error occurs in any of the entities, no entity will be imported

Transaction per Entity

Every entity will be imported in an own transaction. if an error occurs in any of the entities, all other entities will be imported

Pre-Commit Script

The Pre-Commit script is a groovy script, which can be defined in the ImportConfiguration. It will be executed directly before the already bound entity instance will get imported.

Within this script, the entity can be adjusted, values can be re-written, default values can be set etc.

The following variables are injected and available for usage:

  • entity: the already bound, but not persisted entity instance
  • dataRow: the complete data row as it is taken from the import file
  • dataManager: a reference to the DataManager from CUBA
  • importConfiguration: the current import configuration

Veto Right of Pre-commit Script

It is also possible to prevent the import for this entity instance.

To do this, the script has to return a boolean value, which represents if the entity should get imported or not.

  • true will import the entity instance
  • false will not import the entity instance

Example of veto right script:

if (entity.name.startsWith("B") {
    entity.name = entity.name + " - with a B"
    return true
}
else {
    return false
}

NOTE: If there is no explicit return value in the script, groovy will return the return value of the last expression. That might not lead to the expected result. Be aware of that.

Example of an implicit (possibly wrong) veto right script:

entity.name = entity.name + " " + entity.code   

This example will return null, because the MlbPlayer.setName() returns a void return value, which will be evaluated as false in groovy. Therefore this entity will not be imported.

NOTE: always use explicit return statements in the pre-commit script

Import Execution Logging

When executing the data import the results of the operation will be logged as Import execution. Those import executions can be found in the Menu: Administration > Data Import > Import Executions. The Import Execution contains information about the import process for a given file. For each failing import row, it contains detailed information the following information:

  • Category
  • Level
  • failing data row
  • the entity instance after all attributes are bound
  • error message
  • stacktrace

Default Binding Behavior

During the import process the values of the import file have to be bound to the entity attributes. By default the following attribute types are supported in the default binding:

Datatype Binding

  • String
  • Integer
  • Double
  • BigDecimal
  • Boolean
  • Date (java.util.Date)

Boolean

For boolean values it is possible to configure within the Import Configuration which values in the import file represent the true value and which represent the false value. So it is e.g. possible to configure "Yes" / "No" as the values which will be treated as true / false while binding the value.

Date

For Date values, the format can be configured within the Import Configuration as well. It uses the SimpleDateFormat formats for parsing. Examples:

  • dd.MM.yyyy HH:mm:ss z would able to parse values like 07.04.2001 12:08:56 PDT
  • yy/dd/MM would able to parse values like 13/04/07

Enum binding

Enum binding is supported automatically. In order to bind a value from the import file to an Enum value, the value has to match the value of an Enum as it is defined. Example:

public enum CustomerPriority {
    LOW,
    MEDIUM,
    HIGH;
}

The following binding values would lead to the result:

Value from Import file binding result
"HIGH" CustomerPriority.HIGH
"high" CustomerPriority.HIGH
"High" CustomerPriority.HIGH
"" null
"VERY_HIGH" null

Entity Association Binding

A very important case is to import values from entity references. Entity associations are supported to some degree. For all not supported cases, the custom attribute binding script can be used.

N:1 Entity Association

Many-to-one associations are supported by the default binding. In order to use this behavior, it is required that the entity instance that should get referenced is already in the database.

In order to reference an entity in a N:1 fashion, the entity attribute in the "Entity attribute mapper" has to be set.

Example:

In this example the MlbPlayer entity has a reference to the MlbTeam entity.

@Entity(name = "ddcdi$MlbPlayer")
public class MlbPlayer extends StandardEntity {

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    protected MlbTeam team;

}
@Entity(name = "ddcdi$MlbTeam")
public class MlbTeam extends StandardEntity {

    @Column(name = "NAME", nullable = false)
    protected String name;

    @Column(name = "CODE", nullable = false)
    protected String code;

}

The import file (CSV) looks like this:

"Name","Team",
"Adam Donachie","ATH"
"Paul Bako","CEN"
"Ramon Hernandez","BAL"
"Kevin Millar","CEN"
"Chris Gomez","MLN"

In this case, the Team is referenced by the attribute code of the MlbTeam entity.

This means, that the Entity attribute mapper for this example would look like this:

  • Column name: Team
  • Entity attribute: team.code

N:N:1 entity associations

It is also possible to not only bind through one association, but rather through multiple associations.

In case MlbTeam would have a attribute state of type UsState with an attribute code it would also work. The corresponding entity attribute would be team.state.code.

The requirement for this to work is, that there has to be a unique match to identify the correct association.

The following examples would not work:

Let's assume we have the following import file row:

"Name","Team State",
"Adam Donachie","MD"

where "MD" is Maryland and we would like to assign "the" team that is based in Maryland. As you might have noticed, there is an obvious problem already in this sentence: "the team". There might be multiple teams in Maryland, correct (in fact currently there is only one team - the "Baltimore Orioles")?

This is where the uniqueness problems occur.

The following examples will lead to a non-unique result and therefore will not work:

  • there are multiple entity instances that have this values (two UsState entity instances that have the code "MD")
  • there are multiple entity instances that reference an entity which has this value (two MlbTeam entity instances that have a reference to the UsStateentity instance "Maryland")

In case such a situation occurs, the corresponding data row with all non-unique results are logged. Nothing will be bound in this case.

1:N / M:N Entity Association

Currently binding of 1:N / M:N entity associations are not supported automatically. Instead the custom attribute binding script can be used for this purpose.

An example use case can be found in the example project cuba-example-using-data-import.

There the following example shows the behavior of M:N association binding: MlbPlayer -- M:N --> BaseballStrength.

In particular there are the following example configuration within the directory example-data/mlb/mlb_players-with-strengths:

Note: The Baseball Strengths master data file has to be imported first.

Dynamic Attribute Binding

Dynamic attributes are supported as a binding target. Currently the following dynamic attribute datatypes are supported:

  • String
  • Integer
  • Double
  • Boolean
  • Date (java.util.Date)
  • Enumeration

NOTE: Entity references within dynamic attributes are not supported currently.

In order to configure a dynamic attribute the Entity attribute mapper has to be configured with a plus sign as a prefix of the dynamic attribute name:

Let's assume the Entity MlbTeam as a dynamic attribute category Stadium Information. Within this category, there is one dynamic attribute defined with the name stadiumName. In this case the Entity attribute in the Entity attribute mapper would be: +stadiumName

Import Limitations

Integrations between systems is oftentimes highly dependent on the system / process to integrate with. Oftentimes the source and destination data sources oftentimes differ to a high degree.

This application component solves some of the problems that arise during this transformation from the source to the target data source either automatically or via configuration mappings. However, there are a lot of cases, where this kind of configuration is not enough.

Due to this, there is the possibility to create custom scripts like the preCommitScript which enables further customizations.

However, sometimes the mapping exceeds this limits either because of particular limitations of the configuration or because of the scripts are not able to handle every use case.

A few examples of those limitations for the data-import application component are:

  • dealing with composite keys
  • automatic handling of M:N associations
  • interacting with highly complex excel sheets that are far away from a BCNF database schema

Custom File Parsing Logic

In case custom parsing behavior of the original file is needed, that cannot be configured via the import configuration UI, it is oftentimes still possible to do it programmatically.

There is an example project: cuba-example-data-import-custom-parsing-logic that shows how to switch the separator character in the CSV import case to ;. More information can be found in the corresponding README.

Entity Staging Area

In those situations you should try to follow the following general advice:

Instead of rely on the data-import application component to do all the heavy lifting, take the data-import application component only as a first step in your data integration step.

Consider the following complex excel sheet:

excel-sheet-limitations

This oftentimes is a common pattern for the usage of an excel sheet. Furthermore it is quite hard to automatically convert, since there are so many violations to a normalized data model etc.

Imagine there is the following destination data model:

public class Customer extends StandardEntity {
  private String name;
  private String customerId;
  private List<Order> orders;
  private CustomerType customerType;
}

public class Order extends StandardEntity {
  private LocalDate orderDate;
  private Customer customer;
  private BigDecimal totalAmount;
}

public enum CustomerType {
  REGULAR,
  PREMIUM;
}

Transforming the original excel sheet into the destination data model is perhaps possible, but not the most straight forward thing to do.

Instead create a staging area as an entity that mirrors exactly the structure of the source excel sheet:

public class CustomerOrderRow extends StandardEntity {
  private String customerName;

  private BigDecimal salesNorth;
  private String salesNorthNotes;
  private BigDecimal salesWest;
  private String salesWestNotes;

  // ...

  private String orderIdsInformation;
  private String customerContactTelefonnumber;
  private String customerTypeChanged;
}

This way you can still leverage the data-import component without hitting its limits. On the other hand, the logic to transform the source data model to the destination data model can be expressed as regular Java / groovy code as part of the application.

But since you have now a persistent entity acting as a stage area, you can apply the following additional functionality:

  • allow users to do data clean up directly in the staging area
  • use entity listeners & services
  • test transformations with unit tests

This way, you can leverage the app component for still doing the file import. The logic to transform you just treat as a regular part of the application.

cuba-component-data-import's People

Contributors

alex-haulmont avatar genthalili avatar glundy avatar mariodavid avatar morumbi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cuba-component-data-import's Issues

Import of inherited entities

Hi,
There seems to be a bug on import of inherited entities. The importer picks the class for the parent entity instead of the entity i am trying to import, when i change the mapping to be the inherited, i get 0 imports.
Is it possible to cast the entity to the type on the dropdown?

Idea of UI for importing

I imagine that something it is working in Excel could be some potential end goal:

Excel Import example

We could try to come up with UI sketches that will guide us on where the feature-set as well as the implementation should go.

MySQL support

the app component should support MySQL out of the box.

TODO:

  • generate MySQL script through Studio

Issue importing field/column using BigDecimal format

Hello Mario,

Neat tool - thanks for your contribution.

I'm unable to import a BigDecimal-formatted field. Please take a look if you have time, I have some info below that could help you fix this:

It appears BigDecimal format is not handled in the getValue method for the DatatypeAttributeBinder.groovy file (dataimport-core-0.2.0.jar/de.diedavids.cuba.dataimport/binding/DataTypeAttributeBinder.groovy):

private getValue(AttributeBindRequest bindRequest) {
        switch (bindRequest.metaProperty.javaType) {
            case Integer: return getIntegerValue(bindRequest.rawValue, bindRequest.dataRow)
            case Double: return getDoubleValue(bindRequest.rawValue, bindRequest.dataRow)
            case Date: return getDateValue(bindRequest.importConfiguration, bindRequest.rawValue)
            case Boolean: return getBooleanValue(bindRequest.importConfiguration, bindRequest.rawValue, bindRequest.dataRow)
            case String: return getStringValue(bindRequest.rawValue)
        }
    }

Select existing import configuration in Import wizard

As a user of the import system
When I open the import wizard and uploaded a file
Then I want to be able to select an existing import configuration after selecting a entity class
So that I don't need to define the different configurations every time I want to import the same kind of file

Define attribute path without Entity name

In the wizard when changing the entity attribute in the mapping there has to be the prefix of the Entity class (e.g. ddcdit$MlbTeam.code instead of just code). The latter would be better --> refactoring

Abstraction for CSV import

Besides the already existing excel import we should try to come up with a similar thing that can import CSV files. It should in the first phase be at the same abstraction level as the excel import.

Then we can create abstractions in form of configurations which allow to more easily define the import.

  • choose CSV library
  • build abstract superclass for CSVImports
  • build a concrete CustomerCSVImporter as an example

configure attribute(s) unique configurations in wizard

As a user of the wizard
When I try to import data via the wizard
And I configure a unique configuration for the column "customer.firstName"
And I configure the strategy "skip if found"
Then this item should not be imported
As a user of the wizard
When I try to import data via the wizard
And I configure a unique configuration for the column "customer.firstName"
And I configure the strategy "update if found"
Then this item should be imported and update the attributes of the existing entity instance

release 0.1.0

  • create bintray component
  • deploy to bintray
  • create release notes

PostgreSQL support

the app component should support PostgreSQL out of the box.

TODO:

  • generate PostgreSQL script through Studio

N:1 association binding throws error in case there is no result in the query

Currently when an N:1 association is configured in the import mapper and the imported is started with a data row that has a reference to a non-existing enity reference, then an exception is thrown:

NoSuchElementException: Cannot access first() element from an empty List

instead it should just not get bound.

Support for Dynamic Attributes

Mario,

Thank you for providing a valuable component to the community. I'm currently not able to set a category and dynamic attribute on an imported entity. Is this not supported and if not, do you plan to?

Thanks!

create README

topics:

  • using wizard
  • describing options of import configuration
  • format supported

Descope export

Lets try to make clear, that it is not about data export. CUBA already has capabilities for data export: Excel export, reports, Entity Inspector export.

We should get a clear idea about what this is and what it is not. From my point of view: it is only data import from files.

make entity attribute selectable via tree selection

As a user of the wizard
When I configure the entity attribute mapper configuration for "Customer.firstName"
Then I want to select the entity attribute via a tree selection just like in the "generic filter > add filter condition dialog"
As a user of the wizard
When I configure the entity attribute mapper configuration for "MlbPlayer.team.code"
Then I want to select the entity attribute via a tree selection just like in the "generic filter > add filter condition dialog"

support back option in import wizard

As a user of the import wizard
When I have made a mistake in a previous step
Then I would like to have the ability to go one step back in the wizard so that I can correct my mistake

Start import process from any browse screen

As a user of the import system
When I open a browse screen of an entity which is importable
Then I want to be able to trigger an import and only configure the import configuration as well as the upload file as the only parameter

Thinking about the API names

  • ImporterAPI
  • DataImportService

we should try to scope it to something that it definivitly is and descope something that it is not. Import is a very broad term. Webservice import, only File import, only CSV file import etc.?

Support for JSON files

As a user of the wizard
When I start the Import wizard
Then I want to import a JSON file and map the attributes of the file to the entity attributes as with CSV files

Support for XML files

As a user of the wizard
When I start the Import wizard
Then I want to import a XML file and map the attributes of the file to the entity attributes as with CSV files

Support default values for Strings in Entity mapping

As a User of the wizard
When I configure the entity attribute mapping for a String attribute in the import configuration
Then I want to configure a default value in case there is no value defined in the import file

exclude columns from import in wizard

It should be possible to exclude columns from the import process. One candidate in the example would be the position column, that is available in the CSV file, but not included in the domain model.

different import strategies: single-transaction, transaction-per-entity

There are cases where either a single-transaction mode makes sense. All Entities are processed by a single transaction. If any of the entities have validation errors or unique constraints, none of the entities should be processes.
But there is also the case, where the user only wants to see which rows were not processed because of the failure, but for all other entities, they should have imported (transaction-per-entity).

The ImportConfiguration should allow to define, for which configuration which import strategy is used.

No valid Attribute binder for AttributeBindRequest

Testing the component by importing records that are related to another table generates the following error
java.lang.IllegalStateException: No valid Attribute binder for AttributeBindRequest: de.diedavids.cuba.dataimport.binding.AttributeBindRequest@3cc02566 found at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:83) at org.codehaus.groovy.reflection.CachedConstructor.doConstructorInvoke(CachedConstructor.java:77) at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrap.callConstructor(ConstructorSite.java:84) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:247) at de.diedavids.cuba.dataimport.binding.AttributeBinderFactory.createAttributeBinderFromBindingRequest(AttributeBinderFactory.groovy:40) at de.diedavids.cuba.dataimport.binding.AttributeBinderFactory$createAttributeBinderFromBindingRequest.call(Unknown Source) at de.diedavids.cuba.dataimport.binding.EntityBinderImpl.bindAttribute(EntityBinderImpl.groovy:37) at sun.reflect.GeneratedMethodAccessor259.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:384) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022) at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:69) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:190) at de.diedavids.cuba.dataimport.binding.EntityBinderImpl$_bindAttributes_closure1.doCall(EntityBinderImpl.groovy:28) at sun.reflect.GeneratedMethodAccessor258.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022) at groovy.lang.Closure.call(Closure.java:414) at groovy.lang.Closure.call(Closure.java:430) at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2040) at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2025) at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2066) at org.codehaus.groovy.runtime.dgm$163.invoke(Unknown Source) at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:274) at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:56) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125) at de.diedavids.cuba.dataimport.binding.EntityBinderImpl.bindAttributes(EntityBinderImpl.groovy:27) at de.diedavids.cuba.dataimport.binding.EntityBinder$bindAttributes.call(Unknown Source) at de.diedavids.cuba.dataimport.service.GenericDataImporterServiceBean.bindAttributes(GenericDataImporterServiceBean.groovy:232) at de.diedavids.cuba.dataimport.service.GenericDataImporterServiceBean$bindAttributes$0.callCurrent(Unknown Source) at de.diedavids.cuba.dataimport.service.GenericDataImporterServiceBean.createEntityFromRow(GenericDataImporterServiceBean.groovy:226) at sun.reflect.GeneratedMethodAccessor276.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:384) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022) at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:69) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:174) at de.diedavids.cuba.dataimport.service.GenericDataImporterServiceBean$_createEntities_closure5.doCall(GenericDataImporterServiceBean.groovy:218) at sun.reflect.GeneratedMethodAccessor275.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022) at groovy.lang.Closure.call(Closure.java:414) at groovy.lang.Closure.call(Closure.java:430) at org.codehaus.groovy.runtime.DefaultGroovyMethods.collect(DefaultGroovyMethods.java:3202) at org.codehaus.groovy.runtime.DefaultGroovyMethods.collect(DefaultGroovyMethods.java:3172) at org.codehaus.groovy.runtime.dgm$67.invoke(Unknown Source) at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:274) at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:56) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125) at de.diedavids.cuba.dataimport.service.GenericDataImporterServiceBean.createEntities(GenericDataImporterServiceBean.groovy:217) at de.diedavids.cuba.dataimport.service.GenericDataImporterServiceBean$createEntities.callCurrent(Unknown Source) at de.diedavids.cuba.dataimport.service.GenericDataImporterServiceBean.doDataImport(GenericDataImporterServiceBean.groovy:49) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85) at com.haulmont.cuba.core.sys.ServiceInterceptor.aroundInvoke(ServiceInterceptor.java:117) at sun.reflect.GeneratedMethodAccessor147.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629) at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618) at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) at com.sun.proxy.$Proxy147.doDataImport(Unknown Source) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.haulmont.cuba.core.sys.remoting.LocalServiceInvokerImpl.invoke(LocalServiceInvokerImpl.java:94) at com.haulmont.cuba.web.sys.remoting.LocalServiceProxy$LocalServiceInvocationHandler.invoke(LocalServiceProxy.java:154) at com.sun.proxy.$Proxy298.doDataImport(Unknown Source) at de.diedavids.cuba.dataimport.service.GenericDataImporterService$doDataImport.call(Unknown Source) at de.diedavids.cuba.dataimport.web.importwizard.ImportWizard.startImport(ImportWizard.groovy:239) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.haulmont.cuba.gui.xml.DeclarativeAction.actionPerform(DeclarativeAction.java:92) at com.haulmont.cuba.web.gui.components.WebButton.performAction(WebButton.java:44) at com.haulmont.cuba.web.gui.components.WebButton.lambda$new$61446b05$1(WebButton.java:36) at sun.reflect.GeneratedMethodAccessor300.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.vaadin.event.ListenerMethod.receiveEvent(ListenerMethod.java:510) at com.vaadin.event.EventRouter.fireEvent(EventRouter.java:200) at com.vaadin.event.EventRouter.fireEvent(EventRouter.java:163) at com.vaadin.server.AbstractClientConnector.fireEvent(AbstractClientConnector.java:1037) at com.vaadin.ui.Button.fireClick(Button.java:377) at com.haulmont.cuba.web.toolkit.ui.CubaButton.fireClick(CubaButton.java:54) at com.vaadin.ui.Button$1.click(Button.java:54) at sun.reflect.GeneratedMethodAccessor299.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:158) at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:119) at com.vaadin.server.communication.ServerRpcHandler.handleInvocation(ServerRpcHandler.java:444) at com.vaadin.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:409) at com.vaadin.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:274) at com.vaadin.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:90) at com.vaadin.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:41) at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1435) at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:361) at com.haulmont.cuba.web.sys.CubaApplicationServlet.serviceAppRequest(CubaApplicationServlet.java:300) at com.haulmont.cuba.web.sys.CubaApplicationServlet.service(CubaApplicationServlet.java:191) at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:107) at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:73) at com.haulmont.cuba.web.sys.CubaHttpFilter.doFilter(CubaHttpFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at com.haulmont.cuba.web.sys.singleapp.SingleAppWebContextLoader$SetClassLoaderFilter.doFilter(SingleAppWebContextLoader.java:277) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650) at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:677) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1468) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748)
Entity
`@Entity(name = "mdsf$CtlGrupo")
public class CtlGrupo extends BaseStringIdEntity implements Updatable, Creatable {
private static final long serialVersionUID = -5560200675400158080L;

@Id
@Column(name = "ID", nullable = false, length = 3)
private String id;

@NotNull
@Column(name = "NOM_GRUPO", nullable = false, length = 200)
private String nombre;

@Lookup(type = LookupType.DROPDOWN)
@NotNull
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "CAPITULO_ID")
private CtlCapitulo capitulo;

@Composition
@OnDeleteInverse(DeletePolicy.CASCADE)
@OneToMany(mappedBy = "grupo")
private List<CtlCategoria> categorias;

@Column(name = "FH_CREACION")
private Date createTs;

@Column(name = "CREADO_POR", length = 50)
private String createdBy;

@Column(name = "FH_MODIFICADO")
private Date updateTs;

@Column(name = "MODIFICADO_POR", length = 50)
private String updatedBy;`

image

Nelson F.

support boolean type in import

it should be possible to parse a boolean value in the import process. The already existing global configuration options for booleanTrueValue and booleanFalseValue can be used.

What happens in case of: True --> "X", False --> ""? That should be supported as well (false as no value)

Custom preCommit Import script

As a development / development alike user of the system
When I create an import configuration
Then I want to configure a custom "preCommit" groovy script, which will be executed right before the the import is started (and bean validation is executed)
So that I can programmatically define certain behavior on a per entity basis

support N:1 associations by any attribute for import

MlbPlayer N:1 MlbTeam through the attribute team. Now MlbTeam has attributes name and code.

In the CSV file there is a reference to team and it contains the code of the MlbTeam entity.
The equivalent in Excel is vlookup :)

Here's the full example:

bildschirmfoto 2018-04-05 um 12 30 03

bildschirmfoto 2018-04-05 um 12 33 03

It should be possible to define in the wizard that the column team belongs to the entity attribute team.code and this leads to the correct import behavior as described in this example.

Oracle support

the app component should support Oracle out of the box.

TODO:

  • generate Oracle script through Studio

create groovy script per import attribute

In order to enable an escape mode for more complex situations, there should be a way for the user to define for every attribute how the mapping from the CSV / Excel value to the entity attribute should happen.

This will be an alternative to the already implemented "easy" cases, like N:1 association lookup, Enum parsing etc.

Different improvement ideas

Hi Mario,
Grate add-on! I remember proposed to cuba forum for a such module, and finally you did it, really great! ๐Ÿ‘

However, I'm getting an error when starting to upload the data :
NullPointerException: Cannot get property 'metaProperties' on null object

Do you have any idea why this property is null?

0.3.0 AttributeBindRequest not found

IllegalStateException: No valid Attribute binder for AttributeBindRequest: de.diedavids.cuba.dataimport.binding.AttributeBindRequest@1ffc1404 found
Since I upgraded to 0.3.0

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.