Customizing Import
Creating your own import functions
It is possible to contribute user defined functions using the extension point com.heiler.ppm.function.core.functions. The contributed functions can be used within mapping formulas in the same way as the build-in functions.
A function can have zero, one or more parameters and returns a single value. Parameters can be mandatory or optional, optional parameters have to be the last parameters. The parameters as well as the returned value are typed whereby any Java class can be used as type.
A function might throw an exception or log one or more messages into the import protocol (Logging into import protocol is available since HPM 6.0.) If the function throws an exception, a log entry containing the exception is log into the import protocol. The ID of the entity item as well as location information of the function arguments are added automatically to the log entry.
A function consists of two parts: The contribution to the extension point com.heiler.ppm.function.core.functions and a Java class performing the actual work.
The contribution
Each contribution contains the following information:
the name of the function (language independent)
a description (language dependent)
the parameters
the return type
the context in which the function should be active.
the executor class which performs the actual work. The executor has to extend the abstract class com.heiler.ppm.function.core.AbstractFunctionExecutor.
Each parameter entry contains the following information:
the id (language independent), used to access the parameter in the executor
the name (language independent), only used when printing the list of available functions in the formula viewer. Should be set to the same value as id to avoid confusion.
the position of the parameter
whether the parameter is mandatory or optional
the type
The extension point com.heiler.ppm.function.core.functions is used for several purposes within the HPM (for example, also for validation functions in the export module). Therefore, many available fields do not have to be set if the function is only used in the import module. Only the fields mentioned in the example are considered by the import module.
The parameter types and the return type has to be specified in the field value-class / fix-class and not in the field class.
The executor
The executor is a class extending extending the abstract class com.heiler.ppm.function.core.AbstractFunctionExecutor. The executor gets a arguments an execution context and the the parameters.
The context can be used to write an entry into the import protocol (using the method logStatus, available since HPM 6 ). Note that the method isLogEnabled is only relevant for the export module and returns always false when used within the context of the import module.
The parameters can be retrieved using the id specified in the parameter definition. The return value is either
an object of the specified class
null if the referred cell in the import file is empty (Excel, CSV) or the XML tag resp. attribute does not exist
an object of the class com.heiler.ppm.mapper.core.functions.DeleteContentObject if the referred cell in the import file is empty and the option Empty cells delete field content is active is activated (Excel, CSV) or the the XML-tag does not contain any text.
If the value provided in the import file cannot be converted into the specified class, an error is written into the import protocol and the function is not called at all.
The class com.heiler.ppm.function.core.FunctionUtils contains some utility methods that are useful for function executor, like checking if the value of a parameter is not null null or is within a certain range.
Example: addDays
Assume that we need a function that adds to a specified date (argument 1) a number of specified days (argument 2). An example how the function might be used is:
addDays( [Date], 7)
whereby Date is a column in a CSV sheet containing dates. To show how logging works, each time the function is called, an entry is added to the import protocol.
The contribution for the extension point looks like:
<extension
point=
"com.heiler.ppm.function.core.functions"
>
<function
name=
"addDays"
description=
"Adds a number of days to the specified date"
>
<!-- Return type of the function -->
<value-
class
fix-
class
=
"java.sql.Date"
>
</value-
class
>
<!-- Executor that does the actual work (written in Java) -->
<executor
class
=
"AddDaysExecutor"
>
</executor>
<!-- First parameter with id and name
"date"
-->
<parameter
id=
"date"
name=
"date"
mandatory=
"true"
order=
"1"
>
<!-- Type of the first parameter -->
<value-
class
fix-
class
=
"java.sql.Date"
>
</value-
class
>
</parameter>
<!-- Second parameter with id and name
"days"
-->
<parameter
id=
"days"
name=
"days"
mandatory=
"true"
order=
"2"
>
<!-- Type of the second parameter -->
<value-
class
fix-
class
=
"java.lang.Integer"
>
</value-
class
>
</parameter>
<!-- This function should be available only in the
import
module -->
<context
context=
"IMPORT"
>
</context>
</function>
</extension>
An implementation of the executor for the function addDays might look like:
import
java.sql.Date;
import
com.heiler.ppm.function.core.AbstractFunctionExecutor;
import
com.heiler.ppm.function.core.ExecutionContext;
import
com.heiler.ppm.function.core.ExecutionParameter;
import
com.heiler.ppm.function.core.FunctionException;
import
com.heiler.ppm.function.core.FunctionUtils;
class
AddDays
extends
AbstractFunctionExecutor
{
@Override
public
Object doExecute( ExecutionContext context, ExecutionParameter parameters )
throws
FunctionException
{
Date date = ( Date ) parameters.getValue(
"date"
);
FunctionUtils.assertNotNull( date,
"date"
);
Integer days = ( Integer ) parameters.getValue(
"days"
);
FunctionUtils.assertNotNull( days,
"days"
);
return
new
Date( date.getTime() + days *
24
*
60
*
60
*
1000
);
}
}
Activate custom entities
The following steps are necessary to activate a repository entity for import:
In the repository, the property purpose or importPurpose has to be set to 1 for this entity and for each sub entity, each logical key and each field that should be importable. For historical reasons, logical keys allow only the property purpose is. Note that an entity or a field is importable if either purpose or importPurpose is set to 1.
A contribution to the extension point com.heiler.ppm.importer5.core.entityImportConfiguration has to be provided.
Optionally, a contribution to the extension point com.heiler.ppm.importer5.ui.importDataContribution can be provided.
Contributing to the extension point com.heiler.ppm.importer5.core.entitySpecificImportConfiguration
This extension point specifies:
a name (only used the debugging resp. documentation purposes)
the field that contains the external identifier of the item
a class that implements the interface EntitySpecificImportConfiguration. This class handles import specific operations like loading and storing entity items.
the position which the entity should be shown at in the import GUI. This field allows a float as value, therefore it is possible to insert the entity between two existing entities.
whether the entity supports multi-threaded import or not. For example, structure groups do not support multi-threaded import.
optionally a class extending the abstract class com.heiler.ppm.importer5.core.api.entityspecific.EntitySpecificImportData and stores additional information for one import like the catalog or the structure systems the entity items should be imported in.
optionally a priority. If a standard contribution should be overwritten, the priority has to be set to 1.
Example 1: New entity CustomEntity based on entity type EGDType
Assume a new entity named CustomEntity which bases on the entity type EGDType should be made importable.
The contribution to the extension point com.heiler.ppm.importer5.core.entitySpecificImportConfiguration would look like
<extension
point=
"com.heiler.ppm.importer5.core.entitySpecificImportConfiguration"
>
<entitySpecificImportConfiguration
name=
"CustomEntityImportConfiguration"
uniqueKey=
"/CustomEntity/CustomEntity.Identifier"
class
=
"com.heiler.ppm.article.dataimport.core.EGDImportConfiguration"
order=
"1.5"
/>
</extension>
In versions before 6.0.02.00, the class com.heiler.ppm.article.dataimport.core.EGDImportConfiguration cannot be used as it contains a hard coded reference to the field Product.Identifier. You have to make a copy of the class EGDImportConfiguration, specify this class in the extension and replace the field identifier.
Example 2: Change default behavior when importing items
Assume you want to perform an action after an item is saved by the import. This behavior can be achieved by replacing the default ArticleImportConfigurationby an own implementation CustomArticleImportConfiguration.
The contribution to the extension point com.heiler.ppm.importer5.core.entitySpecificImportConfiguration could look like
<extension
point=
"com.heiler.ppm.importer5.core.entitySpecificImportConfiguration"
>
<!-- priority and
<entitySpecificImportConfiguration
name=
"ArticleImportConfiguration"
class
=
"com.heiler.ppm.custom.core.CustomArticleImportConfiguration"
importData=
"com.heiler.ppm.article.dataimport.core.ArticleImportData"
order=
"1"
supportsMultithreadedImport=
"true"
uniqueKey=
"Article/Article.SupplierAID"
priority=
"1"
/>
</extension>
The customized class could look like:
package
com.heiler.ppm.custom.core;
import
org.eclipse.core.runtime.CoreException;
import
com.heiler.ppm.importer5.core.api.entityspecific.DetailModelWrapper;
import
com.heiler.ppm.std.core.entity.EntityProxy;
public
class
CustomArticleImportConfiguration
extends
ArticleImportConfiguration
{
public
CustomArticleImportConfiguration()
{
super
();
}
@Override
public
EntityProxy saveDetailModelWrapper( DetailModelWrapper detailModelWrapper )
throws
CoreException
{
EntityProxy entityProxy =
super
.saveDetailModelWrapper( detailModelWrapper );
// do some post processing
return
entityProxy;
}
}
Executing or scheduling an import programmatically
The central interface for executing or scheduling an import is com.heiler.ppm.importer5.core.api.Importer. An object of this interface can be retrieved by using the factory com.heiler.ppm.importer5.core.api.ImporterFactory.
An example for executing an import immediately using a import profile stored in a file:
Importer importer = ImporterFactory.getInstance()
.createImporter();
// Load the import profile from a file (Other methods exists to load the import profile from the server or a string)
ImportProfile profile = importer.loadImportProfile(
new
File(
"myImportProfile.him"
) );
// Set the import file which should be imported
profile.getDataSources()[
0
]
.setFile(
new
File(
"MyFileToBeImported.csv"
) );
// Perform the import. The method blocks until the import has been finished.
ImportResult result = importer.performImport( profile,
null
,
null
);
// Retrieve some information about the performed import
int
importedItemsWithErrors = result.getImportCounter( )
.getImported(ReturnMode.WITH_ERRORS);
// Clear the problem log
result.getProblemLog()
.clear(
new
NullProgressMonitor() );
An example for scheduling an import in an hour using an import profile stored in the database (method loadImportProfileFromDatabase is provided below).
Importer importer = ImporterFactory.getInstance()
.createImporter();
//Load the import profile from the database
ImportProfile profile = loadImportProfileFromDatabase(
"MyMapping"
);
//Set the import file
profile.getDataSources()[
0
].setFile(
new
File(
"MyFileToBeImported.csv"
) );
//Set execution date (in one hour)
Date date =
new
Date(
new
Date().getTime() +
60
*
60
*
1000
);
//Schedule the import. The method returns immediately after the import has been scheduled.
ImportJobToken jobToken = importer.scheduleImport( profile, date,
null
,
null
);
// Wait until the import has been finished
if
( jobToken.waitUntilFinished() )
{
//Retrieve some information about the import (in case the import has not been canceled)
ImportResult result = jobToken.getImportResult();
int
importedItemsWithErrors = result.getImportCounter()
.getImported( ReturnMode.WITH_ERRORS );
}
//Clear the problem log
jobToken.getProblemLog()
.clear(
new
NullProgressMonitor() );
An example for a method loading an import profile from the database. In case the import profile could not be found, a runtime exception is thrown.
private
ImportProfile loadImportProfileFromDatabase(
final
String mappingName )
throws
CoreException
{
Importer importer = ImporterFactory.getInstance()
.createImporter();
// Retrieve all import profiles with the given mapping name
List< ImportProfileProxy > profileProxies = importer.getAllImportProfiles(
new
Predicate< ImportProfileProxy >()
{
@Override
public
boolean
evaluate( ImportProfileProxy profileProxy )
{
return
mappingName.equals( profileProxy.getName() );
}
} );
// Throw a runtime exception if import profile could not be found
if
( profileProxies.size() ==
0
)
{
throw
new
RuntimeException(
"Mapping '"
+ mappingName +
"' not found"
);
}
// Returns the found import profile (import profile names are unique )
return
profileProxies.get(
0
)
.getImportProfile();
}