/** * * Copyright * 2009-2015 Jayway Products AB * 2016-2017 Föreningen Sambruk * * Licensed under AGPL, Version 3.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.gnu.org/licenses/agpl.txt * * 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 se.streamsource.streamflow.web.context.surface.accesspoints.endusers.formdrafts.summary; import static se.streamsource.dci.api.RoleMap.role; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.ResourceBundle; import org.apache.pdfbox.exceptions.COSVisitorException; import org.apache.pdfbox.pdfwriter.COSWriter; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.velocity.VelocityContext; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.qi4j.api.common.Optional; import org.qi4j.api.concern.Concerns; import org.qi4j.api.entity.Identity; import org.qi4j.api.injection.scope.Service; import org.qi4j.api.injection.scope.Structure; import org.qi4j.api.injection.scope.Uses; import org.qi4j.api.mixin.Mixins; import org.qi4j.api.specification.Specification; import org.qi4j.api.structure.Module; import org.qi4j.api.util.Iterables; import org.qi4j.api.value.ValueBuilder; import org.qi4j.api.value.ValueBuilderFactory; import org.restlet.data.Status; import org.restlet.resource.ResourceException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import se.streamsource.dci.api.Context; import se.streamsource.dci.api.IndexContext; import se.streamsource.dci.api.RoleMap; import se.streamsource.dci.value.StringValue; import se.streamsource.streamflow.api.administration.form.AttachmentFieldValue; import se.streamsource.streamflow.api.administration.form.RequiredSignatureValue; import se.streamsource.streamflow.api.administration.form.RequiredSignaturesValue; import se.streamsource.streamflow.api.workspace.cases.CaseOutputConfigDTO; import se.streamsource.streamflow.api.workspace.cases.conversation.MessageType; import se.streamsource.streamflow.api.workspace.cases.form.AttachmentFieldSubmission; import se.streamsource.streamflow.api.workspace.cases.general.FormDraftDTO; import se.streamsource.streamflow.util.MessageTemplate; import se.streamsource.streamflow.util.Strings; import se.streamsource.streamflow.util.Translator; import se.streamsource.streamflow.util.Visitor; import se.streamsource.streamflow.web.application.defaults.SystemDefaultsService; import se.streamsource.streamflow.web.application.mail.EmailValue; import se.streamsource.streamflow.web.application.mail.HtmlMailGenerator; import se.streamsource.streamflow.web.application.pdf.PdfGeneratorService; import se.streamsource.streamflow.web.domain.entity.attachment.AttachmentEntity; import se.streamsource.streamflow.web.domain.entity.organization.OrganizationsEntity; import se.streamsource.streamflow.web.domain.entity.user.EmailUserEntity; import se.streamsource.streamflow.web.domain.entity.user.UserEntity; import se.streamsource.streamflow.web.domain.interaction.gtd.CaseId; import se.streamsource.streamflow.web.domain.structure.SubmittedFieldValue; import se.streamsource.streamflow.web.domain.structure.attachment.AttachedFile; import se.streamsource.streamflow.web.domain.structure.attachment.AttachedFileValue; import se.streamsource.streamflow.web.domain.structure.attachment.Attachment; import se.streamsource.streamflow.web.domain.structure.attachment.DefaultPdfTemplate; import se.streamsource.streamflow.web.domain.structure.attachment.FormPdfTemplate; import se.streamsource.streamflow.web.domain.structure.caze.Case; import se.streamsource.streamflow.web.domain.structure.conversation.Conversation; import se.streamsource.streamflow.web.domain.structure.conversation.Conversations; import se.streamsource.streamflow.web.domain.structure.form.EndUserCases; import se.streamsource.streamflow.web.domain.structure.form.FieldValueDefinition; import se.streamsource.streamflow.web.domain.structure.form.Form; import se.streamsource.streamflow.web.domain.structure.form.FormDraft; import se.streamsource.streamflow.web.domain.structure.form.FormDrafts; import se.streamsource.streamflow.web.domain.structure.form.MailSelectionMessage; import se.streamsource.streamflow.web.domain.structure.form.RequiredSignatures; import se.streamsource.streamflow.web.domain.structure.form.SubmittedFormValue; import se.streamsource.streamflow.web.domain.structure.form.SubmittedForms; import se.streamsource.streamflow.web.domain.structure.organization.AccessPoint; import se.streamsource.streamflow.web.domain.structure.organization.Organizations; import se.streamsource.streamflow.web.domain.structure.task.DoubleSignatureTask; import se.streamsource.streamflow.web.domain.structure.task.DoubleSignatureTasks; import se.streamsource.streamflow.web.domain.structure.user.EndUser; import se.streamsource.streamflow.web.domain.structure.user.ProxyUser; import se.streamsource.streamflow.web.domain.structure.user.Users; import se.streamsource.streamflow.web.infrastructure.attachment.AttachmentStore; import se.streamsource.streamflow.web.infrastructure.attachment.OutputstreamInput; /** * JAVADOC */ @Concerns(UpdateCaseCountFormSummaryConcern.class) @Mixins(SurfaceSummaryContext.Mixin.class) public interface SurfaceSummaryContext extends Context, IndexContext<FormDraftDTO> { void submitandsend(); RequiredSignaturesValue signatures(); StringValue mailselectionmessage(); void enablemailmessage(); void disablemailmessage(); void changeemailstobenotified( StringValue message ); abstract class Mixin implements SurfaceSummaryContext { @Structure Module module; @Service PdfGeneratorService pdfGenerator; @Uses Locale locale; @Optional @Service SystemDefaultsService defaults; @Structure ValueBuilderFactory vbf; @Service AttachmentStore attachmentStore; final Logger logger = LoggerFactory.getLogger( SubmittedForms.class.getName() ); public FormDraftDTO index() { return RoleMap.role( FormDraftDTO.class ); } public void submitandsend() { EndUserCases userCases = RoleMap.role( EndUserCases.class ); EndUser user = RoleMap.role( EndUser.class ); FormDraft formSubmission = RoleMap.role( FormDraft.class ); Case aCase = RoleMap.role( Case.class ); Users users = RoleMap.role( Users.class ); AccessPoint accessPoint = role( AccessPoint.class ); UserEntity administrator = module.unitOfWorkFactory().currentUnitOfWork().get(UserEntity.class, UserEntity.ADMINISTRATOR_USERNAME); SubmittedFormValue submittedForm = userCases.submitFormAndSendCase( aCase, formSubmission, user ); FormDraftDTO form = role( FormDraftDTO.class ); // find all form attachments and attach them to the email as well List<AttachedFileValue> formAttachments = new ArrayList<AttachedFileValue>(); if ( form.mailSelectionEnablement().get() != null && form.mailSelectionEnablement().get() ) { try { SubmittedForms.Data data = RoleMap.role( SubmittedForms.Data.class ); SubmittedFormValue submittedFormValue = null; for (SubmittedFormValue value : data.submittedForms().get()) { if (value.form().get().identity().equals( form.form().get().identity() )) { submittedFormValue = value; } } if ( submittedFormValue != null ) { Conversations conversations = RoleMap.role( Conversations.class ); Conversation conversation = conversations.createConversation( accessPoint.getDescription(), user ); PDDocument document = generatePdf( submittedFormValue ); String attachmentStoreId = addToAttachmentStore( document ); Attachment formPdfAttachment = conversation.createAttachment( "store:" + attachmentStoreId ); formPdfAttachment.changeDescription( accessPoint.getDescription() + ".pdf" ); formPdfAttachment.changeMimeType( "application/pdf" ); formPdfAttachment.changeModificationDate( new Date( ) ); formPdfAttachment.changeSize( attachmentStore.getAttachmentSize( attachmentStoreId ) ); formPdfAttachment.changeName( accessPoint.getDescription() + ".pdf" ); for (SubmittedFieldValue value : submittedFormValue.fields()) { FieldValueDefinition.Data field = module.unitOfWorkFactory().currentUnitOfWork().get( FieldValueDefinition.Data.class, value.field().get().identity() ); if ( field.fieldValue().get() instanceof AttachmentFieldValue) { if ( !Strings.empty( value.value().get() ) ) { AttachmentFieldSubmission currentFormDraftAttachmentField = module.valueBuilderFactory().newValueFromJSON(AttachmentFieldSubmission.class, value.value().get() ); AttachmentEntity attachment = module.unitOfWorkFactory().currentUnitOfWork().get( AttachmentEntity.class, currentFormDraftAttachmentField.attachment().get().identity() ); conversation.addAttachment( attachment ); } } } String[] mailAddresses = form.enteredEmails().get().split( "," ); for( String mailAddress : mailAddresses ) { conversation.addParticipant( users.createEmailUser( mailAddress ) ); } ResourceBundle bundle = ResourceBundle.getBundle( SurfaceSummaryContext.class.getName(), locale ); HtmlMailGenerator htmlMailGenerator = module.objectBuilderFactory().newObject( HtmlMailGenerator.class ); conversation.changeDraftMessage( bundle.getString( "mail_notification_body" ) ); conversation.createMessageFromDraft( administrator, MessageType.HTML ); } } catch (Throwable throwable) { logger.error( "Could not create confirmation conversation message.", throwable ); } } DoubleSignatureTask task = createDoubleSignatureTaskIfNeccessary( aCase, submittedForm ); if( task != null ) { // set task reference back to subittedform - second signee info try { Organizations.Data organizations = module.unitOfWorkFactory().currentUnitOfWork().get( Organizations.Data.class, OrganizationsEntity.ORGANIZATIONS_ID ); String organisation = organizations.organization().get().getDescription(); String id = ((CaseId.Data)aCase).caseId().get(); String link = defaults.config().configuration().webFormsProxyUrl().get() + "?tid=" + ((Identity)task ).identity().get(); VelocityContext context = new VelocityContext(); context.put( "organisation", organisation ); context.put( "id", id ); context.put( "link", link ); String subjectText = MessageTemplate.text( accessPoint.subject().get() ) .bind( "caseId", id ).bind("organisation", organisation).eval(); String velocityTemplate = accessPoint.emailTemplates().get().get( "secondsigneenotification" ); String htmlMail = module.objectBuilderFactory().newObject( HtmlMailGenerator.class ).createDoubleSignatureMail( velocityTemplate, context ); Conversations conversations = RoleMap.role( Conversations.class ); Conversation conversation = conversations.createConversation( subjectText, administrator ); EmailUserEntity emailUser = users.createEmailUser( submittedForm.secondsignee().get().email().get() ); conversation.addParticipant( emailUser ); conversation.createMessage( htmlMail, MessageType.HTML, administrator ); // TODO is there a way to collect the email value created by notification service to save into the task // for resend ValueBuilder<EmailValue> builder = module.valueBuilderFactory().newValueBuilder( EmailValue.class ); builder.prototype().subject().set( subjectText ); builder.prototype().contentType().set( Translator.HTML ); builder.prototype().content().set( htmlMail ); builder.prototype().to().set( submittedForm.secondsignee().get().email().get() ); EmailValue email = builder.newInstance(); email.attachments().set(formAttachments); task.updateEmailValue( email ); task.updateSecondDraftUrl( link ); task.updateLastReminderSent(new DateTime( DateTimeZone.UTC ) ); } catch (Throwable throwable) { logger.error( "Could not create message", throwable ); throw new ResourceException( Status.SERVER_ERROR_INTERNAL, throwable ); } } } private DoubleSignatureTask createDoubleSignatureTaskIfNeccessary( Case aCase, SubmittedFormValue submittedForm ) { DoubleSignatureTask task = null; // Check for active signatures, catch and ignore IllegalArgumentException if we do not have a role AccessPoint // in that case we are coming from the clients form wizard!! AccessPoint accessPoint = null; try { accessPoint = role( AccessPoint.class ); } catch (IllegalArgumentException ia) { // do nothing - this approach is used to determine if we are coming from Surface Webforms or from client form wizard. } if (accessPoint != null) { RequiredSignatures.Data requiredSignatures = ( RequiredSignatures.Data )accessPoint; Iterable<RequiredSignatureValue> activeSignatures = Iterables.filter( new Specification<RequiredSignatureValue>() { public boolean satisfiedBy( RequiredSignatureValue signature ) { return signature.active().get(); } }, requiredSignatures.requiredSignatures().get() ); // set second signee if we expect one if (Iterables.count( activeSignatures ) > 1) { if (submittedForm.secondsignee().get() != null && !submittedForm.secondsignee().get().singlesignature().get()) { task = ((DoubleSignatureTasks)aCase).createTask( role( Case.class ), submittedForm, null ); Form secondForm = module.unitOfWorkFactory().currentUnitOfWork().get( Form.class, requiredSignatures.requiredSignatures().get().get( 1 ).formid().get() ); FormDraft draft = ((FormDrafts)aCase).createFormDraft( secondForm ); task.updateFormDraft( draft ); task.updateAccessPoint( accessPoint ); } } } return task; } public RequiredSignaturesValue signatures() { AccessPoint accessPoint = RoleMap.role( AccessPoint.class ); RequiredSignatures.Data data = module.unitOfWorkFactory().currentUnitOfWork().get( RequiredSignatures.Data.class, ((Identity)accessPoint).identity( ).get()); ValueBuilder<RequiredSignaturesValue> valueBuilder = module.valueBuilderFactory().newValueBuilder( RequiredSignaturesValue.class ); valueBuilder.prototype().signatures().get(); for (RequiredSignatureValue signature : data.requiredSignatures().get()) { valueBuilder.prototype().signatures().get().add( signature ); } return valueBuilder.newInstance(); } public StringValue mailselectionmessage() { String message = RoleMap.current().get( MailSelectionMessage.Data.class ).mailSelectionMessage().get(); ValueBuilder<StringValue> builder = module.valueBuilderFactory().newValueBuilder( StringValue.class ); if ( message == null ) { message = ""; } builder.prototype().string().set( message ); return builder.newInstance(); } public void enablemailmessage() { FormDraft formDraft = role( FormDraft.class ); formDraft.enableEmailMessage(); } public void disablemailmessage() { FormDraft formDraft = role( FormDraft.class ); formDraft.disableEmailMessage(); } public void changeemailstobenotified( StringValue message ) { FormDraft formDraft = role( FormDraft.class ); formDraft.changeEmailsToBeNotified( message ); } private PDDocument generatePdf( SubmittedFormValue submittedFormValue ) throws Throwable { FormDraftDTO form = role( FormDraftDTO.class ); FormPdfTemplate.Data selectedTemplate = role( FormPdfTemplate.Data.class); AttachedFile.Data template = (AttachedFile.Data) selectedTemplate.formPdfTemplate().get(); if (template == null) { ProxyUser proxyUser = role(ProxyUser.class); template = (AttachedFile.Data) ((FormPdfTemplate.Data) proxyUser.organization().get()).formPdfTemplate().get(); if( template == null) { template = (AttachedFile.Data) ((DefaultPdfTemplate.Data) proxyUser.organization().get()).defaultPdfTemplate().get(); } } String uri = null; if (template != null) { uri = template.uri().get(); } CaseId.Data idData = role( CaseId.Data.class); return pdfGenerator.generateSubmittedFormPdf( submittedFormValue, idData, uri, locale ); } private String addToAttachmentStore( final PDDocument pdf ) throws Throwable { // Store case as PDF for attachment purposes ValueBuilder<CaseOutputConfigDTO> config = vbf.newValueBuilder( CaseOutputConfigDTO.class ); config.prototype().attachments().set(true); config.prototype().contacts().set(true); config.prototype().conversations().set(true); config.prototype().submittedForms().set(true); config.prototype().caselog().set(true); RoleMap.current().set(new Locale( "sv", "SE" )); String id = attachmentStore.storeAttachment(new OutputstreamInput(new Visitor<OutputStream, IOException>() { public boolean visit(OutputStream out) throws IOException { COSWriter writer = new COSWriter(out); try { writer.write(pdf); } catch (COSVisitorException e) { throw new IOException(e); } finally { writer.close(); } return true; } }, 4096)); pdf.close(); return id; } } }