Adding exports to a new domain
- Add the export module as a dependency in your POM
- Create a new class extending
ExportEntityType
and add a new type in accordance to the domain that you're adding exports to Create an admin extension handler to add a button for exporting on the page that you want exporting to be done in the admin
- Most likely this would be done by creating a class that extends
AbstractAdminAbstractControllerExtensionHandler
and implementsaddAdditionalMainActions()
. Be sure that one of the button classes isexport-standard-action
in order for the JavaScript in the export module to automatically setup the onclick listener for it. Example:
@Override public ExtensionResultStatusType addAdditionalMainActions(String sectionClassName, List<EntityFormAction> actions) { if (sectionClassName.equals(CustomerSegment.class.getName()) || sectionClassName.equals(CustomerSegmentImpl.class.getName())) { actions.add(new EntityFormAction("export-customer-segment") .withDisplayText("export_customer_segments") .withIconClass("icon-upload") .withButtonClass("export-standard-action") .withUrlOverride("/export/customer-segment") ); return ExtensionResultStatusType.HANDLED_CONTINUE; } return ExtensionResultStatusType.NOT_HANDLED; }
- Most likely this would be done by creating a class that extends
Create a controller that has a GET and POST for whichever url you chose for the
.withUrlOverride
property. In order to use the default modal popup provided in the export module be sure that the GET returns"views/configureExportPrompt"
. An example of the GET and POST created for the CustomerSegment export is shown below/** * Shows the form that the allows the user to create an export file */ @RequestMapping(value = "/customer-segment", method = RequestMethod.GET) public String viewConfigurePrompt(HttpServletRequest request, HttpServletResponse response, Model model) throws Exception { model.addAttribute("exportFormats", SupportedExportType.getTypes()); model.addAttribute("exportEncodings", SupportedExportEncoding.getTypes()); model.addAttribute("baseUrl", request.getRequestURL().toString()); return "views/configureExportPrompt"; } /** * Used by the Export Prompt to schedule a job so that the export can be processed later. */ @RequestMapping(value = "/customer-segment", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) public @ResponseBody Map<String, String> uploadExport(HttpServletRequest request, @RequestParam(value = "shareable", defaultValue = "false") Boolean shareable, @RequestParam("exportEncoding") String exportEncoding, @RequestParam("exportType") String exportType) { Map<String, String> resp = new HashMap<>(); customerSegmentExportScheduler.scheduleExport(exportType, exportEncoding, shareable); resp.put("status", "success"); return resp; }
Next create an export event scheduler that extends
AbstractExportEventScheduler
. In your custom scheduler, you should utilize the parent's controls forProcess
interactions to correctly interact with status for your background process. For example:@Component @ConditionalOnExport public class CustomerSegmentExportScheduler extends AbstractExportScheduler { @Resource protected CustomerSegmentService customerSegmentService; @Override public void scheduleAllCustomerSegmentExport(String formatType, String encodingType, boolean shareable, String friendlyName) { Map<String, String> processParams = buildProcessParams(formatType, encodingType, shareable, friendlyName); // Register the process Long processId = processRegistrationManager.register(friendlyName, CustomerSegmentExportProcessExecutor.PROCESS_TYPE, processParams); // Figure out the total records for my process to give statuses Long totalRecords = customerSegmentService.getNumberOfCustomerCustomerSegXrefs(); processStateManager.updateProgressTotals(processId, 0l, totalRecords); // Kick off the export process processActionManager.start(processId); } }
Then create an
ExportProcessExector
that extendsAbstractExportProcessExecutor
. You will also notice that some of rthe methods here (likeupdateProcessProgress()
) update the amounts that have been processed as they get exported. For exmple:@ConditionalOnExport @Component("blCustomerSegmentExportProcessExecutor") public class CustomerSegmentExportProcessExecutor extends AbstractExportProcessExecutor { public static final String CUST_SEG_EXP_FILENAME = "customerSegmentExport.csv"; public static final String PROCESS_TYPE = "CUSTOMER_SEG_EXPORT"; public static final String CUSTOMER_SEGMENT_ID = "CUSTOMER_SEGMENT_ID"; @Resource(name = "blCustomerSegmentExportService") protected CustomerSegmentExportService customerSegmentExportService; @Resource(name = "blCustomerSegmentService") protected CustomerSegmentService customerSegmentService; private Integer batchSize = null; @Override protected void export(ExportProcessExecutorContext context) throws IOException { boolean isFirst = true; Long firstId = Long.MIN_VALUE; Long processId = context.getProcessId(); Long recordsProcessed = 0L; if (context.getProcessParams().containsKey(CUSTOMER_SEGMENT_ID)) { Long customerSegmentId = Long.parseLong(context.getProcessParams().get(CUSTOMER_SEGMENT_ID)); Long totalRecords = customerSegmentService.getNumberOfXrefsForCustomerSegment(customerSegmentId); do { firstId = customerSegmentExportService.exportBatchCustomerSegmentsCsv(customerSegmentId, firstId, getBatchSize(), context.getOutputStream(), isFirst); isFirst = false; recordsProcessed = updateProcessProgress(processId, firstId, recordsProcessed, totalRecords); } while (firstId != null); } else { Long totalRecords = customerSegmentService.getNumberOfCustomerCustomerSegXrefs(); do { firstId = customerSegmentExportService.exportAllBatchCustomerSegmentsCsv(firstId, getBatchSize(), context.getOutputStream(), isFirst); isFirst = false; recordsProcessed = updateProcessProgress(processId, firstId, recordsProcessed, totalRecords); } while (firstId != null); } } protected Long updateProcessProgress(Long processId, Long firstId, Long recordsProcessed, Long totalRecords) { // If firstId is null, then we just finished processing the last page, otherwise we just processed a full page if (firstId == null) { processStateManager.updateProgressTotal(processId, totalRecords); return totalRecords; } else { processStateManager.updateProgressTotal(processId, recordsProcessed + getBatchSize()); return recordsProcessed + getBatchSize(); } } @Override public String getExportFileName() { return CUST_SEG_EXP_FILENAME; } @Override public String getEntityType() { return CustomerSegmentExportEntityType.CUSTOMER_SEGMENT.getType(); } protected int getBatchSize() { if (batchSize == null) { batchSize = systemPropertiesService.resolveIntSystemProperty("customerSegments.export.batchSize", 100); } return batchSize; } @Override public String getProcessType() { return PROCESS_TYPE; } }
Lastly create a service that actually does the work of exporting which will be called by
YourEventConsumer.export()
method. For example@ConditionalOnExport @Service("blCustomerSegmentExportService") public class CustomerSegmentExportServiceImpl implements CustomerSegmentExportService { @Resource(name = "blCustomerSegmentService") protected CustomerSegmentService customerSegmentService; @Autowired protected ExportWriterProvider exportWriterProvider; @Autowired protected ApplicationContext applicationContext; @Override public void exportAllCustomerSegmentsCsv(OutputStream output, int batchSize) throws IOException { boolean isFirst = true; Long firstId = Long.MIN_VALUE; do { firstId = exportAllBatchCustomerSegmentsCsv(firstId, batchSize, output, isFirst); isFirst = false; } while (firstId != null); } @Override public Long exportAllBatchCustomerSegmentsCsv(Long firstId, int count, OutputStream output, boolean useHeaders) throws IOException { List<CustomerCustomerSegmentXref> xrefs = customerSegmentService.findBatchCustomerCustomerSegXrefs(firstId, count); return exportCustomerSegments(xrefs, count, output, useHeaders); } @Override public Long exportBatchCustomerSegmentsCsv(Long customerSegmentId, Long firstId, int count, OutputStream output, boolean useHeaders) throws IOException { List<CustomerCustomerSegmentXref> xrefs = customerSegmentService.findBatchXrefsForCustomerSegment(customerSegmentId, firstId, count); return exportCustomerSegments(xrefs, count, output, useHeaders); } protected Long exportCustomerSegments(List<CustomerCustomerSegmentXref> xrefs, int count, OutputStream output, boolean useHeaders) throws JsonGenerationException, JsonMappingException, IOException { Class<?> dtoClass = applicationContext.getBean(CustomerSegmentDTO.class.getName(), CustomerSegmentDTO.class).getClass(); ObjectWriter writer = exportWriterProvider.getCsvWriter(useHeaders, dtoClass); List<CustomerSegmentDTO> currentBatch = new ArrayList<>(); for (CustomerCustomerSegmentXref custXref : xrefs) { CustomerSegmentDTO dto = applicationContext.getBean(CustomerSegmentDTO.class.getName(), CustomerSegmentDTO.class); dto.wrap(custXref); currentBatch.add(dto); } customerSegmentService.clear(); writer.writeValue(output, currentBatch); // Return next ID to be passed in for next batch call or null to indicate last page if (CollectionUtils.size(currentBatch) == count) { return currentBatch.get(currentBatch.size() - 1).getId() + 1L; } else { return null; } } }
If you would wish for your export to support additional formats or encodings then it's advised to create classes extending
SupportedExportEncoding
and/orSupportedExportType
.