-
Notifications
You must be signed in to change notification settings - Fork 19
adding import support for a CDB domain
This page describes the process of adding import support for a CDB domain. These notes cover a couple of different examples to cover a simple case (CDB "Soures") and then one that's more complex (CDB "Machine Design"). The following steps are necessary to add import support for a CDB domain class.
Multiple import formats can be supported for a particular domain. For example, the ItemDomainCatalog domain provides one format for basic import of catalog items and another for creating assemblies of existing catalog items. The steps below describe the process for supporting a particular import format in a domain. Steps 1-4 can be repeated for alternate formats. Steps 5 and 6 only need to be performed once for a domain. The DomainImportInfo object returned by the domain controller method initializeDomainImportInfo(), described in step 5, must contain an entry for each supported format.
1. establish "official" import spreadsheet format
2. create import helper subclass
3. add domain setter methods for saving column values
4. add domain property with getter method for output validation table
5. enable import framework in domain controller
6. add an import view page for the domain
Each step is described in more detail below.
The first step is to establish the official import spreadsheet format for the particular domain. This would probably include columns for the attributes that uniquely identify instances of the domain (e.g., name, itemIdentifier1, itemIdentifier2 etc) plus any other significant attributes for describing instances of the domain.
For the "Source" domain (well, not really a domain, but), the format is simple. It includes a column for each of the four attributes of domain instances: name, description, contact info, and URL.
For the "Machine Design" domain, the format is more complex. The columns included in the import format are:
- Parent ID: (optional) The CDB id of the machine design item that contains the specified item (used to establish relationship between specified parent and new child item).
- Level 0-n: (required for non-template instantiation) A variable number of columns is used to specify the desired machine design hierarchy, each named "Level 0", "Level 1", etc. The name for the new item is specified in ONE of these columns. The nesting is used to establish parent-child relationships. E.g., for a given row, the value "Rack 1" is specified in the "Level 0" column. The subsequent row contains "Valve Controller 1" in the "Level 1" column. This will create an item named "Rack 1", with a child called "Valve Controller 1". Here is an example from the spreadsheet:
Level 0 Level 1 Level 2 PS-SR-S{nn}-CAB11 S{nn}A:S2:PS S{nn}A:S2:PS:DCCT1 S{nn}A:Q3:PS S{nn}A:Q3:PS:DCCT1This will create an item named "PS-SR-S{nn}-CAB11" that contains two children, "S{nn}A:S2:PS" and "S{nn}A:Q3:PS". The item "S{nn}A:S2:PS" contains child "S{nn}A:S2:PS:DCCT1", while item "S{nn}A:Q3:PS" includes child "S{nn}A:Q3:PS:DCCT1".
- Template Instantiation: (optional, not used when creating regular items using "Level" columns) Specifies that a machine design template should be instantiated to create new items using specified name and parameters. E.g., "S{nn}IDFE:XPM1 Assembly(nn=24, p=1, q=2, r=3, t=4)" specifies to instantiate the template named "S{nn}IDFE:XPM1 Assembly" with the parameters nn, p, q, r, and t.
- Machine Design Alternate Name: (optional) the alternate name for the new machine design item
- Machine Design Item Description: (optional) textual description for the new machine design item
- Assigned Catalog/Inventory Item: (optional) this is to document the name of the assigned item, but is only for documentation purposes, it is ignored during parsing.
- Assigned Catalog/Inventory Item: (optional) The CDB ID of the assigned catalog or inventory item (used to establish object reference).
- Location: (optional) The CDB ID of the location object that describes the location of the new item. The word "parent" is an allowed value used for documentation and is ignored in parsing.
- Project ID: (required) The CDB ID of the project object for the new item (e.g., "APS-U Production" is a project). Used to establish object reference.
- Is Template: (required) Specifies whether the new item is a template item (value = true) or regular item or template instantiation (value = false).
The CDB import framework includes the following components:
- ItemDomainImportWizard.java: The controller class for a generic import wizard view that supports all CDB domains.
- ImportHelperBase.java: Contains the core functionality for parsing the xlsx file and mapping it to the properties of a particular domain. A helper subclass is defined for each domain with the specifics to accomplish that mapping.
- item/private/importWizard/*.xhtml: Contains xhtml view elements for the generic wizard GUI.
The key step is to create a subclass of ImportHelperBase for the new domain you wish to add support for. I'll start with the required overrides for any subclass adequate for a simple case like the "Source" domain, and then try to cover some of the more "advanced" topics used for the "Machine Design" domain.
The class ImportHelperSource is a good example of the minimum required overrides for a helper subclass. Required methods include:
- getColumnSpecs(): returns a list of column specs that specify how to handle the columns of the input spreadsheet. This will be described in more detail below.
- getEntityController(): returns the controller instance for creating domain instances and performing transactions to add them
- getTemplateFilename(): returns the filename to use when downloading an empty spreadsheet template.
- createEntityInstance(): returns a new instance of the domain class.
- ignoreDuplicates(): tells framework to ignore duplicate values in the input spreadsheet (defaults to false).
public class ImportHelperSource extends ImportHelperBase<Source, SourceController> { protected static String completionUrlValue = "/views/source/list?faces-redirect=true"; @Override protected List<ColumnSpec> getColumnSpecs() { List<ColumnSpec> specs = new ArrayList<>(); specs.add(new StringColumnSpec(0, "Name", "name", "setName", true, "Name of vendor/manufacturer", 64)); specs.add(new StringColumnSpec(1, "Description", "description", "setDescription", false, "Description of vendor/manufacturer", 256)); specs.add(new StringColumnSpec(2, "Contact Info", "contactInfo", "setContactInfo", false, "Contact name and phone number etc", 64)); specs.add(new StringColumnSpec(3, "URL", "url", "setUrl", false, "URL for vendor/manufacturer", 256)); return specs; } @Override public SourceController getEntityController() { return SourceController.getInstance(); } @Override public String getTemplateFilename() { return "Source Template"; } @Override protected CreateInfo createEntityInstance(Map<String, Object> rowMap) { Source entity = getEntityController().createEntityInstance(); return new CreateInfo(entity, true, ""); } protected boolean ignoreDuplicates() { return true; } }
Because the ImportHelperBase class is generic, the domain class and domain controller class are specified as template parameters. In the example above for the "Source" domain, the class definition looks like this:
public class ImportHelperSource extends ImportHelperBase<Source, SourceController>This allows the framework to treat the domain class and controller in a generic way, but the subclass to deal with them at the domain-specific level.
For the simple import case, where we want to map the values from input spreadsheet columns to properties of the domain object, without any special transformation or processing, the standard column spec handling can be used.
The framework provides column spec classes that map a column from the input spreadsheet to a property of the domain object, as well as to a column of the output validation table used to review the import data before completing the import process. The ColumnSpec base class constructor includes the column index, column header, property name (domain property to display in the validation table), entity setter method (domain method to use for storing the column value), a flag indicating if the column value is required, and a column description. Reflection is used to invoke the entity setter method so that domain-specific code is not required in the helper subclass. The property name for displaying the column value in the validation table is used as the "value" attribute for that cell in the output datatable component. The description is used as a column comment in the empty spreadsheet template that can be downloaded from the wizard.
Specialized ColumnSpec subclasses handle the value parsed from each column with treatment for the particular data type of the column. The getColumnSpecs() method returns a list of these objects, one for each column in the input format. The subclasses include StringColumnSpec, BooleanColumnSpec, IdRefColumnSpec, and IdOrNameColumnSpec(). Each is described in more detail below:
- StringColumnSpec: This class treats the cell value as a String. It adds a single constructor parameter, the maximum length of the value (typically the column width in the corresponding database table). A value of zero means the length is unchecked. The example below specifies that the string value in column 0 is required, limited to 64 characters, the "setName()" method of the domain class will be used to store the value, and the "name" property of the domain class is the model for the corresponding column in the output validation table.
specs.add(new StringColumnSpec(0, "Name", "name", "setName", true, "Name of vendor/manufacturer", 64));
- BooleanColumnSpec: This class treats the cell value as a Boolean value. The values "true" (case insensitive) and 1 are mapped to boolean true, and "false" and 0 are mapped to false. In the example below, the boolean value in column 10 is optional, stored by the "setIsTemplate()" method of the domain class, and "isTemplate" is the property for the corresponding column in the validation table.
specs.add(new BooleanColumnSpec(10, "Is Template?", "isTemplate", "setIsTemplate", false, "Indicates whether item is a template"));
- IntegerColumnSpec: This class treats the cell value as an Integer. It fails validation if the value is not a valid integer. In the example below, the integer value in column 2 is required, stored by the setQrID() method, and the "qrId" domain property is the model for the corresponding column in the output validation table.
specs.add(new IntegerColumnSpec(2, "QR ID", "qrId", "setQrId", true, "Integer QR id of inventory unit."));
- IdRefColumnSpec: This class treats the cell value as an CDB ID reference to some other object. Additional constructor parameters include the controller class for retrieving the appropriate object by ID, and the class of the corresponding domain object. In the example below, the value in column 0 is required, and treated as a CDB object ID that is used to retrieve an instance of ItemDomainCableCatalog using the ItemDomainCableCatalogController, which is then saved using setCableCatalogItem(), and the domain property "cableCatalogItem" is the model for the corresponding column in the validation table.
specs.add(new IdRefColumnSpec(0, "Cable Catalog Item", "cableCatalogItem", "setCableCatalogItem", true, "ID or name of cable catalog item for inventory unit", ItemDomainCableCatalogController.getInstance(), ItemDomainCableCatalog.class));
- IdOrNameColumnSpec: This class is similar to the IdRefColumnSpec, but it also allows an object name reference to be used, in cases where name can uniquely identify instances of the referenced object class. To indicate that the value is a name, it must be preceded by a '#' hash character.
This is to allow items to have names that are all numeric characters. An additional constructor parameter, domainNameFilter, can be used in cases where we need to filter objects retrieved from the controller to a particular domain. For example, ItemCategory objects (used for CDB technical systems) must be filtered by the particular domain of interest (e.g., ItemDomainCableCatalog). The example is the same as above, except that the column allows an object id or name to be used as the reference.
specs.add(new IdOrNameRefColumnSpec(0, "Cable Catalog Item", "cableCatalogItem", "setCableCatalogItem", true, "ID or name of cable catalog item for inventory unit", ItemDomainCableCatalogController.getInstance(), ItemDomainCableCatalog.class, null));
Sometimes you'll want to do more than simply mapping columns from the input spreadsheet to fields of the domain object. The import helper framework supports custom column handlers, tailored instance creation from the input values, handling a variable number of columns in the input spreadsheet, and supports post-import processing. Each of these topics is discussed in more detail below.
A custom column handler is used by the helper subclass to provide specialized handling for one or more columns from the input spreadsheet.
For example, the ImportHelperCableDesign class defines a NameHandler nested class that replaces the value "#cdbid#" in the name and alternate name columns with the numeric CDB object identifier for the domain object.
There are several custom column handlers defined in ImportHelperMachineDesign. A simple example, LocationHandler, ignores the value "parent" in the location column, which otherwise expects the column values to be identifiers for CDB location objects. A more complex example, NameHandler, is defined to handle a range of columns from the input spreadsheet. It is described in more detail in the section below "variable number of columns in input spreadsheet". The "assigned item" column in the machine design import spreadsheet can contain either catalog or inventory item id's. The AssignedItemHandler provides handling for that column, looking up the CDB object for the specified numeric identifier and storing it in the rowMap dictionary for the spreadsheet row.
A custom column handler is derived from one of the handler base classes in ImportHelperBase, typically either SingleColumnInputHandler or ColumnRangeInputHandler. In either case, the primary method override is handleInput(), which does the work of the handler, raking the value(s) for the cell(s) of interest, and adding one or more entries the rowMap dictionary for the spreadsheet row. Overriding the method updateEntity() is optional. After the framework calls createEntityInstance() to generate an instance of the domain class for the spreadsheet row, it calls updateEntity() for each handler to modify the domain instance in a handler-specific way. If updateEntity() is not implemented, the domain instance is not modified by the handler after creating the instance.
For example, ImportHelperMachineDesign.AssignedItemHandler overrides handleInput to look up the CDB item by numeric object identifier and adds the corresponding object to the rowMap dictionary:
private class AssignedItemHandler extends SingleColumnInputHandler { public AssignedItemHandler(int columnIndex) { super(columnIndex); } @Override public ValidInfo handleInput( Row row, Map<Integer, String> cellValueMap, Map<String, Object> rowMap) { boolean isValid = true; String validString = ""; String parsedValue = cellValueMap.get(columnIndex); Item assignedItem = null; if ((parsedValue != null) && (!parsedValue.isEmpty())) { // assigned item is specified assignedItem = ItemFacade.getInstance().findById(Integer.valueOf(parsedValue)); if (assignedItem == null) { String msg = "Unable to find object for: " + columnNameForIndex(columnIndex) + " with id: " + parsedValue; isValid = false; validString = msg; LOGGER.info("AssignedItemHandler.handleInput() " + msg); } rowMap.put(KEY_ASSIGNED_ITEM, assignedItem); } return new ValidInfo(isValid, validString); } }
To use the custom handler, the helper class must override initialize_() to create the handler instance and associate it with a spreadsheet column, as well as creating InputColumnModel and OutputColumnModel objects for the column.
The InputColumnModel specifies a column in the input spreadsheet, including column index, column label, a flag indicating if the column value is required, and a column description. This information is used for parsing the input spreadsheet row, and creating the empty spreadsheet template.
The OutputColumnModel defines a column to be displayed in the data table on the import wizard view's validation tab, including the column index, label, and property from the domain object to display in the column.
Here is an example from ImportHelperCableDesign for the "name" column:
@Override protected ValidInfo initialize_( int actualColumnCount, Map<Integer, String> headerValueMap, List<InputColumnModel> inputColumns, List<InputHandler> inputHandlers, List<OutputColumnModel> outputColumns) { inputColumns.add(new InputColumnModel(0, "Name", true, "Cable name, uniquely identifies cable in CDB. Embedded '#cdbid# tag will be replaced with the internal CDB identifier (integer).")); inputHandlers.add(new NameHandler(0, 128)); outputColumns.add(new OutputColumnModel(0, "Name", "name")); return new ValidInfo(true, ""); }
It defines an input column model for index 0 in the import spreadsheet, adds a NameHandler for that column (specifying the max length allowed for the column value), and specifies a column for the output validation table.
The helper subclass can tailor the instance returned by the override createEntityInstance(). In the simple case, that method just returns a new instance of the domain class. But in more complex cases, it uses the dictionary of values read for the corresponding row from the input spreadsheet to initialize the domain object before returning it.
One example is ImportHelperCableInventory, which supports automatic generation of inventory unit names, instead of requiring them to be explicitly specified in the import spreadsheet. A value of "auto" in the name column triggers this behavior. In the example below, we check the value of the name column from the rowMap value dictionary and generate the name if specified to do so:
String itemName = (String) rowMap.get(KEY_NAME); if (itemName.equals(AUTO_VALUE)) { String autoName = generateItemName(item, rowMap); item.setName(autoName); } else { item.setName(itemName); }
Because we are setting the domain object's "name" property directly, we define the column spec for the "Name" column in getColumnSpecs() to specify an empty string for "propertyName" in the StringColumnSpec constructor invocation, so that the name value is not overwritten by the framework after createEntityInstance() is called:
specs.add(new StringColumnSpec(1, "Name", KEY_NAME, "", true, "Name for inventory unit.", 64));
One tricky requirement was posed after the initial implementation of the import framework, to support a variable number of columns in the input spreadsheet. This lead to the separation of the InputColumnModel, InputHandler, and OutputColumnModel classes described in the section above.
The values from the spreadsheet's header row are passed to the initialize_() method (discussed above). The initialize method can use the header row to dynamically generate InputColumnModel, InputHandler, and OutputColumnModel objects.
For example, the machine design import spreadsheet format allows a variable number of "Level" columns (e.g., "Level 1", "Level 2", ...) to capture the relationship between parents and children in the machine design hierarchy. E.g., the parent for an item whose name appears in the "Level 2" column was the last row that had a name in the "Level 1" column. ImportHelperMachineDesign.initialize_() iterates over the header row values, looking for "Level" columns, and adding an InputColumnModel for each column. A single NameHandler, derived from ColumnRangeInputHandler, is defined for the range of "Level" columns. Here is a snippet from the initialize_() method:
@Override protected ValidInfo initialize_( int actualColumnCount, Map<Integer, String> headerValueMap, List<InputColumnModel> inputColumns, List<InputHandler> inputHandlers, List<OutputColumnModel> outputColumns) { //<snip various variable declarations>> for (Entry<Integer, String> entry : headerValueMap.entrySet()) { int columnIndex = entry.getKey(); String columnHeader = entry.getValue(); // check to see if this is a "level" column if (columnHeader.startsWith(HEADER_BASE_LEVEL)) { colInfo = getColumnInfoMap().get(HEADER_BASE_LEVEL); inputColumns.add(new InputColumnModel(columnIndex, columnHeader, false, colInfo.description)); foundLevel = true; if (firstLevelIndex == -1) { firstLevelIndex = columnIndex; } lastLevelIndex = columnIndex; } else { switch (columnHeader) { case HEADER_PARENT: colInfo = getColumnInfoMap().get(HEADER_PARENT); inputColumns.add(new InputColumnModel(columnIndex, columnHeader, colInfo.isRequired, colInfo.description)); inputHandlers.add(new IdOrNameRefInputHandler(columnIndex, KEY_CONTAINER, "setImportContainerItem", ItemDomainMachineDesignController.getInstance(), ItemDomainMachineDesign.class, "")); break; // <snip other static column definitions> default: // unexpected column found, so fail isValid = false; String msg = "found unexpected column header: " + columnHeader; validString = msg; LOGGER.info(methodLogName + msg); } } } if (!foundLevel) { // didn't find any "Level" columns, so fail isValid = false; String msg = "one or more 'Level' columns is required"; validString = msg; LOGGER.info(methodLogName + msg); } else { // add handler for multiple "level" columns inputHandlers.add(new NameHandler(firstLevelIndex, lastLevelIndex, 128)); } // output columns are fixed outputColumns.add(new OutputColumnModel(0, "Parent Item", "importContainerString")); // <snip, skip other static output column definitions> return new ValidInfo(isValid, validString); }
ImportHelperMachineDesign.NameHandler is a single ColumnRangeInputHandler subclass that handles the range of "Level" columns. Its handleInput() method iterates through the range of column values, checking for a value in one of the columns. If a single column value is found, the value and indent level are added as entries to the rowMap dictionary for the spreadsheet row. If values are found in multiple "Level" columns, the row is flagged as invalid:
private class NameHandler extends ColumnRangeInputHandler { protected int maxLength = 0; public NameHandler(int firstIndex, int lastIndex, int maxLength) { super(firstIndex, lastIndex); this.maxLength = maxLength; } public int getMaxLength() { return maxLength; } @Override public ValidInfo handleInput( Row row, Map<Integer, String> cellValueMap, Map<String, Object> rowMap) { boolean isValid = true; String validString = ""; int currentIndentLevel = 1; int itemIndentLevel = 0; String itemName = null; for (int colIndex = getFirstColumnIndex(); colIndex <= getLastColumnIndex(); colIndex++) { String parsedValue = cellValueMap.get(colIndex); if ((parsedValue != null) && (!parsedValue.isEmpty())) { if (itemName != null) { // invalid, we have a value in 2 columns isValid = false; validString = "Found value in multiple 'Level' columns, only one allowed"; } else { itemName = parsedValue; itemIndentLevel = currentIndentLevel; } } currentIndentLevel = currentIndentLevel + 1; } if (itemName != null) { // check column length is valid if ((getMaxLength() > 0) && (itemName.length() > getMaxLength())) { isValid = false; validString = appendToString(validString, "Invalid name, length exceeds " + getMaxLength()); } // set item info rowMap.put(KEY_NAME, itemName); rowMap.put(KEY_INDENT, itemIndentLevel); } return new ValidInfo(isValid, validString); } }
The rowMap dictionary entries for KEY_NAME and KEY_INDENT are used in ImportHelperMachineDesign.createEntityInstance() to set the new machine design item's name and find its parent. Here are snippets:
// set name for this item item.setName((String) rowMap.get(KEY_NAME)); if ((item.getName() == null) || (item.getName().isEmpty())) { // didn't find a non-empty name column for this row isValid = false; validString = "name columns are all empty"; LOGGER.info(methodLogName + validString); return new CreateInfo(item, isValid, validString); } // find parent for this item if (!rowMap.containsKey(KEY_INDENT)) { // return because we need this value to continue isValid = false; validString = "missing indent level map entry"; LOGGER.info(methodLogName + validString); return new CreateInfo(item, isValid, validString); } int itemIndentLevel = (int) rowMap.get(KEY_INDENT); // find parent at previous indent level itemParent = parentIndentMap.get(itemIndentLevel - 1); if (itemParent == null) { // should have a parent for this item in map String msg = "Unable to determine parent for item"; LOGGER.info(methodLogName + msg); validString = appendToString(validString, msg); isValid = false; } // create "parent path" to display item hierarchy in validation table String path = ""; for (int i = 1; i < itemIndentLevel; i++) { ItemDomainMachineDesign pathItem = parentIndentMap.get(i); path = path + pathItem.getName() + "/ "; } item.setImportPath(path);
Finally, the import helper subclass may wish to take action after the import process completes. This facility was added to support ImportHelperCableDesign.NameHandler. We wanted to use the CDB numeric object identifier for the newly created cable design domain object in the object's name, but the id is not generated by the JPA framework until after the new objects are committed to the database. So we added a "postImport()" override, allowing the helper to perform custom processing after the import commit.
NOTE that we don't like this approach because it requires two transactions to generate the item names, and there is some slight chance that the 2nd transaction to update the item names will fail after the first transaction to create the cable design items succeeds. If we decide we want to utilize this functionality, we will probably add some sort of sequence generator to generate the numeric object identifiers during the first transaction, eliminating the need for the second transaction.
At any rate, this example is from ImportHelperCableDesign, where the NameHandler replaces the string "#cdbid# in the name and alternate name columns with a string like "#cdbid-nnnnnnnn#" (nnnnnnnn is a unique sequential number for the import so that the item names are unique). Here is NameHandler.handleInput() and updateEntity():
@Override public ValidInfo handleInput( Row row, Map<Integer, String> cellValueMap, Map<String, Object> rowMap) { boolean isValid = true; String validString = ""; String parsedValue = cellValueMap.get(columnIndex); // check column length is valid if ((getMaxLength() > 0) && (parsedValue.length() > getMaxLength())) { isValid = false; validString = appendToString(validString, "Value length exceeds " + getMaxLength() + " characters for column " + columnNameForIndex(columnIndex)); } // replace "#cdbid#" with a unique identifier entityNum = entityNum + 1; String idPattern = "#cdbid#"; String replacePattern = "#cdbid-" + getUniqueId() + "#"; String result = parsedValue.replaceAll(idPattern, replacePattern); rowMap.put(PROPERTY, result); return new ValidInfo(isValid, validString); } @Override public ValidInfo updateEntity( Map<String, Object> rowMap, ItemDomainCableDesign entity) { entity.setName((String)rowMap.get(PROPERTY)); return new ValidInfo(true, ""); }
In ImportHelperCableDesign.postImport(), we do a regular expression replace on the unique identifier name and alternate name strings of the form "#cdbid-nnnnnnnn#" created by the NameHandler during the import process, replacing them with the CDB numeric object identifier for the item:
@Override protected ValidInfo postImport() { String idRegexPattern = "#cdbid[^#]*#"; idRegexPattern = Matcher.quoteReplacement(idRegexPattern); Pattern pattern = Pattern.compile(idRegexPattern); List<ItemDomainCableDesign> updatedRows = new ArrayList<>(); for (ItemDomainCableDesign cable : rows) { boolean updated = false; Matcher nameMatcher = pattern.matcher(cable.getName()); if (nameMatcher.find()) { updated = true; String nameValue = cable.getName().replaceAll(idRegexPattern, String.valueOf(cable.getId())); cable.setName(nameValue); } Matcher altNameMatcher = pattern.matcher(cable.getAlternateName()); if (altNameMatcher.find()) { updated = true; String altNameValue = cable.getAlternateName().replaceAll(idRegexPattern, String.valueOf(cable.getId())); cable.setAlternateName(altNameValue); } if (updated) { updatedRows.add(cable); } } String message = ""; if (updatedRows.size() > 0) { try { getEntityController().updateList(updatedRows); message = "Updated attributes for " + updatedRows.size() + " instance(s)."; } catch (CdbException | RuntimeException ex) { Throwable t = ExceptionUtils.getRootCause(ex); message = "Post commit attribute update failed. Additional action required to update name and alternate name fields. " + ex.getMessage() + ": " + t.getMessage() + "."; } } return new ValidInfo(true, message); }
You must ensure that the setter methods exist for each column spec defined by your import helper. Some of the setter methods exist by default, for example, setName() and setDescription() are defined for all domain objects. But you will likely need to add setter methods for "non-core" properties.
If there is not an existing property for the column value you wish to save (e.g., it is not one of the core item attributes), you might need to add a metadata field to hold the property. See adding core metadata support for a CDB domain for more details.
Similarly, make sure that the domain property for each column spec exists and has at least a getter method. This is required to display the column value in the output validation table of the import wizard.
If there is not an existing property for the column value you wish to save (e.g., it is not one of the core item attributes), you might need to add a metadata field to hold the property. See adding core metadata support for a CDB domain for more details.
To add the "Import" button at the top of the domain's list view, and enable the import framework for the domain, two methods must be overridden in the domain controller:
@Override public boolean getEntityDisplayImportButton() { return true; } @Override protected DomainImportInfo initializeDomainImportInfo() { List<ImportFormatInfo> formatInfo = new ArrayList<>(); formatInfo.add(new ImportFormatInfo("Basic Catalog Format", ImportHelperCatalog.class)); formatInfo.add(new ImportFormatInfo("Catalog Assembly Format", ImportHelperCatalogAssembly.class)); String completionUrl = "/views/itemDomainCatalog/list?faces-redirect=true"; return new DomainImportInfo(formatInfo, completionUrl); }
The first, getEntityDisplayImportButton(), tells the list view framework to add the Import button for this domain. The second, initializeDomainImportInfo(), provides information to the import framewrok for mapping the import formats for a particular domain to the corresponding import helper class. It should contain an ImportFormatInfo object for each format supported by the domain.
You need to an an import.xhtml file under the domain for which you are adding import support. This simple file includes a title and identifies the appropriate type of entityController, and then includes the importWizard view. I recommend making a copy of an existing file and changing it for the new domain. Here is an example from ItemDomainCableDesign:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:p="http://primefaces.org/ui" template="../item/private/templates/itemCreateTemplate.xhtml"> <f:metadata> <f:viewAction action="#{settingController.updateGlobalSettings()}" /> <f:viewAction action="#{itemDomainCableDesignController.processCreateRequestParams}"/> <f:viewAction action="#{logController.processPreRender()}"/> </f:metadata> <ui:param name="entityController" value="#{itemDomainCableDesignController}"/> <ui:define name="middleCenter"> <h:form id="importCableDesignForm" rendered="#{loginController.loggedInAsAdmin and pageRendered}"> <div class="pageTitle"> <h1>Import Cable Designs</h1> </div> <ui:include src="../item/private/importWizard/importWizard.xhtml"> <ui:param name="entityController" value="#{entityController}" /> </ui:include> </h:form> </ui:define> </ui:composition>