View Javadoc
1   /*
2    * Copyright 2006 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.kuali.ole.fp.document.service.impl;
18  
19  import java.io.ByteArrayOutputStream;
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.OutputStream;
23  
24  import org.apache.commons.lang.StringUtils;
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.kuali.ole.fp.businessobject.Check;
28  import org.kuali.ole.fp.document.CashReceiptDocument;
29  import org.kuali.ole.fp.document.service.CashReceiptCoverSheetService;
30  import org.kuali.rice.kew.api.WorkflowDocument;
31  import org.kuali.rice.kns.service.DataDictionaryService;
32  import org.kuali.rice.kns.service.DocumentHelperService;
33  
34  import com.lowagie.text.Document;
35  import com.lowagie.text.DocumentException;
36  import com.lowagie.text.Rectangle;
37  import com.lowagie.text.pdf.AcroFields;
38  import com.lowagie.text.pdf.BaseFont;
39  import com.lowagie.text.pdf.PdfContentByte;
40  import com.lowagie.text.pdf.PdfImportedPage;
41  import com.lowagie.text.pdf.PdfReader;
42  import com.lowagie.text.pdf.PdfStamper;
43  import com.lowagie.text.pdf.PdfWriter;
44  
45  /**
46   * Implementation of service for handling creation of the cover sheet of the <code>{@link CashReceiptDocument}</code>
47   */
48  public class CashReceiptCoverSheetServiceImpl implements CashReceiptCoverSheetService {
49      private static Log LOG = LogFactory.getLog(CashReceiptCoverSheetService.class);
50      
51      private DataDictionaryService dataDictionaryService;
52      private DocumentHelperService documentHelperService;
53  
54      public static final String CR_COVERSHEET_TEMPLATE_NM = "CashReceiptCoverSheetTemplate.pdf";
55  
56      private static final float LEFT_MARGIN = 45;
57      private static final float TOP_MARGIN = 45;
58      private static final float TOP_FIRST_PAGE = 440;
59  
60      private static final String DOCUMENT_NUMBER_FIELD = "DocumentNumber";
61      private static final String INITIATOR_FIELD = "Initiator";
62      private static final String CREATED_DATE_FIELD = "CreatedDate";
63      private static final String AMOUNT_FIELD = "Amount";
64      private static final String ORG_DOC_NUMBER_FIELD = "OrgDocNumber";
65      private static final String CAMPUS_FIELD = "Campus";
66      private static final String DEPOSIT_DATE_FIELD = "DepositDate";
67      private static final String DESCRIPTION_FIELD = "Description";
68      private static final String EXPLANATION_FIELD = "Explanation";
69      private static final String CHECKS_FIELD = "Checks";
70      private static final String CURRENCY_FIELD = "Currency";
71      private static final String COIN_FIELD = "Coin";
72      private static final String CREDIT_CARD_FIELD = "CreditCard";
73      private static final String CHANGE_OUT_FIELD = "ChangeOut";
74      private static final String TOTAL_RECONCILIATION_FIELD = "ReconciliationTotal";
75  
76      private static final int FRONT_PAGE = 1;
77      private static final int CHECK_PAGE_NORMAL = 2;
78      private static final float CHECK_DETAIL_HEADING_HEIGHT = 45;
79      private static final float CHECK_LINE_SPACING = 12;
80      private static final float CHECK_FIELD_MARGIN = 12;
81      private static final float CHECK_NORMAL_FIELD_LENGTH = 100;
82      private static final float CHECK_FIELD_HEIGHT = 10;
83      private static final int MAX_CHECKS_FIRST_PAGE = 30;
84      private static final int MAX_CHECKS_NORMAL = 65;
85  
86      private static final float CHECK_HEADER_HEIGHT = 12;
87      private static final String CHECK_NUMBER_FIELD_PREFIX = "CheckNumber";
88      private static final float CHECK_NUMBER_FIELD_POSITION = LEFT_MARGIN;
89  
90      private static final String CHECK_DATE_FIELD_PREFIX = "CheckDate";
91      private static final float CHECK_DATE_FIELD_POSITION = CHECK_NUMBER_FIELD_POSITION + CHECK_NORMAL_FIELD_LENGTH + CHECK_FIELD_MARGIN;
92  
93      private static final String CHECK_DESCRIPTION_FIELD_PREFIX = "CheckDescription";
94      private static final float CHECK_DESCRIPTION_FIELD_POSITION = CHECK_DATE_FIELD_POSITION + CHECK_NORMAL_FIELD_LENGTH + CHECK_FIELD_MARGIN;
95      private static final float CHECK_DESCRIPTION_FIELD_LENGTH = 250;
96  
97      private static final String CHECK_AMOUNT_FIELD_PREFIX = "CheckAmount";
98      private static final float CHECK_AMOUNT_FIELD_POSITION = CHECK_DESCRIPTION_FIELD_POSITION + CHECK_DESCRIPTION_FIELD_LENGTH + CHECK_FIELD_MARGIN;
99  
100     private float _yPos;
101 
102 
103     /**
104      * This method determines if cover sheet printing is allowed by reviewing the CashReceiptDocumentRule to see if the 
105      * cover sheet is printable.
106      * 
107      * @param crDoc The document the cover sheet is being printed for.
108      * @return True if the cover sheet is printable, false otherwise.
109      * 
110      * @see org.kuali.ole.fp.document.service.CashReceiptCoverSheetService#isCoverSheetPrintingAllowed(org.kuali.ole.fp.document.CashReceiptDocument)
111      * @see org.kuali.ole.fp.document.validation.impl.CashReceiptDocumentRule#isCoverSheetPrintable(org.kuali.ole.fp.document.CashReceiptFamilyBase)
112      */
113     public boolean isCoverSheetPrintingAllowed(CashReceiptDocument crDoc) {
114         WorkflowDocument workflowDocument = crDoc.getDocumentHeader().getWorkflowDocument();
115         return !(workflowDocument.isCanceled() || workflowDocument.isInitiated() || workflowDocument.isDisapproved() || workflowDocument.isException() || workflowDocument.isDisapproved() || workflowDocument.isSaved());
116     }
117     
118     /**
119      * Generate a cover sheet for the <code>{@link CashReceiptDocument}</code>. An <code>{@link OutputStream}</code> is written
120      * to for the cover sheet.
121      * 
122      * @param document The cash receipt document the cover sheet is for.
123      * @param searchPath The directory path to the template to be used to generate the cover sheet.
124      * @param returnStream The output stream the cover sheet will be written to.
125      * @exception DocumentException Thrown if the document provided is invalid, including null.
126      * @exception IOException Thrown if there is a problem writing to the output stream.
127      * @see org.kuali.rice.kns.module.financial.service.CashReceiptCoverSheetServiceImpl#generateCoverSheet(
128      *      org.kuali.module.financial.documentCashReceiptDocument )
129      */
130     public void generateCoverSheet(CashReceiptDocument document, String searchPath, OutputStream returnStream) throws Exception {
131 
132         if (isCoverSheetPrintingAllowed(document)) {
133             ByteArrayOutputStream stamperStream = new ByteArrayOutputStream();
134 
135             stampPdfFormValues(document, searchPath, stamperStream);
136             
137             PdfReader reader = new PdfReader(stamperStream.toByteArray());
138             Document pdfDoc = new Document(reader.getPageSize(FRONT_PAGE));
139             PdfWriter writer = PdfWriter.getInstance(pdfDoc, returnStream);
140 
141             pdfDoc.open();
142             populateCheckDetail(document, writer, reader);
143             pdfDoc.close();
144             writer.close();
145         }
146     }
147 
148     /**
149      * Use iText <code>{@link PdfStamper}</code> to stamp information from <code>{@link CashReceiptDocument}</code> into field
150      * values on a PDF Form Template.
151      * 
152      * @param document The cash receipt document the values will be pulled from.
153      * @param searchPath The directory path of the template to be used to generate the cover sheet.
154      * @param returnStream The output stream the cover sheet will be written to.
155      */
156     protected void stampPdfFormValues(CashReceiptDocument document, String searchPath, OutputStream returnStream) throws Exception {
157         String templateName = CR_COVERSHEET_TEMPLATE_NM;
158 
159         try {
160             // populate form with document values
161             
162             //KFSMI-7303
163             //The PDF template is retrieve through web static URL rather than file path, so the File separator is unnecessary
164             final boolean isWebResourcePath = StringUtils.containsIgnoreCase(searchPath, "HTTP");
165             
166             //skip the File.separator if reference by web resource
167             PdfStamper stamper = new PdfStamper(new PdfReader(searchPath + (isWebResourcePath? "" : File.separator) + templateName), returnStream);
168             AcroFields populatedCoverSheet = stamper.getAcroFields();
169             
170             populatedCoverSheet.setField(DOCUMENT_NUMBER_FIELD, document.getDocumentNumber());
171             populatedCoverSheet.setField(INITIATOR_FIELD, document.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId());
172             populatedCoverSheet.setField(CREATED_DATE_FIELD, document.getDocumentHeader().getWorkflowDocument().getDateCreated().toString());
173             populatedCoverSheet.setField(AMOUNT_FIELD, document.getTotalDollarAmount().toString());
174             populatedCoverSheet.setField(ORG_DOC_NUMBER_FIELD, document.getDocumentHeader().getOrganizationDocumentNumber());
175             populatedCoverSheet.setField(CAMPUS_FIELD, document.getCampusLocationCode());
176             if (document.getDepositDate() != null) {
177                 // This value won't be set until the CR document is
178                 // deposited. A CR document is deposited only when it has
179                 // been associated with a Cash Management Document (CMD)
180                 // and with a Deposit within that CMD. And only when the
181                 // CMD is submitted and FINAL, will the CR documents
182                 // associated with it, be "deposited." So this value will
183                 // fill in at an arbitrarily later point in time. So your
184                 // code shouldn't expect it, but if it's there, then
185                 // display it.
186                 populatedCoverSheet.setField(DEPOSIT_DATE_FIELD, document.getDepositDate().toString());
187             }
188             populatedCoverSheet.setField(DESCRIPTION_FIELD, document.getDocumentHeader().getDocumentDescription());
189             populatedCoverSheet.setField(EXPLANATION_FIELD, document.getDocumentHeader().getExplanation());
190             populatedCoverSheet.setField(CHECKS_FIELD, document.getTotalCheckAmount().toString());
191             populatedCoverSheet.setField(CURRENCY_FIELD, document.getTotalCashAmount().toString());
192             populatedCoverSheet.setField(COIN_FIELD, document.getTotalCoinAmount().toString());
193             populatedCoverSheet.setField(CHANGE_OUT_FIELD, document.getTotalChangeAmount().toString());
194             
195             populatedCoverSheet.setField(TOTAL_RECONCILIATION_FIELD, document.getTotalDollarAmount().toString());
196 
197             stamper.setFormFlattening(true);
198             stamper.close();
199         }
200         catch (Exception e) {
201             LOG.error("Error creating coversheet for: " + document.getDocumentNumber() + ". ::" + e);
202             throw e;
203         }
204     }
205 
206     /**
207      * 
208      * This method writes the check number from the check provided to the PDF template.
209      * @param output The PDF output field the check number will be written to.
210      * @param check The check the check number will be retrieved from.
211      */
212     protected void writeCheckNumber(PdfContentByte output, Check check) {
213         writeCheckField(output, CHECK_NUMBER_FIELD_POSITION, check.getCheckNumber().toString());
214     }
215 
216     /**
217      * 
218      * This method writes the check date from the check provided to the PDF template.
219      * @param output The PDF output field the check date will be written to.
220      * @param check The check the check date will be retrieved from.
221      */
222     protected void writeCheckDate(PdfContentByte output, Check check) {
223         writeCheckField(output, CHECK_DATE_FIELD_POSITION, check.getCheckDate().toString());
224     }
225 
226     /**
227      * 
228      * This method writes the check description from the check provided to the PDF template.
229      * @param output The PDF output field the check description will be written to.
230      * @param check The check the check description will be retrieved from.
231      */
232     protected void writeCheckDescription(PdfContentByte output, Check check) {
233         writeCheckField(output, CHECK_DESCRIPTION_FIELD_POSITION, check.getDescription());
234     }
235 
236     /**
237      * 
238      * This method writes the check amount from the check provided to the PDF template.
239      * @param output The PDF output field the check amount will be written to.
240      * @param check The check the check amount will be retrieved from.
241      */
242     protected void writeCheckAmount(PdfContentByte output, Check check) {
243         writeCheckField(output, CHECK_AMOUNT_FIELD_POSITION, check.getAmount().toString());
244     }
245 
246     /**
247      * 
248      * This method writes out the value provided to the output provided and aligns the value outputted using the xPos float
249      * provided.
250      * @param output The content byte used to write out the field to the PDF template.
251      * @param xPos The x coordinate of the starting point on the document where the value will be written to.
252      * @param fieldValue The value to be written to the PDF cover sheet.
253      */
254     protected void writeCheckField(PdfContentByte output, float xPos, String fieldValue) {
255         output.beginText();
256         output.setTextMatrix(xPos, getCurrentRenderingYPosition());
257         output.newlineShowText(fieldValue);
258         output.endText();
259     }
260 
261     /**
262      * Read-only accessor for <code>{@link BaseFont}</code>. Used for creating the check detail information.  The font being 
263      * used is  Helvetica.
264      * 
265      * @return A BaseFont object used to identify what type of font is used on the cover sheet.
266      */
267     protected BaseFont getTextFont() throws DocumentException, IOException {
268         return BaseFont.createFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
269     }
270 
271     /**
272      * Defines a state of Y position for the text.
273      * 
274      * @param y The y coordinate to be set.
275      */
276     protected void setCurrentRenderingYPosition(float y) {
277         _yPos = y;
278     }
279 
280     /**
281      * Defines a state of Y position for the text.
282      * 
283      * @return The current y coordinate.
284      */
285     protected float getCurrentRenderingYPosition() {
286         return _yPos;
287     }
288 
289     /**
290      * Method responsible for producing Check Detail section of the cover sheet. Not all Cash Receipt documents have checks.
291      * 
292      * @param crDoc The CashReceipt document the cover sheet is being created for.
293      * @param writer The output writer used to write the check data to the PDF file.
294      * @param reader The input reader used to read data from the PDF file.
295      */
296     protected void populateCheckDetail(CashReceiptDocument crDoc, PdfWriter writer, PdfReader reader) throws Exception {
297         PdfContentByte content;
298         ModifiableInteger pageNumber;
299         int checkCount = 0;
300         int maxCheckCount = MAX_CHECKS_FIRST_PAGE;
301 
302         pageNumber = new ModifiableInteger(0);
303         content = startNewPage(writer, reader, pageNumber);
304 
305         for (Check current : crDoc.getChecks()) {
306             writeCheckNumber(content, current);
307             writeCheckDate(content, current);
308             writeCheckDescription(content, current);
309             writeCheckAmount(content, current);
310             setCurrentRenderingYPosition(getCurrentRenderingYPosition() - CHECK_FIELD_HEIGHT);
311 
312             checkCount++;
313 
314             if (checkCount > maxCheckCount) {
315                 checkCount = 0;
316                 maxCheckCount = MAX_CHECKS_NORMAL;
317                 content = startNewPage(writer, reader, pageNumber);
318             }
319         }
320     }
321 
322     /**
323      * Responsible for creating a new PDF page and workspace through <code>{@link PdfContentByte}</code> for direct writing to the
324      * PDF.
325      * 
326      * @param writer The PDF writer used to write to the new page with.
327      * @param reader The PDF reader used to read information from the PDF file.
328      * @param pageNumber The current number of pages in the PDF file, which will be incremented by one inside this method.
329      * 
330      * @return The PDFContentByte used to access the new PDF page.
331      * @exception DocumentException
332      * @exception IOException
333      */
334     protected PdfContentByte startNewPage(PdfWriter writer, PdfReader reader, ModifiableInteger pageNumber) throws DocumentException, IOException {
335         PdfContentByte retval;
336         PdfContentByte under;
337         Rectangle pageSize;
338         Document pdfDoc;
339         PdfImportedPage newPage;
340 
341         pageNumber.increment();
342         pageSize = reader.getPageSize(FRONT_PAGE);
343         retval = writer.getDirectContent();
344         // under = writer.getDirectContentUnder();
345 
346         if (pageNumber.getInt() > FRONT_PAGE) {
347             newPage = writer.getImportedPage(reader, CHECK_PAGE_NORMAL);
348             setCurrentRenderingYPosition(pageSize.top(TOP_MARGIN + CHECK_DETAIL_HEADING_HEIGHT));
349         }
350         else {
351             newPage = writer.getImportedPage(reader, FRONT_PAGE);
352             setCurrentRenderingYPosition(pageSize.top(TOP_FIRST_PAGE));
353         }
354 
355         pdfDoc = retval.getPdfDocument();
356         pdfDoc.newPage();
357         retval.addTemplate(newPage, 0, 0);
358         retval.setFontAndSize(getTextFont(), 8);
359 
360         return retval;
361     }
362 
363     /**
364      * Gets the dataDictionaryService attribute. 
365      * @return Returns the dataDictionaryService.
366      */
367     public DataDictionaryService getDataDictionaryService() {
368         return dataDictionaryService;
369     }
370 
371     /**
372      * Sets the dataDictionaryService attribute value.
373      * @param dataDictionaryService The dataDictionaryService to set.
374      */
375     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
376         this.dataDictionaryService = dataDictionaryService;
377     }
378 
379     /**
380      * Gets the documentHelperService attribute. 
381      * @return Returns the documentHelperService.
382      */
383     public DocumentHelperService getDocumentHelperService() {
384         return documentHelperService;
385     }
386 
387     /**
388      * Sets the documentHelperService attribute value.
389      * @param documentHelperService The documentHelperService to set.
390      */
391     public void setDocumentHelperService(DocumentHelperService documentHelperService) {
392         this.documentHelperService = documentHelperService;
393     }
394 
395 }
396 
397 
398 
399 /**
400  * Utility class used to replace an <code>{@link Integer}</code> because an integer cannot be modified once it has been
401  * instantiated.
402  */
403 class ModifiableInteger {
404     int _value;
405 
406     /**
407      * 
408      * Constructs a ModifiableInteger object.
409      * @param val The initial value of the object.
410      */
411     public ModifiableInteger(Integer val) {
412         this(val.intValue());
413     }
414 
415     /**
416      * 
417      * Constructs a ModifiableInteger object.
418      * @param val The initial value of the object.
419      */
420     public ModifiableInteger(int val) {
421         setInt(val);
422     }
423 
424     /**
425      * 
426      * This method sets the local attribute to the value given.
427      * @param val The int value to be set.
428      */
429     public void setInt(int val) {
430         _value = val;
431     }
432 
433     /**
434      * 
435      * This method retrieves the value of the object.
436      * @return The int value of this object.
437      */
438     public int getInt() {
439         return _value;
440     }
441 
442     /**
443      * 
444      * This method increments the value of this class by one.
445      * @return An instance of this class with the value incremented by one.
446      */
447     public ModifiableInteger increment() {
448         _value++;
449         return this;
450     }
451 
452     /**
453      * 
454      * This method increments the value of this class by the amount specified.
455      * @param inc The amount the class value should be incremented by.
456      * @return An instance of this class with the value incremented by the amount specified.
457      */
458     public ModifiableInteger increment(int inc) {
459         _value += inc;
460         return this;
461     }
462 
463     /**
464      * 
465      * This method decrements the value of this class by one.
466      * @return An instance of this class with the value decremented by one.
467      */
468     public ModifiableInteger decrement() {
469         _value--;
470         return this;
471     }
472 
473     /**
474      * 
475      * This method decrements the value of this class by the amount specified.
476      * @param dec The amount the class value should be decremented by.
477      * @return An instance of this class with the value decremented by the amount specified.
478      */
479     public ModifiableInteger decrement(int dec) {
480         _value -= dec;
481         return this;
482     }
483 
484     /**
485      * 
486      * This method converts the value of this class and returns it as an Integer object.
487      * @return The value of this class formatted as an Integer.
488      */
489     public Integer getInteger() {
490         return new Integer(_value);
491     }
492 
493     /**
494      * This method generates and returns a String representation of this class.
495      * @return A string representation of this object.
496      * 
497      * @see java.lang.Object#toString()
498      */
499     @Override
500     public String toString() {
501         return getInteger().toString();
502     }
503 }