Cmobilecom AF 5.19 Developer Guide

16 Entity Export/Import

Interfaces To Implement

To enable entity import and/or export as XML, its entity type must implement EntityImportSupport and/or EntityExportSupport interfaces. For example, to enable HR module ExpenseClaim export and import,

	public class ExpenseClaim extends PersistenceEntityBase implements NormativeId, CreatorAware,
		EntityImportSupport, EntityExportSupport {
	
	}
The entity import and/or export menu nodes will be added to entityType context menu and its EntityListBackingBean header or footer menu.

XML Encoding

By default the entity XML encoding/decoding uses com.cmobilecom.af.model.util.ObjectXMLSerializer. To change the XML encoding for an entity type, override the following method in its EntityBackingBean, For example, in ExpenseClaimBean,

	@Override
	protected Element createEntityElement(ExpenseClaim entity, Document doc) throws SystemException {
		Element entityElem = doc.createElement("expenseClaim");
		entityElem.setAttribute("nid", entity.getNid());
		XmlUtil.setChildElementTextValue(entityElem, "summary", entity.getSummary());
		...
		return entityElem;
	}
Accordingly override the XML decoding by overriding the following method in its EntityBackingBean (ExpenseClaimBean).
	
	@Override
	protected void fillEntityData(Element entityElem, ExpenseClaim entity, 
		Map<EntityTypeId, PersistenceEntity> entityRemap) throws SystemException {
		String nid = entityElem.getAttribute("nid");
		String summary = XmlUtil.getChildElementTextContent(entityElem, "summary");
		entity.setNid(nid);
		entity.setSummary(summary);	
		...	
	}

Import From System

A module can provide entity data for import. Take the example HR module for instance,
moduleRootDir/
	src/
		|-- main/
			|-- java/
			|-- resources
					|-- import/
							System.FD/
								|-- 1001.xml
								|-- 1002.xml
								|-- index.xml
To enable import from system (module) for an entity type, override the following method in its EntityBackingBean:
	
	@Override
	public SystemEntityImportDescriptor getSystemEntityImportDescriptor(
			TypedMenuNodeFactory factory) {
		return new SystemEntityImportDescriptor(true, null);
	}
Entity data files for import will be packaged inside module jar under the following path:
	<moduleName>/import/<dataType>
For example, ExpenseClaim form design XMLs of HR module will be packaged inside jar as followings.

	HR/import/System.FD/1001.xml
	HR/import/System.FD/1002.xml
	HR/import/System.FD/index.xml
"System.FD" is the dataType of FormDesign in System module. FormDesign entity type is defined in System module, but other modules can provide their own FormDesign XMLs for import. The module name can be omitted for entity types defined in its own module. For example, if HR module defines AreaCode(Country/State/City) for import, and the dataType for AreaCode is HR.AC.
moduleRootDir/
	src/
		|-- main/
			|-- java/
			|-- resources
					|-- import/
							System.FD/
								|-- 1001.xml
								|-- 1002.xml
								|-- index.xml
							AC/
								|-- united_states.xml
								|-- canada.xml
								|-- index.xml

The index.xml lists all the entity data files available for import.


	<?xml version="1.0" encoding="UTF-8"?>
	<dataSources>
		<dataSource id="1001">
			<name>Expense Claim - Classic</name>
			<file>1001.xml</file>
		</dataSource>
	
		<dataSource id="1002">
			<name>Expense Claim - Modern</name>
			<file>1002.xml</file>
		</dataSource>
	</dataSources>

Locale

Entity data files can be associated with a locale. Only the data files for current user locale will be available to import. Locale fallback is supported. For example, Different form designs for locales, which can not be resolved by resource bundles #{bundle.Key}.

	HR/import/System.FD/1001.xml
	HR/import/System.FD/1002.xml
	HR/import/System.FD/index.xml
	HR/import/System.FD/fr/1001.xml
	HR/import/System.FD/fr/1002.xml
	HR/import/System.FD/fr/index.xml
	HR/import/System.FD/zh/1001.xml
	HR/import/System.FD/zh/1002.xml
	HR/import/System.FD/zh/index.xml

Entity Graph

To export/import more than one type of entities, one way is to package them as zip file.

Take HR module for instance. Export the addresses and expenseClaims of an employee as zip. Assume that Address and ExpenseClaim implement EntityImportSupport and EntityExportSupport, and Employee implements EntityImportSupport. Add an Export menu node in EmployeeBean to export data for the current employee. If Employee implements EntityExportSupport, the Export context menu node would export all the employees in one file.

In EmployeeBean,

Override export/import file name extension,

	
	@Override
	public String getFileNameExtension() {
		return "zip"; 
	}
Override conflict action choices,

	@Override
	public List<SelectItem> getConflictActionSelectItems() throws SystemException {
		// create only
		Set<Object> excludeValues = new HashSet<Object>();
		excludeValues.add(ConflictAction.ABORT);
		excludeValues.add(ConflictAction.OVERRIDE);
		excludeValues.add(ConflictAction.SKIP);
		return SelectItemListProvider.getInstance(null).getSelectItems(
				SelectItemListProvider.CONFLICT_ACTION, excludeValues, null, true,
				false);
	}
