Entity Export/Import

To enable entity import and/or export as XML, its entity type must implement EntityImportSupport and/or EntityExportSupport interfaces. For example, to enable ExampleHR 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

Entities can be imported from file upload or from system. To enable import from system, override
	
	@Override
	public SystemEntityImportDescriptor getSystemEntityImportDescriptor(
			TypedMenuNodeFactory factory) {
		return new SystemEntityImportDescriptor(true, null);
	}
Entity data files can be packaged inside a module jar or put under installation directory (CMOBILECOM.HOME).

Entity data files in jar must be put under [module]/import/[dataType]. For example, package ExpenseClaim form design XMLs inside jar.


	ExampleHR/import/System.FD/1001.xml
	ExampleHR/import/System.FD/1002.xml
	ExampleHR/import/System.FD/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>

On file system, entity data files must be put under [CMOBILECOM.HOME]/import/[module]/[dataType]. For example,


	[CMOBILECOM.HOME]/import/ExampleHR/System.FD/1001.xml
	[CMOBILECOM.HOME]/import/ExampleHR/System.FD/1002.xml
	[CMOBILECOM.HOME]/import/ExampleHR/System.FD/index.xml

Locale

Entity data files can be assocated with a locale. Only the data files for current user locale will be available to import. Locale fallback is supported. For example,

In jar,


	ExampleHR/import/System.FD/1001.xml
	ExampleHR/import/System.FD/1002.xml
	ExampleHR/import/System.FD/index.xml
	ExampleHR/import/System.FD/zh_CN/1001.xml
	ExampleHR/import/System.FD/zh_CN/1002.xml
	ExampleHR/import/System.FD/zh_CN/index.xml
On file system,

	[CMOBILECOM.HOME]/import/ExampleHR/System.FD/1001.xml
	[CMOBILECOM.HOME]/import/ExampleHR/System.FD/1002.xml
	[CMOBILECOM.HOME]/import/ExampleHR/System.FD/index.xml
	[CMOBILECOM.HOME]/import/ExampleHR/System.FD/zh_CN/1001.xml
	[CMOBILECOM.HOME]/import/ExampleHR/System.FD/zh_CN/1002.xml
	[CMOBILECOM.HOME]/import/ExampleHR/System.FD/zh_CN/index.xml	

Entity Graph

To export/import more than one type of entities, one way is to package them as zip file. Take ExmpleHR 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 confict 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("ExampleHR", 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("ExampleHR", 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("ExampleHR", 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);
		}
	}