Java tutorial
/* * Copyright (C) 2016 FormKiQ Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.formkiq.core.service; import static com.formkiq.core.domain.type.FolderPermission.PERM_FORM_ADMIN; import static com.formkiq.core.domain.type.FolderPermission.PERM_FORM_DESIGN; import static com.formkiq.core.domain.type.FolderPermission.PERM_FORM_ENTRY; import static com.formkiq.core.domain.type.FolderPermission.PERM_FORM_RESULTS; import static com.formkiq.core.form.FormFinder.findSectionAndField; import static com.formkiq.core.form.FormFinder.findValueByKey; import static com.formkiq.core.form.dto.WorkflowOutputDocumentType.FORM; import static com.formkiq.core.form.dto.WorkflowOutputDocumentType.PDF; import static com.formkiq.core.util.Strings.extractLabelAndValue; import static org.springframework.util.StringUtils.hasText; import static org.springframework.util.StringUtils.isEmpty; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.tuple.Pair; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import com.fasterxml.jackson.core.JsonParseException; import com.formkiq.core.dao.FolderDao; import com.formkiq.core.dao.QueueDao; import com.formkiq.core.domain.Folder; import com.formkiq.core.domain.FolderAccess; import com.formkiq.core.domain.FolderForm; import com.formkiq.core.domain.User; import com.formkiq.core.domain.type.ClientFormType; import com.formkiq.core.domain.type.FolderDTO; import com.formkiq.core.domain.type.FolderFormLedgerListDTO; import com.formkiq.core.domain.type.FolderFormStatus; import com.formkiq.core.domain.type.FolderFormsListDTO; import com.formkiq.core.domain.type.FolderFormsSearchCriteria; import com.formkiq.core.domain.type.FolderListDTO; import com.formkiq.core.domain.type.FolderStatus; import com.formkiq.core.domain.type.FolderUserListDTO; import com.formkiq.core.domain.type.FormDTO; import com.formkiq.core.form.JSONService; import com.formkiq.core.form.dto.ArchiveDTO; import com.formkiq.core.form.dto.DateAuditable; import com.formkiq.core.form.dto.FormBuiltInType; import com.formkiq.core.form.dto.FormJSON; import com.formkiq.core.form.dto.FormJSONField; import com.formkiq.core.form.dto.FormJSONSection; import com.formkiq.core.form.dto.Workflow; import com.formkiq.core.form.dto.WorkflowOutput; import com.formkiq.core.form.dto.WorkflowOutputForm; import com.formkiq.core.form.dto.WorkflowOutputPdf; import com.formkiq.core.form.dto.WorkflowRoute; import com.formkiq.core.form.dto.WorkflowRouteNotification; import com.formkiq.core.form.dto.WorkflowStatus; import com.formkiq.core.form.service.FormCalculatorService; import com.formkiq.core.form.service.FormValidatorService; import com.formkiq.core.service.dto.FormSaveResult; import com.formkiq.core.service.generator.WorkflowOutputGenerator; import com.formkiq.core.service.notification.FolderOnSaveEvent; import com.formkiq.core.service.notification.NotificationService; import com.formkiq.core.service.sign.PrintRenderer; import com.formkiq.core.service.sign.SigningService; import com.formkiq.core.util.DateService; import com.formkiq.core.util.Strings; import com.formkiq.core.util.Zips; import com.google.common.collect.ImmutableMap; /** * FormService implementation. * */ public class FolderServiceImpl implements FolderService { /** ArchiveService. */ @Autowired private ArchiveService archiveService; /** AssetServiceRouter. */ @Autowired private AssetServiceRouter assetService; /** ApplicationContext. */ @Autowired private ApplicationContext context; /** {@link DateService}. */ @Autowired private DateService dateService; /** FolderDao. */ @Autowired private FolderDao folderDao; /** FormCalculatorService. */ @Autowired private FormCalculatorService formcalcService; /** Jackson2ObjectMapperBuilder. */ /** JSONService. */ @Autowired private JSONService jsonService; /** NotificationService. */ @Autowired private NotificationService notificationService; /** {@link PrintRenderer}. */ @Autowired private PrintRenderer printRenderer; /** ApplicationEventPublisher. */ @Autowired private ApplicationEventPublisher publisher; /** SpringSecurityService. */ @Autowired private SpringSecurityService securityService; /** SigningService. */ @Autowired private SigningService signingService; /** QueueDao. */ @Autowired private QueueDao queueDao; /** Validator Service. */ @Autowired private FormValidatorService validator; @Override public UUID addFolder(final String foldername) { Folder f = new Folder(); f.setName(foldername); this.folderDao.saveFolder(f); return f.getFolderid(); } /** * Check the Sync Status. * @param form {@link FolderForm} * @param formName {@link String} * @param parentuuid {@link String} * @param lastSha1hash {@link String} * @param isAdmin boolean */ private void checkSyncStatus(final FolderForm form, final String formName, final String parentuuid, final String lastSha1hash, final boolean isAdmin) { if (form != null) { if (!isAdmin && isEmpty(parentuuid) && isEmpty(lastSha1hash)) { throw new FormNotFoundException("sha1hash parameter required"); } if (hasText(lastSha1hash) && !lastSha1hash.equals(form.getSha1hash())) { throw new FormNotFoundException(formName + " has been modified. 'Sync' and try again."); } } } @Override public void createWorkflowOutput(final ArchiveDTO archive) throws IOException { Workflow workflow = archive.getWorkflow(); List<WorkflowOutput> outputs = !CollectionUtils.isEmpty(workflow.getOutputs()) ? workflow.getOutputs() : new ArrayList<>(); if (!CollectionUtils.isEmpty(workflow.getPrintsteps())) { WorkflowOutputPdf pdf = new WorkflowOutputPdf(); pdf.setForms(workflow.getPrintsteps()); outputs.add(pdf); } if (!CollectionUtils.isEmpty(outputs)) { for (WorkflowOutput wo : outputs) { if (FORM.equals(wo.getInputDocumentType())) { WorkflowOutputForm wof = (WorkflowOutputForm) wo; String uuid = extractLabelAndValue(wof.getForm()).getRight(); this.formcalcService.calculate(archive, uuid); } else if (PDF.equals(wo.getInputDocumentType())) { byte[] pdf = this.printRenderer.createPDF(archive, (WorkflowOutputPdf) wo); archive.addPDF(workflow.getUUID() + ".pdf", pdf); } else { for (WorkflowOutputGenerator wog : this.context.getBeansOfType(WorkflowOutputGenerator.class) .values()) { if (wog.isSupported(wo)) { wog.addOutputDocument(archive, wo); } } } } } } @Override public void deleteFolder(final UserDetails ud, final String folder) { User user = (User) ud; FolderAccess access = this.folderDao.findFolderAccess(user, folder); if (!this.securityService.hasPermission(access, PERM_FORM_ADMIN)) { throw new FormAccessDeniedException(); } if (this.folderDao.getFolderAccessCount(folder) > 1) { throw new PreconditionFailedException("User permissions need to be removed from folder"); } if (!this.folderDao.hasFiles(folder)) { this.folderDao.deleteFolder(folder); } else { throw new PreconditionFailedException("Forms need to be deleted first"); } } @Override public void deleteFolderAccess(final UserDetails ud, final String folder) { User user = (User) ud; FolderAccess fa = this.folderDao.findFolderAccess(user, folder); this.folderDao.deleteFolderAccess(fa); } @Override public void deleteFolderFile(final UserDetails ud, final String folder, final String uuid, final boolean isparentuuid) { User user = (User) ud; FolderAccess access = this.folderDao.findFolderAccess(user, folder); if (!this.securityService.hasPermission(access, PERM_FORM_ADMIN)) { throw new FormAccessDeniedException(); } if (isparentuuid) { this.folderDao.deleteForm(folder, uuid, isparentuuid); } else if (!this.folderDao.hasFormChildren(folder, uuid)) { this.folderDao.deleteForm(folder, uuid, isparentuuid); Map<String, String> map = ImmutableMap.<String, String>builder().put("folderid", folder) .put("formid", uuid).build(); this.queueDao.delete(map); } else { throw new PreconditionFailedException("Sub records need to be deleted first"); } } @Override public FolderDTO findFolder(final UserDetails ud, final String folder) { User user = (User) ud; FolderDTO dto = this.folderDao.findFolderDTO(user, folder); if (dto == null) { throw new FormNotFoundException("Folder not found"); } return dto; } @Override public FolderUserListDTO findFolderUsers(final String folder, final String token) { return this.folderDao.findFolderUsers(folder, token); } @Override public FormDTO findForm(final UserDetails ud, final String folder, final String uuid) { FormDTO dto = null; User user = (User) ud; FolderAccess access = this.folderDao.findFolderAccess(user, folder); if (access != null) { dto = this.folderDao.findFormDTO(folder, uuid); } if (dto == null || FolderFormStatus.DELETED.name().equals(dto.getStatus())) { throw new FormNotFoundException("Form " + uuid + " not found in folder " + folder); } return dto; } @Override public Pair<byte[], String> findFormData(final String folder, final String uuid) throws IOException { Pair<byte[], FolderForm> p = findFormDataInternal(folder, uuid); if (p.getRight() == null || FolderFormStatus.DELETED.equals(p.getRight().getStatus())) { throw new FormNotFoundException("Form " + uuid + " not found in folder " + folder); } return Pair.of(p.getLeft(), p.getRight().getSha1hash()); } /** * Finds Form Data. * @param folder {@link String} * @param uuid {@link String} * @return {@link Pair} * @throws IOException IOException */ private Pair<byte[], FolderForm> findFormDataInternal(final String folder, final String uuid) throws IOException { User user = (User) this.securityService.getUserDetails(); Pair<FolderForm, FolderAccess> pair = this.folderDao.findForm(user, folder, uuid); FolderForm form = pair.getKey(); if (form != null) { byte[] data = this.assetService.findAsset(folder, form.getAssetid().toString()); return Pair.of(data, form); } throw new FormNotFoundException("form " + uuid + " not found"); } @Override public FolderFormLedgerListDTO findFormLedger(final String folder, final String uuid) { FolderFormLedgerListDTO dto = new FolderFormLedgerListDTO(); dto.setLedgers(this.folderDao.findFormLedger(folder, uuid)); return dto; } @Override public List<WorkflowOutput> findFormOutputs(final String folder, final String uuid) throws IOException { User user = (User) this.securityService.getUserDetails(); FolderAccess access = this.folderDao.findFolderAccess(user, folder); if (access != null) { if (!this.securityService.hasPermission(access, PERM_FORM_RESULTS)) { throw new FormAccessDeniedException(); } String json = this.folderDao.findFormData(folder, uuid); Workflow wf = this.jsonService.readValue(json, Workflow.class); return wf.getOutputs(); } throw new FormNotFoundException("Folder not found"); } @Override public FolderFormsListDTO findForms(final String folder, final String uuid, final FolderFormsSearchCriteria criteria, final String token) { User user = (User) this.securityService.getUserDetails(); FolderAccess access = this.folderDao.findFolderAccess(user, folder); if (access != null) { if (!this.securityService.hasPermission(access, PERM_FORM_RESULTS, PERM_FORM_ENTRY)) { throw new FormAccessDeniedException(); } return this.folderDao.findForms(folder, uuid, criteria, token); } throw new FormNotFoundException("Folder not found"); } /** * Finds the main FolderForm. If workflow is not null then * that is it. Otherwise forms should only have 1 record and * that is the main one. * @param workflow {@link FolderForm} * @param forms {@link List} * @return {@link FolderForm} */ private FolderForm findMainFolderForm(final FolderForm workflow, final List<FolderForm> forms) { if (workflow != null) { return workflow; } if (forms.size() == 1) { return forms.iterator().next(); } throw new PreconditionFailedException("Invalid Data"); } /** * Generate SHA 256 for {@link FolderForm}. * @param user {@link User} * @param form {@link FolderForm} * @return {@link String} */ private String generateSha256(final User user, final FolderForm form) { SimpleDateFormat sdf = new SimpleDateFormat(JSONService.DEFAULT_DATE_FORMAT); String data = user.getEmail() + sdf.format(form.getUpdatedDate()) + form.getFolderid() + form.getUUID() + form.getData(); String sha256hash = DigestUtils.sha256Hex(data); return sha256hash; } /** * Get {@link FolderFormStatus}. * @param archive {@link ArchiveDTO} * @param currentStep {@link String} * @param errors {@link Map} * @return {@link FolderFormStatus} */ private FolderFormStatus getFolderFormStatus(final ArchiveDTO archive, final String currentStep, final Map<String, String> errors) { FolderFormStatus status = FolderFormStatus.ACTIVE; if (archive.getWorkflow().isAllowinprocess() && !errors.isEmpty()) { status = FolderFormStatus.IN_PROCESS; } else if (isRoutingStep(currentStep, archive)) { status = FolderFormStatus.ROUTED; } return status; } @Override public FolderListDTO getFolderList(final String text, final String token) { return this.folderDao.findFolderList(text, token); } @Override public FolderListDTO getFolderList(final UserDetails ud, final String text, final String token) { User user = (User) ud; return this.folderDao.findFolderList(user, text, token); } /** * @param archive {@link ArchiveDTO} * @param labelform {@link String} * @param labelfield {@link String} * @return {@link String} */ private String getLabel(final ArchiveDTO archive, final String labelform, final String labelfield) { String label = null; if (hasText(labelform) && hasText(labelfield)) { String formUUID = extractLabelAndValue(labelform).getRight(); int id = Integer.parseInt(extractLabelAndValue(labelfield).getRight()); Optional<FormJSON> form = archive.getForms().values().stream() .filter(s -> formUUID.equals(s.getSourceFormUUID())).findFirst(); if (form.isPresent()) { Optional<Pair<FormJSONSection, FormJSONField>> f = findSectionAndField(form.get(), id); if (f.isPresent()) { label = f.get().getRight().getValue(); } } } return label; } /** * Get Save Form Steps. * @param archive {@link ArchiveDTO} * @param formsMap {@link Map} * @return {@link List} */ private List<String> getSaveFormsSteps(final ArchiveDTO archive, final Map<String, FolderForm> formsMap) { int saveCount = 0; List<String> results = new ArrayList<>(); Workflow workflow = archive.getWorkflow(); if (workflow != null) { if (hasText(workflow.getParentUUID())) { List<String> steps = workflow.getSteps(); for (String step : steps) { if (!formsMap.containsKey(step)) { if (!isRoutingStep(step, archive)) { results.add(step); saveCount++; } else if (saveCount > 0) { break; } } } } } else { List<String> list = archive.getForms().values().stream().map(s -> s.getUUID()) .collect(Collectors.toList()); results.addAll(list); } return results; } /** * Get Validation Save Workflow. * @param archive {@link ArchiveDTO} * @return {@link Workflow} */ private Workflow getValidationSaveWorkflow(final ArchiveDTO archive) { Workflow workflow = archive.getWorkflow(); if (workflow == null) { workflow = new Workflow(); List<String> steps = archive.getForms().values().stream().map(s -> s.getUUID()) .collect(Collectors.toList()); workflow.setSteps(steps); Optional<String> first = steps.stream().findFirst(); if (first.isPresent()) { workflow.setParentUUID(archive.getForms().get(first.get()).getParentUUID()); } } return workflow; } /** * Whether Archvie DTO has a Parent UUID. * @param archive {@link ArchiveDTO} * @return boolean */ private boolean hasParentUUID(final ArchiveDTO archive) { Workflow workflow = archive.getWorkflow(); int workflowParentCount = workflow != null && hasText(workflow.getParentUUID()) ? 1 : 0; Collection<FormJSON> forms = archive.getForms().values(); int formCount = forms.size(); long formParentCount = forms.stream().filter(e -> hasText(e.getParentUUID())).count(); Collection<WorkflowRoute> routes = archive.getRoutes().values(); int routeCount = routes.size(); long routeParentCount = routes.stream().filter(e -> hasText(e.getParentUUID())).count(); if (formParentCount > 0 && formCount != formParentCount) { throw new PreconditionFailedException("Not all forms have parent uuid set"); } if (routeParentCount > 0 && routeCount != routeParentCount) { throw new PreconditionFailedException("Not all routes have parent uuid set"); } return workflowParentCount > 0 || formParentCount > 0 || routeParentCount > 0; } /** * Whether to publish the folder on save event. * @param archive {@link ArchiveDTO} * @return boolean */ private boolean isFolderOnSaveEvent(final ArchiveDTO archive) { Workflow wf = archive.getWorkflow(); if (wf != null && hasText(wf.getParentUUID())) { return true; } for (Map.Entry<String, FormJSON> e : archive.getForms().entrySet()) { FormJSON form = e.getValue(); String parentuuid = form.getParentUUID(); if (hasText(parentuuid)) { return true; } } return false; } /** * Is Current Step a routing Step. * @param step {@link String} * @param archive {@link ArchiveDTO} * @return boolean */ private boolean isRoutingStep(final String step, final ArchiveDTO archive) { boolean found = false; Workflow wf = archive.getWorkflow(); if (wf != null) { if (hasText(step)) { found = archive.getRoutes().get(step) != null; if (!found) { FormJSON form = archive.getForms().get(step); found = form != null && FormBuiltInType.DOCSIGN.toString().equals(form.getBuiltintype()); } } } return found; } /** * Loads Folder Forms from database. * * @param archive {@link ArchiveDTO} * @param folder {@link String} * @param isAdmin boolean * @return {@link Map} */ private Map<String, FolderForm> loadFolderForms(final ArchiveDTO archive, final String folder, final boolean isAdmin) { Map<String, FolderForm> map = new HashMap<>(); User user = (User) this.securityService.getUserDetails(); Workflow workflow = archive.getWorkflow(); if (workflow != null) { FolderForm ff = isAdmin ? this.folderDao.findForm(folder, workflow.getUUID()) : this.folderDao.findForm(user, folder, workflow.getUUID()).getLeft(); if (ff != null) { map.put(workflow.getUUID(), ff); } } for (FormJSON formJSON : archive.getForms().values()) { FolderForm form = isAdmin ? this.folderDao.findForm(folder, formJSON.getUUID()) : this.folderDao.findForm(user, folder, formJSON.getUUID()).getLeft(); if (form != null) { map.put(formJSON.getUUID(), form); } } return map; } /** * Process Workflow Routing. * @param folder {@link String} * @param route {@link WorkflowRoute} * @param archive {@link ArchiveDTO} */ private void processRouting(final String folder, final WorkflowRoute route, final ArchiveDTO archive) { for (WorkflowRouteNotification notify : route.getNotifications()) { this.notificationService.sendRoutingNotification(folder, notify.getMethod(), notify.getEmail(), archive); } } @Override public UUID saveFolder(final UserDetails ud, final String folder, final String foldername) { User user = (User) ud; if (hasText(folder)) { FolderAccess fa = this.folderDao.findFolderAccess(user, folder); if (fa != null) { if (!this.securityService.hasPermission(fa, PERM_FORM_ADMIN)) { throw new FormAccessDeniedException(); } Folder f = this.folderDao.findFolder(folder); f.setName(foldername); this.folderDao.saveFolder(f); return f.getFolderid(); } throw new FormNotFoundException(foldername); } UUID folderId = addFolder(foldername); FolderAccess fa = new FolderAccess(); fa.setStatus(FolderStatus.ACTIVE); fa.setFolderid(folderId); fa.setUserid(user.getUserid()); fa.setPermissions(Arrays.asList(PERM_FORM_ADMIN)); this.folderDao.saveFolderAccess(fa); return folderId; } @Override public FormSaveResult saveForm(final String folder, final ArchiveDTO archive, final String lastSha1hash, final boolean isAdmin) throws IOException { if (hasParentUUID(archive)) { return saveFormWithParentUUID(folder, archive, lastSha1hash, isAdmin); } return saveFormWithoutParentUUID(folder, archive, lastSha1hash, isAdmin); } // Save without parent.. Workflow, Form, Route @Override public FormSaveResult saveForm(final String folder, final byte[] bytes, final String lastSha1hash, final boolean isAdmin) throws IOException { ArchiveDTO archive = null; try { archive = this.jsonService.readValue(bytes, ArchiveDTO.class); } catch (JsonParseException e) { archive = this.archiveService.extractJSONFromZipFile(bytes); } return saveForm(folder, archive, lastSha1hash, isAdmin); } /** * Saves Forms. * @param archive {@link ArchiveDTO} * @param workflow {@link FolderForm} * @param folder {@link String} * @param formsMap {@link Map} * @param status {@link FolderFormStatus} * @return {@link String} - SHA1Hash * @throws IOException IOException */ private List<FolderForm> saveForms(final ArchiveDTO archive, final FolderForm workflow, final String folder, final Map<String, FolderForm> formsMap, final FolderFormStatus status) throws IOException { String sha1hash = null; List<FolderForm> formlist = new ArrayList<>(); User user = (User) this.securityService.getUserDetails(); if (workflow != null && hasText(workflow.getSha1hash())) { sha1hash = workflow.getSha1hash(); } Map<String, FormJSON> forms = archive.getForms(); List<String> steps = getSaveFormsSteps(archive, formsMap); for (String step : steps) { FormJSON form = forms.get(step); String uuid = form.getUUID(); String parentuuid = form.getParentUUID(); String data = this.jsonService.writeValueAsString(form); if (formsMap.containsKey(uuid) && hasText(parentuuid)) { continue; } FolderForm folderForm = formsMap.containsKey(uuid) ? formsMap.get(uuid) : new FolderForm(); updateDates(folderForm, form); folderForm.setType(ClientFormType.FORM); folderForm.setFolderid(UUID.fromString(folder)); folderForm.setUUID(UUID.fromString(uuid)); folderForm.setData(data); folderForm.setStatus(status); folderForm.setInsertedDate(form.getInserteddate()); folderForm.setUpdatedDate(form.getUpdateddate()); if (hasText(parentuuid)) { folderForm.setParentUUID(UUID.fromString(parentuuid)); } String sha256 = generateSha256(user, folderForm); if (isEmpty(parentuuid)) { byte[] zipfile = Zips.zipFile(form.getUUID() + ".form", data); updateAsset(folderForm, folder, zipfile, sha1hash); this.folderDao.saveForm(user, folderForm, sha256); } else if (workflow == null) { byte[] bytes = this.archiveService.createZipFile(archive); updateAsset(folderForm, folder, bytes, null); this.folderDao.saveForm(user, folderForm, sha256); } else { folderForm.setSha1hash(sha1hash); folderForm.setType(ClientFormType.WORKFLOW_FORM); this.folderDao.saveForm(user, folderForm, sha256); } if (isEmpty(sha1hash)) { sha1hash = folderForm.getSha1hash(); } formlist.add(folderForm); } return formlist; } /** * Saves ArchiveDTO without Parent UUID. * @param folder {@link String} * @param archive {@link ArchiveDTO} * @param lastSha1hash {@link String} * @param isAdmin boolean * @return {@link FormSaveResult} * @throws IOException IOException * @throws FormValidatorException FormValidatorException */ private FormSaveResult saveFormWithoutParentUUID(final String folder, final ArchiveDTO archive, final String lastSha1hash, final boolean isAdmin) throws IOException { FolderForm workflow = null; Map<String, String> errors = Collections.emptyMap(); if (archive.getRoutes().size() > 1) { throw new PreconditionFailedException("Data can only contain 1 route"); } FolderFormStatus status = FolderFormStatus.ACTIVE; validateSaveFormFolderAccess(archive, folder, isAdmin); Map<String, FolderForm> formsMap = loadFolderForms(archive, folder, isAdmin); validateFormsSync(archive, folder, formsMap, lastSha1hash, isAdmin); errors = this.validator.validate(archive); // errors = validateFormAndGetCurrentStep(archive, formsMap).getRight(); if (errors.isEmpty()) { Pair<FolderForm, Map<String, String>> pair = saveWorkflow(archive, folder, formsMap, status); workflow = pair.getLeft(); errors = pair.getRight(); } String sha1hash = workflow != null ? workflow.getSha1hash() : null; return new FormSaveResult(null, sha1hash, errors); } /** * Saves ArchiveDTO with Parent UUID. * @param folder {@link String} * @param archive {@link ArchiveDTO} * @param lastSha1hash {@link String} * @param isAdmin boolean * @return {@link FormSaveResult} * @throws IOException IOException */ private FormSaveResult saveFormWithParentUUID(final String folder, final ArchiveDTO archive, final String lastSha1hash, final boolean isAdmin) throws IOException { String sha1hash = null; FolderFormStatus status = null; Workflow w = archive.getWorkflow(); boolean allowinprocess = w.isAllowinprocess(); Map<String, String> errors = Collections.emptyMap(); validateSaveFormFolderAccess(archive, folder, isAdmin); validateFormsParent(archive, folder, lastSha1hash, isAdmin); Map<String, FolderForm> formsMap = loadFolderForms(archive, folder, isAdmin); updateWorkflowLabels(archive); validateFormsSync(archive, folder, formsMap, lastSha1hash, isAdmin); Pair<String, Map<String, String>> pair = validateFormAndGetCurrentStep(archive, formsMap); String currentStep = pair.getLeft(); errors = pair.getRight(); if (errors.isEmpty() || allowinprocess) { status = getFolderFormStatus(archive, currentStep, errors); w.setStatus( FolderFormStatus.IN_PROCESS.equals(status) ? WorkflowStatus.IN_PROCESS : WorkflowStatus.DONE); if (FolderFormStatus.ACTIVE.equals(status)) { createWorkflowOutput(archive); } boolean publishSaveEvent = isFolderOnSaveEvent(archive); Pair<FolderForm, Map<String, String>> pworkflow = saveWorkflow(archive, folder, formsMap, status); FolderForm workflow = pworkflow.getLeft(); errors = pworkflow.getRight(); if (errors.isEmpty()) { List<FolderForm> forms = saveForms(archive, workflow, folder, formsMap, status); FolderForm mainRecord = findMainFolderForm(workflow, forms); WorkflowRoute route = archive.getRoutes().get(currentStep); if (route != null) { processRouting(folder, route, archive); } sendDocsignIfStep(folder, mainRecord, archive, currentStep); updateDocsignResultIfStep(folder, archive, currentStep); if (publishSaveEvent) { User user = (User) this.securityService.getUserDetails(); this.publisher.publishEvent(new FolderOnSaveEvent(user, folder, archive)); } sha1hash = workflow != null ? workflow.getSha1hash() : forms.get(0).getSha1hash(); } } return new FormSaveResult(status, sha1hash, errors); } /** * Save Workflow. * @param archive {@link ArchiveDTO} * @param folder {@link String} * @param formsMap {@link Map} * @param status {@link FolderFormStatus} * @return {@link Pair} * @throws IOException IOException */ private Pair<FolderForm, Map<String, String>> saveWorkflow(final ArchiveDTO archive, final String folder, final Map<String, FolderForm> formsMap, final FolderFormStatus status) throws IOException { Workflow wf = archive.getWorkflow(); // TODO test.. Map<String, String> errors = this.validator.validate(wf); if (wf != null && errors.isEmpty()) { String uuid = wf.getUUID(); String parentuuid = wf.getParentUUID(); FolderForm form = formsMap.containsKey(uuid) ? formsMap.get(uuid) : new FolderForm(); updateDates(form, wf); form.setType(ClientFormType.WORKFLOW); form.setFolderid(UUID.fromString(folder)); form.setUUID(UUID.fromString(uuid)); String workflowJSON = this.jsonService.writeValueAsString(wf); form.setData(workflowJSON); form.setStatus(status); if (hasText(parentuuid)) { form.setParentUUID(UUID.fromString(parentuuid)); } byte[] data = this.archiveService.createZipFile(archive); updateAsset(form, folder, data, null); User user = (User) this.securityService.getUserDetails(); String sha256 = generateSha256(user, form); this.folderDao.saveForm(user, form, sha256); return Pair.of(form, errors); } return Pair.of(null, errors); } /** * Send Email when Docsign is Completed. * @param archive {@link ArchiveDTO} * @param form {@link FormJSON} * @throws IOException IOException */ private void sendDocsignCompleteEmail(final ArchiveDTO archive, final FormJSON form) throws IOException { String emailsubject = "Completed signing " + archive.getName(); String body = IOUtils.toString( this.context.getResource("classpath:/templates/email/sign-complete.html").getInputStream(), Strings.CHARSET_UTF8); String signer1name = findValueByKey(form, "signer1name").get().getValue(); body = body.replaceAll("\\$\\{signername\\}", signer1name); User user = (User) this.securityService.getUserDetails(); this.notificationService.sendEmail(user.getEmail(), emailsubject, body, null, null); } /** * Send Docsign If Step. * @param folder {@link String} * @param mainRecord {@link FolderForm} * @param archive {@link ArchiveDTO} * @param step {@link String} * @throws IOException IOException */ private void sendDocsignIfStep(final String folder, final FolderForm mainRecord, final ArchiveDTO archive, final String step) throws IOException { FormJSON form = archive.getForm(step, FormBuiltInType.DOCSIGN); if (form != null) { this.signingService.sendSigningRequest(mainRecord, form); } } /** * Update Asset on FolderForm. * @param form {@link FolderForm} * @param folder {@link String} * @param bytes byte[] * @param sha1hash {@link String} */ private void updateAsset(final FolderForm form, final String folder, final byte[] bytes, final String sha1hash) { UUID assetid = form.getAssetid(); if (assetid == null) { assetid = UUID.randomUUID(); } if (bytes != null) { this.assetService.saveAsset(folder, assetid.toString(), bytes); form.setAssetid(assetid); form.setSha1hash(DigestUtils.sha1Hex(bytes)); } if (hasText(sha1hash)) { form.setSha1hash(sha1hash); } } /** * Update {@link Workflow} dates if missing. * @param form {@link FolderForm} * @param audit {@link DateAuditable} */ private void updateDates(final FolderForm form, final DateAuditable audit) { form.setInsertedDate(audit.getInserteddate()); form.setUpdatedDate(audit.getUpdateddate()); Date now = this.dateService.now(); if (form.getInsertedDate() == null) { form.setInsertedDate(now); audit.setInserteddate(now); } form.setUpdatedDate(now); audit.setUpdateddate(now); } /** * Update Docsign Result Step. * @param folder {@link String} * @param archive {@link ArchiveDTO} * @param step {@link String} * @throws IOException IOException */ private void updateDocsignResultIfStep(final String folder, final ArchiveDTO archive, final String step) throws IOException { FormJSON docsignResult = archive.getForm(step, FormBuiltInType.DOCSIGN_RESULT); if (docsignResult != null) { Map<String, String> map = ImmutableMap.<String, String>builder().put("folderid", folder) .put("formid", archive.getWorkflow().getUUID()).build(); this.queueDao.delete(map); FormJSON docsign = archive.getForm(FormBuiltInType.DOCSIGN); sendDocsignCompleteEmail(archive, docsign); } } /** * Update Workflow Labels. * @param archive {@link ArchiveDTO} */ private void updateWorkflowLabels(final ArchiveDTO archive) { Workflow workflow = archive.getWorkflow(); if (isEmpty(workflow.getLabel1())) { workflow.setLabel1(getLabel(archive, workflow.getLabel1form(), workflow.getLabel1field())); } if (isEmpty(workflow.getLabel2())) { workflow.setLabel2(getLabel(archive, workflow.getLabel2form(), workflow.getLabel2field())); } if (isEmpty(workflow.getLabel3())) { workflow.setLabel3(getLabel(archive, workflow.getLabel3form(), workflow.getLabel3field())); } } /** * Validates Forms and returns the current step. * @param archive {@link ArchiveDTO} * @param formsMap {@link Map} * @return {@link Pair} */ private Pair<String, Map<String, String>> validateFormAndGetCurrentStep(final ArchiveDTO archive, final Map<String, FolderForm> formsMap) { String currentstep = null; WorkflowRoute currentRoute = null; Map<String, String> errors = Collections.emptyMap(); Workflow workflow = getValidationSaveWorkflow(archive); // validate Entries for (String step : workflow.getSteps()) { FormJSON form = archive.getForm(step); WorkflowRoute route = archive.getRoute(step); if (form != null) { Map<String, String> errs = this.validator.validateFormJSON(archive, form); if (errs.isEmpty()) { validateUserHasAccessToRoute(currentRoute); currentstep = step; } else if (currentRoute == null) { errors = errs; } else { break; } } else if (route != null) { currentRoute = route; currentstep = step; } else { throw new FormNotFoundException("form " + step + " not found"); } } return Pair.of(currentstep, errors); } /** * Validate Form Parent Exists. * @param archive {@link ArchiveDTO} * @param folder {@link String} * @param lastSha1hash {@link String} * @param isAdmin boolean */ private void validateFormsParent(final ArchiveDTO archive, final String folder, final String lastSha1hash, final boolean isAdmin) { User user = (User) this.securityService.getUserDetails(); for (FormJSON formJSON : archive.getForms().values()) { String parentuuid = formJSON.getParentUUID(); if (!isEmpty(parentuuid)) { FolderForm ff = isAdmin ? this.folderDao.findForm(folder, parentuuid) : this.folderDao.findForm(user, folder, parentuuid).getLeft(); if (ff == null && StringUtils.isEmpty(formJSON.getBuiltintype())) { throw new FormNotFoundException("form " + parentuuid + " not found"); } } } } /** * Validate Form with out Parent (IE: top level). * @param archive {@link ArchiveDTO} * @param folder {@link String} * @param formsMap {@link Map} * @param lastSha1hash {@link String} * @param isAdmin boolean * @return {@link Map} */ private Map<String, FolderForm> validateFormsSync(final ArchiveDTO archive, final String folder, final Map<String, FolderForm> formsMap, final String lastSha1hash, final boolean isAdmin) { Map<String, FolderForm> map = new HashMap<>(); Workflow workflow = archive.getWorkflow(); if (workflow != null) { FolderForm form = formsMap.get(workflow.getUUID()); checkSyncStatus(form, workflow.getName(), workflow.getParentUUID(), lastSha1hash, isAdmin); } for (FormJSON formJSON : archive.getForms().values()) { FolderForm form = formsMap.get(formJSON.getUUID()); checkSyncStatus(form, formJSON.getName(), formJSON.getParentUUID(), lastSha1hash, isAdmin); } return map; } /** * Validate user has save access to folder. * @param archive {@link ArchiveDTO} * @param folder {@link String} * @param isAdmin boolean */ private void validateSaveFormFolderAccess(final ArchiveDTO archive, final String folder, final boolean isAdmin) { if (!isAdmin) { User user = (User) this.securityService.getUserDetails(); FolderAccess access = this.folderDao.findFolderAccess(user, folder); Workflow workflow = getValidationSaveWorkflow(archive); if (access != null) { String parentuuid = workflow.getParentUUID(); if (!isEmpty(parentuuid) && !this.securityService.hasPermission(access, PERM_FORM_ENTRY)) { throw new FormAccessDeniedException(); } if (isEmpty(parentuuid) && !this.securityService.hasPermission(access, PERM_FORM_DESIGN)) { throw new FormAccessDeniedException(); } } else { throw new FormAccessDeniedException(); } } } /** * Validates User has access to route. * @param wr {@link WorkflowRoute} */ private void validateUserHasAccessToRoute(final WorkflowRoute wr) { if (wr != null) { User us = (User) this.securityService.getUserDetails(); boolean match = wr.getNotifications().stream().anyMatch(s -> s.getEmail().equals(us.getEmail())); if (!match) { throw new PreconditionFailedException("User not allowed to continue form"); } } } }