To handle Export menu node action to export an employee data to zip,

	@Override
	public PageNavigation clickMenuNode(MenuNode menuNode) throws BackingBeanException,
			SystemException {
		String command = menuNode.getCommand();

		Employee employee = this.getEntity();
		if (command.equals("Export")) {
			File zipFile = ; // temporary file
			exportEmployeeDataAsZip(employee, zipFile);
			
			// download
			DownloadHelper downloadHelper = new DownloadHelper("application/zip", null,
				employee.getName());
			OutputStream os = downloadHelper.getOutputStream();
			InputStream is = null;
			try {
				is = new FileInputStream(zipFile);
				FileUtil.copy(is, os);
				downloadHelper.close();
			} catch (IOException e) {
				throw new BackingBeanException(e);
			} finally {
				try {
					if (is != null)
						is.close();
				} catch (Throwable t) {
					// log error
				}
				zipFile.delete();
			}

			return null;
		} 

		return super.clickMenuNode(menuNode);
	}
	
	public void exportEmployeeDataAsZip(Employee employee, File zipFile) throws SystemException {
		try {
			File tempDir = new File(...); // create temporary directory

			/**
			 * zip:
			 * 
			 * addresses.xml
			 * employee.xml
			 * expenseClaims.xml
			 */
			BackingBeanContext context = BackingBeanContext.getInstance();
			User user = employee.getUser();
			
			// addresses
			File addressesXmlFile = File.createTempFile(null, ".xml", tempDir);
			QueryCriteria<Address, Address> queryCriteria = new QueryCriteria<Address, Address>(Address.class, 
				new CriteriaElement[]{DetachedCriteria.eq("user", user),
					DetachedCriteria.asc("id")});
			EntityBackingBean<Address> addressBean = context.getEntityBackingBean(Address.class, null);
			addressBean.exportEntitiesToFile(queryCriteria, addressesXmlFile);

			// employee
			File employeeXmlFile = File.createTempFile(null, ".xml", tempDir);
			QueryCriteria<Employee, Employee> queryCriteria = new QueryCriteria<Employee, Employee>(Employee.class, 
				new CriteriaElement[]{DetachedCriteria.eq("id", employee.getId())});
			EntityBackingBean<Employee> employeeBean = context.getEntityBackingBean(Employee.class, null);
			employeeBean.exportEntitiesToFile(queryCriteria, employeeXmlFile);
		
			// expenseClaims
			File expenseClaimsXmlFile = File.createTempFile(null, ".xml", tempDir);
			QueryCriteria<ExpenseClaim, ExpenseClaim> queryCriteria = new QueryCriteria<ExpenseClaim, ExpenseClaim>(ExpenseClaim.class, 
				new CriteriaElement[]{DetachedCriteria.eq("employee", employee),
					DetachedCriteria.asc("nid")});
			EntityBackingBean<ExpenseClaim> expenseClaimBean = context.getEntityBackingBean(ExpenseClaim.class, null);
			expenseClaimBean.exportEntitiesToFile(queryCriteria, expenseClaimsXmlFile);

			ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));
			ZipUtil.addToZip(addressesXmlFile, "addresses.xml", zos);
			ZipUtil.addToZip(employeeXmlFile, "employee.xml", zos);
			ZipUtil.addToZip(expenseClaimsXmlFile, "expenseClaims.xml", zos);
			zos.close();

			// delete temporary files
			addressXmlFile.delete();
			employeeXmlFile.delete();
			expenseClaimXmlFile.delete();
		} catch (Exception e) {
			throw new SystemException(e);
		}
	}
The following method will be called from Employee context menu to import the addresses and expenseClaims of an employee from a zip:

	@Override
	public HandleResult importEntities(EntityImportDataSource dataSource,
			ConflictAction conflictAction, Map<EntityTypeId, PersistenceEntity> entityRemap) throws SystemException {

		File tempDir = new File(...);// create a temporary directory
		try {
			InputStream zipFileInputStream = dataSource.getInputStream();
			ZipUtil.unzip(zipFileInputStream, tempDir, null);
			
			/**
			 * zip:
			 * 
			 * addresses.xml
			 * employee.xml
			 * expenseClaims.xml
			 */
			Map<EntityTypeId, PersistenceEntity> entityRemap = new HashMap<EntityTypeId, PersistenceEntity>();
			BackingBeanContext context = BackingBeanContext.getInstance();

			HandleResult totalResults = new HandleResult(0, 0 0);
			// addresses
			File addressesXmlFile = new File(tempDir, "addresses.xml");
			if (addressesXmlFile.exists()) {
				EntityBackingBean<Address> addressBean = context.getEntityBackingBean(Address.class, containerBean);
				EntityImportDataSource dataSource = new FileDataSource("HR", addressesXmlFile);
				HandleResult result = addressBean.importEntities(dataSource, ConflictAction.CREATE_NEW, entityRemap);
				totalResults.add(result);
			}
			
			// employee
			File employeeXmlFile = new File(tempDir, "employee.xml");
			if (employeeXmlFile.exists()) {
				EntityBackingBean<Address> employeeBean = context.getEntityBackingBean(Employee.class, containerBean);
				EntityImportDataSource dataSource = new FileDataSource("HR", employeeXmlFile);
				HandleResult result = employeeBean.importEntities(dataSource, ConflictAction.CREATE_NEW, entityRemap);
				totalResults.add(result);
			}
			
			// expenseClaims
			File expenseClaimsXmlFile = new File(tempDir, "expenseClaims.xml");
			if (expenseClaimsXmlFile.exists()) {
				EntityBackingBean<ExpenseClaim> expenseClaimBean = context.getEntityBackingBean(ExpenseClaim.class, containerBean);
				EntityImportDataSource dataSource = new FileDataSource("HR", expenseClaimsXmlFile);
				HandleResult result = expenseClaimBean.importEntities(dataSource, ConflictAction.CREATE_NEW, entityRemap);
				totalResults.add(result);
			}

			return totalResults;
		} catch (Throwable t) {
			throw new BackingBeanException(t);
		} finally {
			FileUtil.deleteDir(tempDir);
		}
	}
ValidationEntity Batch PrintFrames / No Frames