Java tutorial
/** * * Copyright 2009-2014 Jayway Products AB * * 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 se.streamsource.streamflow.web.application.pdf; import org.apache.commons.lang.StringUtils; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.qi4j.api.common.Optional; import org.qi4j.api.entity.EntityReference; 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.io.Input; import org.qi4j.api.io.Output; import org.qi4j.api.io.Outputs; import org.qi4j.api.io.Receiver; import org.qi4j.api.io.Sender; import org.qi4j.api.io.Transforms; import org.qi4j.api.structure.Module; import org.qi4j.api.unitofwork.UnitOfWork; import org.qi4j.api.util.DateFunctions; import se.streamsource.streamflow.api.administration.form.AttachmentFieldValue; import se.streamsource.streamflow.api.administration.form.DateFieldValue; import se.streamsource.streamflow.api.administration.form.FieldValue; import se.streamsource.streamflow.api.administration.form.GeoLocationFieldValue; import se.streamsource.streamflow.api.administration.form.LocationDTO; import se.streamsource.streamflow.api.workspace.cases.CaseOutputConfigDTO; import se.streamsource.streamflow.api.workspace.cases.contact.ContactAddressDTO; import se.streamsource.streamflow.api.workspace.cases.contact.ContactDTO; 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.FormSignatureDTO; import se.streamsource.streamflow.util.Translator; import se.streamsource.streamflow.web.context.workspace.cases.conversation.MessagesContext; import se.streamsource.streamflow.web.domain.Describable; import se.streamsource.streamflow.web.domain.entity.caze.CaseDescriptor; import se.streamsource.streamflow.web.domain.entity.caze.CaseOutput; import se.streamsource.streamflow.web.domain.entity.form.FieldEntity; import se.streamsource.streamflow.web.domain.interaction.gtd.Assignable; import se.streamsource.streamflow.web.domain.interaction.gtd.Assignee; import se.streamsource.streamflow.web.domain.interaction.gtd.CaseId; import se.streamsource.streamflow.web.domain.interaction.gtd.DueOn; import se.streamsource.streamflow.web.domain.interaction.gtd.Ownable; import se.streamsource.streamflow.web.domain.interaction.gtd.Owner; 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.Attachment; import se.streamsource.streamflow.web.domain.structure.caselog.CaseLogEntryValue; import se.streamsource.streamflow.web.domain.structure.casetype.CaseType; import se.streamsource.streamflow.web.domain.structure.casetype.Resolution; import se.streamsource.streamflow.web.domain.structure.casetype.Resolvable; import se.streamsource.streamflow.web.domain.structure.casetype.TypedCase; import se.streamsource.streamflow.web.domain.structure.caze.Case; import se.streamsource.streamflow.web.domain.structure.caze.CasePriority; import se.streamsource.streamflow.web.domain.structure.caze.Notes; import se.streamsource.streamflow.web.domain.structure.conversation.Conversation; import se.streamsource.streamflow.web.domain.structure.conversation.Message; import se.streamsource.streamflow.web.domain.structure.conversation.Messages; import se.streamsource.streamflow.web.domain.structure.created.Creator; import se.streamsource.streamflow.web.domain.structure.form.SubmittedFormValue; import se.streamsource.streamflow.web.domain.structure.form.SubmittedPageValue; import se.streamsource.streamflow.web.domain.structure.label.Label; import se.streamsource.streamflow.web.domain.structure.label.Labelable; import se.streamsource.streamflow.web.infrastructure.attachment.AttachmentStore; import java.awt.Color; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URI; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; import static se.streamsource.streamflow.util.Strings.*; /** * A specialisation of CaseOutput that is responsible for exporting a case in * PDF format; The provided configuration tells what parts of the case are * included in the export. */ public class CasePdfGenerator implements CaseOutput { @Structure Module module; @Service AttachmentStore store; private PdfDocument document; private ResourceBundle bundle; private final CaseOutputConfigDTO config; private Locale locale; private String templateUri; private PdfFont h1Font = new PdfFont(PDType1Font.HELVETICA_BOLD, 16); private PdfFont valueFont = new PdfFont(PDType1Font.HELVETICA, 12); private PdfFont valueFontBold = new PdfFont(PDType1Font.HELVETICA_BOLD, 12); private PdfFont valueFontBoldItalic = new PdfFont(PDType1Font.HELVETICA_BOLD_OBLIQUE, 12); private PdfFont headerFont = new PdfFont(PDType1Font.HELVETICA, 10); private String caseId = ""; private String printedOn = ""; private Color headingColor = new Color(0x4b, 0x89, 0xd0); public CasePdfGenerator(@Uses CaseOutputConfigDTO config, @Optional @Uses String templateUri, @Uses Locale locale, @Uses PdfDocument document) { this.config = config; this.locale = locale; this.templateUri = templateUri; bundle = ResourceBundle.getBundle(CasePdfGenerator.class.getName(), locale); this.document = document; document.init(); } public void outputCase(CaseDescriptor cazeDescriptor) throws Throwable { Case caze = cazeDescriptor.getCase(); caseId = ((CaseId.Data) caze).caseId().get(); printedOn = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale).format(new Date()); document.print("", valueFont).changeColor(headingColor) .println(bundle.getString("caseSummary") + " - " + caseId, h1Font).line(headingColor) .changeColor(Color.BLACK).print("", valueFont); float tabStop = document.calculateTabStop(valueFontBold, bundle.getString("title"), bundle.getString("createdOn"), bundle.getString("createdBy"), bundle.getString("owner"), bundle.getString("assignedTo"), bundle.getString("caseType"), bundle.getString("labels"), bundle.getString("resolution"), bundle.getString("dueOn"), bundle.getString("priority")); if (StringUtils.isNotEmpty(caze.getDescription())) { document.printLabelAndTextWithTabStop(bundle.getString("title") + ": ", valueFontBold, caze.getDescription(), valueFont, tabStop); } if (((CasePriority.Data) caze).casepriority().get() != null) { document.printLabelAndTextWithTabStop(bundle.getString("priority") + ": ", valueFontBold, ((CasePriority.Data) caze).casepriority().get().getDescription(), valueFont, tabStop); } if (caze.createdOn().get() != null) { document.printLabelAndTextWithTabStop(bundle.getString("createdOn") + ": ", valueFontBold, DateFormat .getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale).format(caze.createdOn().get()), valueFont, tabStop); } if (((DueOn.Data) caze).dueOn().get() != null) { document.printLabelAndTextWithTabStop(bundle.getString("dueOn") + ": ", valueFontBold, new SimpleDateFormat(bundle.getString("date_format")).format(((DueOn.Data) caze).dueOn().get()), valueFont, tabStop); } Creator creator = caze.createdBy().get(); if (creator != null) { document.printLabelAndTextWithTabStop(bundle.getString("createdBy") + ": ", valueFontBold, ((Describable) creator).getDescription(), valueFont, tabStop); } Owner owner = ((Ownable.Data) caze).owner().get(); if (owner != null) { document.printLabelAndTextWithTabStop(bundle.getString("owner") + ": ", valueFontBold, ((Describable) owner).getDescription(), valueFont, tabStop); } Assignee assignee = ((Assignable.Data) caze).assignedTo().get(); if (assignee != null) { document.printLabelAndTextWithTabStop(bundle.getString("assignedTo") + ": ", valueFontBold, ((Describable) assignee).getDescription(), valueFont, tabStop); } CaseType caseType = ((TypedCase.Data) caze).caseType().get(); if (caseType != null) { document.printLabelAndTextWithTabStop(bundle.getString("caseType") + ": ", valueFontBold, ((Describable) caseType).getDescription(), valueFont, tabStop); } Resolution resolution = ((Resolvable.Data) caze).resolution().get(); if (resolution != null) { document.printLabelAndTextWithTabStop(bundle.getString("resolution") + ":", valueFontBold, ((Describable) resolution).getDescription(), valueFont, tabStop); } List<Label> labels = ((Labelable.Data) caze).labels().toList(); if (!labels.isEmpty()) { String allLabels = ""; int count = 0; for (Label label : labels) { count++; allLabels += label.getDescription() + (count == labels.size() ? "" : ", "); } document.printLabelAndTextWithTabStop(bundle.getString("labels") + ": ", valueFontBold, allLabels, valueFont, tabStop); } String note = ((Notes) caze).getLastNote() != null ? ((Notes) caze).getLastNote().note().get() : ""; if (!empty(note)) { if (Translator.HTML.equalsIgnoreCase(((Notes) caze).getLastNote().contentType().get())) { note = Translator.htmlToText(note); } document.moveUpOneRow(valueFontBold).print(bundle.getString("note") + ":", valueFontBold) .print(note, valueFont).print("", valueFont); } // traverse structure if (config.contacts().get()) { generateContacts(cazeDescriptor.contacts()); } if (config.submittedForms().get()) { generateSubmittedForms(cazeDescriptor.submittedForms()); } if (config.conversations().get()) { generateConversations(cazeDescriptor.conversations()); } if (config.attachments().get()) { generateAttachments(cazeDescriptor.attachments()); } if (config.caselog().get()) { generateCaselog(cazeDescriptor.caselog()); } } private void generateCaselog(Input<CaseLogEntryValue, RuntimeException> caselog) throws IOException { // TODO This needs to be cleaned up. Translations should be in a better place! ResourceBundle bnd = ResourceBundle.getBundle(MessagesContext.class.getName(), locale); final Map<String, String> translations = new HashMap<String, String>(); for (String key : bnd.keySet()) { translations.put(key, bnd.getString(key)); } caselog.transferTo(new Output<CaseLogEntryValue, IOException>() { public <SenderThrowableType extends Throwable> void receiveFrom( Sender<? extends CaseLogEntryValue, SenderThrowableType> sender) throws IOException, SenderThrowableType { document.changeColor(headingColor).println(bundle.getString("caselog"), valueFontBold) .changeColor(Color.BLACK); sender.sendTo(new Receiver<CaseLogEntryValue, IOException>() { public void receive(CaseLogEntryValue entry) throws IOException { UnitOfWork uow = module.unitOfWorkFactory().currentUnitOfWork(); String label = uow.get(Describable.class, entry.createdBy().get().identity()) .getDescription() + ", " + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale) .format(entry.createdOn().get()) + ": "; document.print(label, valueFontBold) .print(Translator.translate(entry.message().get(), translations), valueFont) .print("", valueFont); } }); } }); } private void generateContacts(Input<ContactDTO, RuntimeException> contacts) throws IOException { final Transforms.Counter<ContactDTO> counter = new Transforms.Counter<ContactDTO>(); contacts.transferTo(Transforms.map(counter, new Output<ContactDTO, IOException>() { public <SenderThrowableType extends Throwable> void receiveFrom( Sender<? extends ContactDTO, SenderThrowableType> sender) throws IOException, SenderThrowableType { sender.sendTo(new Receiver<ContactDTO, IOException>() { public void receive(ContactDTO value) throws IOException { Map<String, String> nameValuePairs = new LinkedHashMap<String, String>(10); if (!empty(value.name().get())) nameValuePairs.put(bundle.getString("name"), value.name().get()); if (!value.phoneNumbers().get().isEmpty() && !empty(value.phoneNumbers().get().get(0).phoneNumber().get())) nameValuePairs.put(bundle.getString("phoneNumber"), value.phoneNumbers().get().get(0).phoneNumber().get()); if (!value.addresses().get().isEmpty()) { ContactAddressDTO address = value.addresses().get().get(0); if (!empty(address.address().get())) nameValuePairs.put(bundle.getString("address"), address.address().get()); if (!empty(address.zipCode().get())) nameValuePairs.put(bundle.getString("zipCode"), address.zipCode().get()); if (!empty(address.city().get())) nameValuePairs.put(bundle.getString("city"), address.city().get()); if (!empty(address.region().get())) nameValuePairs.put(bundle.getString("region"), address.region().get()); if (!empty(address.country().get())) nameValuePairs.put(bundle.getString("country"), address.country().get()); } if (!value.emailAddresses().get().isEmpty() && !empty(value.emailAddresses().get().get(0).emailAddress().get())) nameValuePairs.put(bundle.getString("email"), value.emailAddresses().get().get(0).emailAddress().get()); if (!empty(value.contactId().get())) nameValuePairs.put(bundle.getString("contactID"), value.contactId().get()); if (!empty(value.company().get())) nameValuePairs.put(bundle.getString("company"), value.company().get()); if (!empty(value.note().get())) nameValuePairs.put(bundle.getString("note"), value.note().get()); float tabStop = document.calculateTabStop(valueFontBold, nameValuePairs.keySet().toArray(new String[nameValuePairs.keySet().size()])); if (!nameValuePairs.entrySet().isEmpty()) { document.changeColor(headingColor); document.print( bundle.getString("contact") + (counter.getCount() == 1 ? "" : " " + counter.getCount()), valueFontBold); document.changeColor(Color.BLACK).print("", valueFont); } for (Map.Entry<String, String> stringEntry : nameValuePairs.entrySet()) { document.printLabelAndTextWithTabStop(stringEntry.getKey() + ":", valueFontBold, stringEntry.getValue(), valueFont, tabStop); } } }); } })); } public void generateConversations(Input<Conversation, RuntimeException> conversations) throws IOException { final Transforms.Counter<Conversation> counter = new Transforms.Counter<Conversation>(); Output<Conversation, IOException> output = Transforms.map(counter, new Output<Conversation, IOException>() { public <SenderThrowableType extends Throwable> void receiveFrom( Sender<? extends Conversation, SenderThrowableType> sender) throws IOException, SenderThrowableType { sender.sendTo(new Receiver<Conversation, IOException>() { public void receive(Conversation conversation) throws IOException { if (counter.getCount() == 1) { document.changeColor(headingColor) .print(bundle.getString("conversations"), valueFontBold) .changeColor(Color.BLACK); } List<Message> messages = ((Messages.Data) conversation).messages().toList(); if (!messages.isEmpty()) { document.print(conversation.getDescription(), valueFontBold); for (Message message : messages) { Message.Data data = ((Message.Data) message); String label = data.sender().get().getDescription() + ", " + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale) .format(data.createdOn().get()) + ": "; String text = data.body().get(); if (MessageType.HTML.equals(data.messageType().get())) { text = Translator.htmlToText(text); } document.print(label, valueFontBold).print(text, valueFont).print("", valueFont); } } } }); } }); conversations.transferTo(output); } public void generateSubmittedForms(Input<SubmittedFormValue, RuntimeException> submittedForms) throws IOException { ArrayList<SubmittedFormValue> formValues = new ArrayList<SubmittedFormValue>(); submittedForms.transferTo(Outputs.collection(formValues)); // Latest forms first please Collections.reverse(formValues); Set<EntityReference> printedForms = new HashSet<EntityReference>(); boolean printedHeader = false; for (SubmittedFormValue formValue : formValues) { if (printedForms.contains(formValue.form().get())) continue; // Skip this form - already printed Describable form = module.unitOfWorkFactory().currentUnitOfWork().get(Describable.class, formValue.form().get().identity()); // Form PDF section header if (!printedHeader) { document.changeColor(headingColor); document.print(bundle.getString("submittedForms") + ":", valueFontBold); document.changeColor(Color.BLACK); printedHeader = true; } document.print(form.getDescription() + ":", valueFontBold).print("", valueFontBold).print("", valueFontBold); float tabStop = document.calculateTabStop(valueFontBold, new String[] { bundle.getString("lastSubmitted"), bundle.getString("lastSubmittedBy") }); // Submitted by whom and when document.printLabelAndTextWithTabStop(bundle.getString("lastSubmitted") + ":", valueFontBold, DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale) .format(formValue.submissionDate().get()), valueFont, tabStop); document.printLabelAndTextWithTabStop(bundle.getString("lastSubmittedBy") + ":", valueFontBold, module.unitOfWorkFactory().currentUnitOfWork() .get(Describable.class, formValue.submitter().get().identity()).getDescription(), valueFont, tabStop); if (formValue.signatures().get() != null && !formValue.signatures().get().isEmpty()) { FormSignatureDTO signature = formValue.signatures().get().get(0); document.printLabelAndTextWithTabStop(bundle.getString("signedBy") + ":", valueFontBold, signature.signerName().get() + " (" + signature.signerId().get() + ")", valueFont, tabStop); } for (SubmittedPageValue submittedPageValue : formValue.pages().get()) { if (!submittedPageValue.fields().get().isEmpty()) { Describable page = module.unitOfWorkFactory().currentUnitOfWork().get(Describable.class, submittedPageValue.page().get().identity()); document.print(page.getDescription() + (page.getDescription().trim().endsWith(":") ? "" : ":"), valueFontBoldItalic); document.print("", valueFont); // Fetch and occasionally format field name and value for (SubmittedFieldValue submittedFieldValue : submittedPageValue.fields().get()) { FieldValue fieldValue = module.unitOfWorkFactory().currentUnitOfWork() .get(FieldEntity.class, submittedFieldValue.field().get().identity()).fieldValue() .get(); if (!empty(submittedFieldValue.value().get())) { String label = module.unitOfWorkFactory().currentUnitOfWork() .get(Describable.class, submittedFieldValue.field().get().identity()) .getDescription(); String value = ""; // convert JSON String if field type AttachmentFieldValue if (fieldValue instanceof AttachmentFieldValue) { AttachmentFieldSubmission attachment = module.valueBuilderFactory() .newValueFromJSON(AttachmentFieldSubmission.class, submittedFieldValue.value().get()); value = attachment.name().get(); } else if (fieldValue instanceof DateFieldValue && !empty(submittedFieldValue.value().get())) { value = new SimpleDateFormat(bundle.getString("date_format")) .format(DateFunctions.fromString(submittedFieldValue.value().get())); } else if (fieldValue instanceof GeoLocationFieldValue) { LocationDTO locationDTO = module.valueBuilderFactory() .newValueFromJSON(LocationDTO.class, submittedFieldValue.value().get()); value = locationDTO.street().get() + ", " + locationDTO.zipcode().get() + ", " + locationDTO.city().get(); // String locationString = locationDTO.location().get(); // if (locationString != null) { // locationString = locationString.replace( ' ', '+' ); // if (locationString.contains( "(" )) { // String[] positions = locationString.split( "\\),\\("); // locationString = positions[0].substring( 1, positions[0].length() -1 ); // } // } // text += "<a href=\"http://maps.google.com/maps?z=13&t=m&q=" + locationString + "\" alt=\"Google Maps\">Klicka hr fr att visa karta</a>"; } else { value = submittedFieldValue.value().get(); } document.printLabelAndIndentedText(label + (label.trim().endsWith(":") ? "" : ":"), valueFontBold, value, valueFont, 20.0f); } } document.print("", valueFont); } } printedForms.add(formValue.form().get()); } } public void generateAttachments(Input<Attachment, RuntimeException> attachments) throws IOException { final Transforms.Counter<Attachment> counter = new Transforms.Counter<Attachment>(); attachments.transferTo(Transforms.map(counter, new Output<Attachment, IOException>() { public <SenderThrowableType extends Throwable> void receiveFrom( Sender<? extends Attachment, SenderThrowableType> sender) throws IOException, SenderThrowableType { sender.sendTo(new Receiver<Attachment, IOException>() { public void receive(Attachment attachment) throws IOException { if (counter.getCount() == 1) { document.changeColor(headingColor) .print(bundle.getString("attachments") + ":", valueFontBold) .changeColor(Color.BLACK); } document.print(((AttachedFile.Data) attachment).name().get(), valueFont); /* TODO Fix image insert. For some reason adding images to a PDF doesn't seem to work if (((AttachedFile.Data) attachment).mimeType().get().startsWith("image/")) { try { store.attachment(((AttachedFile.Data) attachment).uri().get(), new Visitor<InputStream, IOException>() { public boolean visit(InputStream visited) throws IOException { BufferedImage image = ImageIO.read(visited); document.print("Image insert", valueFont); document.insertImage(image); document.print("Image inserted", valueFont); return true; } }); } catch (IOException e) { LoggerFactory.getLogger(getClass()).warn("Could not insert image into generated PDF", e); } } */ } }); } })); } public PDDocument getPdf() throws IOException { document.closeAndReturn(); PDDocument generatedDoc = document.generateHeaderAndPageNumbers(headerFont, caseId, bundle.getString("printDate") + ": " + printedOn); generatedDoc.getDocumentInformation().setCreator("Streamflow"); Calendar calendar = Calendar.getInstance(); generatedDoc.getDocumentInformation().setCreationDate(calendar); generatedDoc.getDocumentInformation().setTitle(caseId); if (templateUri != null) { String attachmentId; try { attachmentId = new URI(templateUri).getSchemeSpecificPart(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); store.attachment(attachmentId).transferTo(Outputs.byteBuffer(baos)); Underlay underlay = new Underlay(); generatedDoc = underlay.underlay(generatedDoc, new ByteArrayInputStream(baos.toByteArray())); } catch (Exception e) { e.printStackTrace(); } } return generatedDoc; } }