Java tutorial
/* * Copyright (c) 2018 OBiBa. All rights reserved. * * This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.obiba.mica.access.service; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.inject.Inject; import javax.validation.constraints.NotNull; import com.google.common.base.Strings; import com.google.common.base.Throwables; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.eventbus.EventBus; import com.google.common.io.ByteStreams; import com.itextpdf.text.DocumentException; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.InvalidPathException; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.Option; import com.jayway.jsonpath.PathNotFoundException; import org.apache.shiro.SecurityUtils; import org.joda.time.DateTime; import org.obiba.mica.PdfUtils; import org.obiba.mica.access.DataAccessRequestRepository; import org.obiba.mica.access.NoSuchDataAccessRequestException; import org.obiba.mica.access.domain.DataAccessRequest; import org.obiba.mica.access.domain.StatusChange; import org.obiba.mica.access.event.DataAccessRequestDeletedEvent; import org.obiba.mica.access.event.DataAccessRequestUpdatedEvent; import org.obiba.mica.core.repository.AttachmentRepository; import org.obiba.mica.core.service.MailService; import org.obiba.mica.core.service.SchemaFormContentFileService; import org.obiba.mica.core.support.IdentifierGenerator; import org.obiba.mica.file.Attachment; import org.obiba.mica.file.FileStoreService; import org.obiba.mica.micaConfig.domain.DataAccessForm; import org.obiba.mica.micaConfig.service.DataAccessFormService; import org.obiba.mica.micaConfig.service.MicaConfigService; import org.obiba.mica.security.Roles; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import static com.jayway.jsonpath.Configuration.defaultConfiguration; @Service @Validated public class DataAccessRequestService { private static final Logger log = LoggerFactory.getLogger(DataAccessRequestService.class); private static final Configuration conf = defaultConfiguration().addOptions(Option.ALWAYS_RETURN_LIST); @Inject private DataAccessRequestRepository dataAccessRequestRepository; @Inject private DataAccessFormService dataAccessFormService; @Inject private DataAccessRequestUtilService dataAccessRequestUtilService; @Inject private FileStoreService fileStoreService; @Inject private SchemaFormContentFileService schemaFormContentFileService; @Inject private EventBus eventBus; @Inject private MailService mailService; @Inject private AttachmentRepository attachmentRepository; @Inject private MicaConfigService micaConfigService; @Value("classpath:config/data-access-form/data-access-request-template.pdf") private Resource defaultTemplateResource; public DataAccessRequest save(@NotNull DataAccessRequest request) { DataAccessRequest saved = request; DataAccessRequest.Status from = null; Iterable<Attachment> attachmentsToDelete = null; Iterable<Attachment> attachmentsToSave = null; if (request.isNew()) { setAndLogStatus(saved, DataAccessRequest.Status.OPENED); saved.setId(generateId()); attachmentsToSave = saved.getAttachments(); } else { saved = dataAccessRequestRepository.findOne(request.getId()); if (saved != null) { attachmentsToDelete = Sets.difference(Sets.newHashSet(saved.getAttachments()), Sets.newHashSet(request.getAttachments())); attachmentsToSave = Sets.difference(Sets.newHashSet(request.getAttachments()), Sets.newHashSet(saved.getAttachments())); from = saved.getStatus(); // validate the status dataAccessRequestUtilService.checkStatusTransition(saved, request.getStatus()); saved.setStatus(request.getStatus()); if (request.hasStatusChangeHistory()) saved.setStatusChangeHistory(request.getStatusChangeHistory()); // merge beans BeanUtils.copyProperties(request, saved, "id", "version", "createdBy", "createdDate", "lastModifiedBy", "lastModifiedDate", "statusChangeHistory"); } else { saved = request; setAndLogStatus(saved, DataAccessRequest.Status.OPENED); } } schemaFormContentFileService.save(saved, dataAccessRequestRepository.findOne(request.getId()), String.format("/data-access-request/%s", saved.getId())); if (attachmentsToSave != null) attachmentsToSave.forEach(a -> { fileStoreService.save(a.getId()); a.setJustUploaded(false); attachmentRepository.save(a); }); saved.setLastModifiedDate(DateTime.now()); dataAccessRequestRepository.saveWithReferences(saved); if (attachmentsToDelete != null) attachmentsToDelete.forEach(a -> fileStoreService.delete(a.getId())); eventBus.post(new DataAccessRequestUpdatedEvent(saved)); sendNotificationEmails(saved, from); return saved; } public DataAccessRequest saveAttachments(@NotNull DataAccessRequest request) { DataAccessRequest saved = findById(request.getId()); saved.setAttachments(request.getAttachments()); save(saved); sendAttachmentsUpdatedNotificationEmail(request); return saved; } /** * Delete the {@link DataAccessRequest} matching the identifier. * * @param id * @throws NoSuchDataAccessRequestException */ public void delete(@NotNull String id) throws NoSuchDataAccessRequestException { DataAccessRequest dataAccessRequest = findById(id); List<Attachment> attachments = dataAccessRequest.getAttachments(); dataAccessRequestRepository.deleteWithReferences(dataAccessRequest); schemaFormContentFileService.deleteFiles(dataAccessRequest); attachments.forEach(a -> fileStoreService.delete(a.getId())); eventBus.post(new DataAccessRequestDeletedEvent(dataAccessRequest)); } /** * Update the status of the {@link DataAccessRequest} matching the identifier. * * @param id * @param status * @throws NoSuchDataAccessRequestException */ public DataAccessRequest updateStatus(@NotNull String id, @NotNull DataAccessRequest.Status status) throws NoSuchDataAccessRequestException { DataAccessRequest request = findById(id); setAndLogStatus(request, status); save(request); return request; } /** * Update the content of the {@link DataAccessRequest} matching the identifier. * * @param id * @param content */ public void updateContent(@NotNull String id, String content) { DataAccessRequest request = findById(id); if (request.getStatus() != DataAccessRequest.Status.OPENED) throw new IllegalArgumentException( "Data access request content can only be modified when status is draft"); request.setContent(content); save(request); } // // Finders // /** * Get the {@link DataAccessRequest} matching the identifier. * * @param id * @return * @throws NoSuchDataAccessRequestException */ @NotNull public DataAccessRequest findById(@NotNull String id) throws NoSuchDataAccessRequestException { DataAccessRequest request = dataAccessRequestRepository.findOne(id); if (request == null) throw NoSuchDataAccessRequestException.withId(id); return request; } /** * Get all {@link DataAccessRequest}s, optionally filtered by applicant. * * @param applicant * @return */ public List<DataAccessRequest> findAll(@Nullable String applicant) { if (Strings.isNullOrEmpty(applicant)) return dataAccessRequestRepository.findAll(); return dataAccessRequestRepository.findByApplicant(applicant); } public List<DataAccessRequest> findByStatus(@Nullable List<String> status) { if (status == null || status.isEmpty()) return dataAccessRequestRepository.findAll(); List<DataAccessRequest.Status> statusList = status.stream().map(DataAccessRequest.Status::valueOf) .collect(Collectors.toList()); return dataAccessRequestRepository.findAll().stream().filter(dar -> statusList.contains(dar.getStatus())) .collect(Collectors.toList()); } // // Private methods // /** * Send a notification email, depending on the status of the request. * * @param request * @param from */ private void sendNotificationEmails(DataAccessRequest request, @Nullable DataAccessRequest.Status from) { // check is new request if (from == null) return; // check no transition if (request.getStatus() == from) return; switch (request.getStatus()) { case SUBMITTED: sendSubmittedNotificationEmail(request); break; case REVIEWED: sendReviewedNotificationEmail(request); break; case OPENED: sendOpenedNotificationEmail(request); break; case CONDITIONALLY_APPROVED: sendConditionallyApprovedEmail(request); break; case APPROVED: sendApprovedNotificationEmail(request); break; case REJECTED: sendRejectedNotificationEmail(request); break; } } public byte[] getRequestPdf(String id, String lang) { DataAccessRequest dataAccessRequest = findById(id); ByteArrayOutputStream ba = new ByteArrayOutputStream(); Object content = defaultConfiguration().jsonProvider().parse(dataAccessRequest.getContent()); try { fillPdfTemplateFromRequest(getTemplate(Locale.forLanguageTag(lang)), ba, content); } catch (IOException | DocumentException e) { throw Throwables.propagate(e); } return ba.toByteArray(); } // // Private methods // private Map<String, String> getNotificationEmailContext(DataAccessRequest request) { Map<String, String> ctx = Maps.newHashMap(); String organization = micaConfigService.getConfig().getName(); String id = request.getId(); String title = dataAccessRequestUtilService.getRequestTitle(request); ctx.put("organization", organization); ctx.put("publicUrl", micaConfigService.getPublicUrl()); ctx.put("id", id); if (Strings.isNullOrEmpty(title)) title = id; ctx.put("title", title); ctx.put("applicant", request.getApplicant()); ctx.put("status", request.getStatus().name()); return ctx; } private void sendSubmittedNotificationEmail(DataAccessRequest request) { DataAccessForm dataAccessForm = dataAccessFormService.find().get(); if (dataAccessForm.isNotifySubmitted()) { Map<String, String> ctx = getNotificationEmailContext(request); mailService.sendEmailToUsers( mailService.getSubject(dataAccessForm.getSubmittedSubject(), ctx, DataAccessRequestUtilService.DEFAULT_NOTIFICATION_SUBJECT), "dataAccessRequestSubmittedApplicantEmail", ctx, request.getApplicant()); mailService.sendEmailToGroups( mailService.getSubject(dataAccessForm.getSubmittedSubject(), ctx, DataAccessRequestUtilService.DEFAULT_NOTIFICATION_SUBJECT), "dataAccessRequestSubmittedDAOEmail", ctx, Roles.MICA_DAO); } } private void sendConditionallyApprovedEmail(DataAccessRequest request) { DataAccessForm dataAccessForm = dataAccessFormService.find().get(); if (dataAccessForm.isNotifyConditionallyApproved()) { Map<String, String> ctx = getNotificationEmailContext(request); mailService.sendEmailToUsers( mailService.getSubject(dataAccessForm.getConditionallyApprovedSubject(), ctx, DataAccessRequestUtilService.DEFAULT_NOTIFICATION_SUBJECT), "dataAccessRequestConditionallyApprovedApplicantEmail", ctx, request.getApplicant()); } } private void sendReviewedNotificationEmail(DataAccessRequest request) { DataAccessForm dataAccessForm = dataAccessFormService.find().get(); if (dataAccessForm.isNotifyReviewed() && dataAccessForm.isWithReview()) { Map<String, String> ctx = getNotificationEmailContext(request); mailService.sendEmailToUsers( mailService.getSubject(dataAccessForm.getReviewedSubject(), ctx, DataAccessRequestUtilService.DEFAULT_NOTIFICATION_SUBJECT), "dataAccessRequestReviewedApplicantEmail", ctx, request.getApplicant()); } } private void sendOpenedNotificationEmail(DataAccessRequest request) { DataAccessForm dataAccessForm = dataAccessFormService.find().get(); if (dataAccessForm.isNotifyReopened()) { Map<String, String> ctx = getNotificationEmailContext(request); mailService.sendEmailToUsers( mailService.getSubject(dataAccessForm.getReopenedSubject(), ctx, DataAccessRequestUtilService.DEFAULT_NOTIFICATION_SUBJECT), "dataAccessRequestReopenedApplicantEmail", ctx, request.getApplicant()); } } private void sendApprovedNotificationEmail(DataAccessRequest request) { DataAccessForm dataAccessForm = dataAccessFormService.find().get(); if (dataAccessForm.isNotifyApproved()) { Map<String, String> ctx = getNotificationEmailContext(request); mailService.sendEmailToUsers( mailService.getSubject(dataAccessForm.getApprovedSubject(), ctx, DataAccessRequestUtilService.DEFAULT_NOTIFICATION_SUBJECT), "dataAccessRequestApprovedApplicantEmail", ctx, request.getApplicant()); } } private void sendRejectedNotificationEmail(DataAccessRequest request) { DataAccessForm dataAccessForm = dataAccessFormService.find().get(); if (dataAccessForm.isNotifyRejected()) { Map<String, String> ctx = getNotificationEmailContext(request); mailService.sendEmailToUsers( mailService.getSubject(dataAccessForm.getRejectedSubject(), ctx, DataAccessRequestUtilService.DEFAULT_NOTIFICATION_SUBJECT), "dataAccessRequestRejectedApplicantEmail", ctx, request.getApplicant()); } } private void sendAttachmentsUpdatedNotificationEmail(DataAccessRequest request) { DataAccessForm dataAccessForm = dataAccessFormService.find().get(); if (dataAccessForm.isNotifyAttachment()) { Map<String, String> ctx = getNotificationEmailContext(request); mailService.sendEmailToUsers( mailService.getSubject(dataAccessForm.getAttachmentSubject(), ctx, DataAccessRequestUtilService.DEFAULT_NOTIFICATION_SUBJECT), "dataAccessRequestAttachmentsUpdated", ctx, request.getApplicant()); mailService.sendEmailToGroups( mailService.getSubject(dataAccessForm.getAttachmentSubject(), ctx, DataAccessRequestUtilService.DEFAULT_NOTIFICATION_SUBJECT), "dataAccessRequestAttachmentsUpdated", ctx, Roles.MICA_DAO); } } private byte[] getTemplate(Locale locale) throws IOException { DataAccessForm dataAccessForm = dataAccessFormService.find().get(); Attachment pdfTemplate = dataAccessForm.getPdfTemplates().get(locale); if (pdfTemplate == null) { Map<Locale, Attachment> pdfTemplates = dataAccessForm.getPdfTemplates(); if (!pdfTemplates.isEmpty()) { pdfTemplate = dataAccessForm.getPdfTemplates().get(Locale.ENGLISH); if (pdfTemplate == null) pdfTemplate = dataAccessForm.getPdfTemplates().values().stream().findFirst().get(); } } return pdfTemplate != null ? ByteStreams.toByteArray(fileStoreService.getFile(pdfTemplate.getFileReference())) : ByteStreams.toByteArray(defaultTemplateResource.getInputStream()); } private void fillPdfTemplateFromRequest(byte[] template, OutputStream output, Object content) throws IOException, DocumentException { Map<String, Object> requestValues = PdfUtils.getFieldNames(template).stream() .map(k -> getMapEntryFromContent(content, k)) // .filter(e -> e != null && !e.getValue().isEmpty()) // .map(e -> Maps.immutableEntry(e.getKey(), e.getValue().get(0))) // .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); PdfUtils.fillOutForm(template, output, requestValues); } private Map.Entry<String, List<Object>> getMapEntryFromContent(Object content, String jsonPath) { try { List<Object> values = JsonPath.using(conf).parse(content).read(jsonPath); return Maps.immutableEntry(jsonPath, values); } catch (PathNotFoundException ex) { //ignore } catch (InvalidPathException e) { log.warn("Invalid jsonpath {}", jsonPath); } return null; } private void setAndLogStatus(DataAccessRequest request, DataAccessRequest.Status to) { DataAccessRequest.Status from = request.getStatus(); dataAccessRequestUtilService.checkStatusTransition(request, to); request.setStatus(to); request.getStatusChangeHistory().add( // StatusChange.newBuilder() // .previous(from) // .current(to) // .author(SecurityUtils.getSubject().getPrincipal().toString()) // .now().build() // ); // } private String generateId() { DataAccessForm dataAccessForm = dataAccessFormService.find().get(); IdentifierGenerator idGenerator = IdentifierGenerator.newBuilder().prefix(dataAccessForm.getIdPrefix()) .size(dataAccessForm.getIdLength()).zeros().build(); while (true) { String id = idGenerator.generateIdentifier(); if (dataAccessRequestRepository.findOne(id) == null) return id; } } }