View Javadoc
1   /*
2    * The Kuali Financial System, a comprehensive financial management system for higher education.
3    * 
4    * Copyright 2005-2014 The Kuali Foundation
5    * 
6    * This program is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU Affero General Public License as
8    * published by the Free Software Foundation, either version 3 of the
9    * License, or (at your option) any later version.
10   * 
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU Affero General Public License for more details.
15   * 
16   * You should have received a copy of the GNU Affero General Public License
17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  package org.kuali.kfs.sys.service.impl;
20  
21  import java.io.File;
22  import java.io.FileNotFoundException;
23  import java.io.PrintStream;
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.commons.lang.StringUtils;
31  import org.kuali.kfs.sys.KFSConstants;
32  import org.kuali.kfs.sys.Message;
33  import org.kuali.kfs.sys.batch.service.WrappingBatchService;
34  import org.kuali.kfs.sys.context.SpringContext;
35  import org.kuali.kfs.sys.report.BusinessObjectReportHelper;
36  import org.kuali.kfs.sys.service.ReportWriterService;
37  import org.kuali.rice.core.api.datetime.DateTimeService;
38  import org.kuali.rice.krad.bo.BusinessObject;
39  import org.kuali.rice.krad.util.ObjectUtils;
40  
41  /**
42   * Text output implementation of <code>ReportWriterService</code> interface. If you are a developer attempting to add a new business
43   * object for error report writing, take a look at the Spring definitions for BusinessObjectReportHelper.<br>
44   * This class CANNOT be used by 2 processes simultaneously. It is for very specific batch processes that should not run at the same
45   * time, and initialize and destroy must be called and the beginning and end of each process that uses it.
46   *
47   * @see org.kuali.kfs.sys.report.BusinessObjectReportHelper
48   */
49  public class ReportWriterTextServiceImpl implements ReportWriterService, WrappingBatchService {
50      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ReportWriterTextServiceImpl.class);
51  
52      // Changing the initial line number would only affect that a page break occurs early. It does not actually print in the
53      // middle of the page. Hence changing this has little use.
54      protected static final int INITIAL_LINE_NUMBER = 0;
55  
56      protected String filePath;
57      protected String fileNamePrefix;
58      protected String fileNameSuffix;
59      protected String title;
60      protected int pageWidth;
61      protected int pageLength;
62      protected int initialPageNumber;
63      protected String errorSubTitle;
64      protected String statisticsLabel;
65      protected String statisticsLeftPadding;
66      private String parametersLabel;
67      private String parametersLeftPadding;
68      protected String pageLabel;
69      protected String newLineCharacter;
70      protected DateTimeService dateTimeService;
71      protected boolean aggregationModeOn;
72  
73      /**
74       * A map of BO classes to {@link BusinessObjectReportHelper} bean names, to configure which BO's will be rendered by which
75       * BusinessObjectReportHelper. This property should be configured via the spring bean definition
76       */
77      protected Map<Class<? extends BusinessObject>, String> classToBusinessObjectReportHelperBeanNames;
78  
79      // Local caching field to speed up the selection of formatting BusinessObjectReportHelper to use per configuration in Spring
80      protected Map<Class<? extends BusinessObject>, BusinessObjectReportHelper> businessObjectReportHelpers;
81  
82      protected PrintStream printStream;
83      protected int page;
84      protected int line = INITIAL_LINE_NUMBER;
85      protected String errorFormat;
86  
87      // Ensures that the statistics header isn't written multiple times. Does not check that a user doesn't write other stuff into
88      // the statistics
89      // section. A developer is responsible for ensuring that themselves
90      protected boolean modeStatistics = false;
91  
92      // Ensures that the parameters header isn't written multiple times. Does not check that a user doesn't write other stuff into
93      // the parameters
94      // section. A developer is responsible for ensuring that themselves
95      protected boolean modeParameters = false;
96  
97      // So that writeError knows when to writeErrorHeader
98      protected boolean newPage = true;
99  
100     // For printing new headers when the BO is changed
101     protected Class<? extends BusinessObject> businessObjectClass;
102 
103     public ReportWriterTextServiceImpl() {
104         // TODO Auto-generated constructor stub
105     }
106 
107     /**
108      * @see org.kuali.kfs.sys.batch.service.WrappingBatchService#initialize()
109      */
110     public void initialize() {
111         try {
112             printStream = new PrintStream(generateFullFilePath());
113         }
114         catch (FileNotFoundException e) {
115             throw new RuntimeException(e);
116         }
117 
118         page = initialPageNumber;
119         initializeBusinessObjectReportHelpers();
120         // Initial header
121         this.writeHeader(title);
122     }
123 
124     protected void initializeBusinessObjectReportHelpers() {
125         businessObjectReportHelpers = new HashMap<Class<? extends BusinessObject>, BusinessObjectReportHelper>();
126         if (classToBusinessObjectReportHelperBeanNames != null) {
127             for (Class<? extends BusinessObject> clazz : classToBusinessObjectReportHelperBeanNames.keySet()) {
128                 String businessObjectReportHelperBeanName = classToBusinessObjectReportHelperBeanNames.get(clazz);
129                 BusinessObjectReportHelper reportHelper = (BusinessObjectReportHelper) SpringContext.getService(businessObjectReportHelperBeanName);
130                 if (ObjectUtils.isNull(reportHelper)) {
131                     LOG.error("Cannot find BusinessObjectReportHelper implementation for class: " + clazz.getName() + " bean name: " + businessObjectReportHelperBeanName);
132                     throw new RuntimeException("Cannot find BusinessObjectReportHelper implementation for class: " + clazz.getName() + " bean name: " + businessObjectReportHelperBeanName);
133                 }
134                 businessObjectReportHelpers.put(clazz, reportHelper);
135             }
136         }
137     }
138 
139     protected String generateFullFilePath() {
140         if (aggregationModeOn) {
141             return filePath + File.separator + this.fileNamePrefix + fileNameSuffix;
142         }
143         else {
144             return filePath + File.separator + this.fileNamePrefix + dateTimeService.toDateTimeStringForFilename(dateTimeService.getCurrentDate()) + fileNameSuffix;
145         }
146     }
147 
148     /**
149      * @see org.kuali.kfs.sys.batch.service.WrappingBatchService#destroy()
150      */
151     public void destroy() {
152         if(printStream != null) {
153             printStream.close();
154             printStream = null;
155         }
156 
157         // reset variables that track state
158         page = initialPageNumber;
159         line = INITIAL_LINE_NUMBER;
160         modeStatistics = false;
161         modeParameters = false;
162         newPage = true;
163         businessObjectClass = null;
164     }
165 
166     /**
167      * @see org.kuali.kfs.sys.service.ReportWriterService#writeSubTitle(java.lang.String)
168      */
169     public void writeSubTitle(String message) {
170         if (message.length() > pageWidth) {
171             LOG.warn("sub title to be written exceeds pageWidth. Printing anyway.");
172             this.writeFormattedMessageLine(message);
173         }
174         else {
175             int padding = (pageWidth - message.length()) / 2;
176             this.writeFormattedMessageLine("%" + (padding + message.length()) + "s", message);
177         }
178     }
179 
180     /**
181      * @see org.kuali.kfs.sys.service.ReportWriterService#writeError(java.lang.Class, org.kuali.kfs.sys.Message)
182      */
183     public void writeError(BusinessObject businessObject, Message message) {
184         this.writeError(businessObject, message, true);
185     }
186 
187     /**
188      * @param printBusinessObjectValues indicates whether the bo values should be printed before the message
189      * @see org.kuali.kfs.sys.service.ReportWriterService#writeError(java.lang.Class, org.kuali.kfs.sys.Message)
190      */
191     public void writeError(BusinessObject businessObject, Message message, boolean printBusinessObjectValues) {
192         // Check if we need to write a new table header. We do this if it hasn't been written before or if the businessObject
193         // changed
194         if (newPage || businessObjectClass == null || !businessObjectClass.getName().equals(businessObject.getClass().getName())) {
195             if (businessObjectClass == null) {
196                 // If we didn't write the header before, write it with a subTitle
197                 this.writeSubTitle(errorSubTitle);
198             }
199             else if (!businessObjectClass.getName().equals(businessObject.getClass().getName())) {
200                 // If it changed push a newline in for neater formatting
201                 this.writeNewLines(1);
202             }
203 
204             this.writeErrorHeader(businessObject);
205             newPage = false;
206             businessObjectClass = businessObject.getClass();
207         }
208 
209         // Get business object formatter that will be used
210         BusinessObjectReportHelper businessObjectReportHelper = getBusinessObjectReportHelper(businessObject);
211 
212         // Print the values of the businessObject per formatting determined by writeErrorHeader
213         List<Object> formatterArgs = new ArrayList<Object>();
214         if (printBusinessObjectValues) {
215             formatterArgs.addAll(businessObjectReportHelper.getValues(businessObject));
216         }
217         else {
218             formatterArgs.addAll(businessObjectReportHelper.getBlankValues(businessObject));
219         }
220 
221         // write rest of message on new line(s) if it was cut off
222         int maxMessageLength = Integer.parseInt(StringUtils.substringBefore(StringUtils.substringAfterLast(errorFormat, "%-"), "s"));
223         String messageToPrint = message.getMessage();
224 
225         boolean firstMessageLine = true;
226         while (messageToPrint.length() > 0 && StringUtils.isNotBlank(messageToPrint)) {
227             if (!firstMessageLine) {
228                 formatterArgs = new ArrayList<Object>();
229                 formatterArgs.addAll(businessObjectReportHelper.getBlankValues(businessObject));
230             }
231             else {
232                 firstMessageLine = false;
233             }
234 
235             messageToPrint =  StringUtils.trim(messageToPrint);
236             String messageLine = messageToPrint;
237             if (messageLine.length() > maxMessageLength) {
238                 messageLine = StringUtils.substring(messageLine, 0, maxMessageLength);
239                 if (StringUtils.contains(messageLine, " ")) {
240                     messageLine = StringUtils.substringBeforeLast(messageLine, " ");
241                 }
242             }
243 
244             formatterArgs.add(new Message(messageLine, message.getType()));
245             this.writeFormattedMessageLine(errorFormat, formatterArgs.toArray());
246 
247             messageToPrint = StringUtils.removeStart(messageToPrint, messageLine);
248         }
249     }
250 
251     /**
252      * @see org.kuali.kfs.sys.service.ReportWriterService#writeError(java.lang.Class, java.util.List)
253      */
254     public void writeError(BusinessObject businessObject, List<Message> messages) {
255         int i = 0;
256         for (Iterator<Message> messagesIter = messages.iterator(); messagesIter.hasNext();) {
257             Message message = messagesIter.next();
258 
259             if (i == 0) {
260                 // First one has its values written
261                 this.writeError(businessObject, message, true);
262             }
263             else {
264                 // Any consecutive one only has message written
265                 this.writeError(businessObject, message, false);
266             }
267 
268             i++;
269         }
270     }
271 
272     /**
273      * @see org.kuali.kfs.sys.service.ReportWriterService#writeNewLines(int)
274      */
275     public void writeNewLines(int lines) {
276         for (int i = 0; i < lines; i++) {
277             this.writeFormattedMessageLine("");
278         }
279     }
280 
281     /**
282      * @see org.kuali.kfs.sys.service.ReportWriterService#writeStatisticLine(java.lang.String, java.lang.Object[])
283      */
284     public void writeStatisticLine(String message, Object... args) {
285         // Statistics header is only written if it hasn't been written before
286         if (!modeStatistics) {
287             this.modeStatistics = true;
288 
289             // If nothing has been written to the report we don't want to page break
290             if (!(page == initialPageNumber && line == INITIAL_LINE_NUMBER + 2)) {
291                 this.pageBreak();
292             }
293 
294             this.writeFormattedMessageLine("*********************************************************************************************************************************");
295             this.writeFormattedMessageLine("*********************************************************************************************************************************");
296             this.writeFormattedMessageLine("*******************" + statisticsLabel + "*******************");
297             this.writeFormattedMessageLine("*********************************************************************************************************************************");
298             this.writeFormattedMessageLine("*********************************************************************************************************************************");
299         }
300 
301         this.writeFormattedMessageLine(statisticsLeftPadding + message, args);
302     }
303 
304     /**
305      * @see org.kuali.kfs.sys.service.ReportWriterService#writeParameterLine(java.lang.String, java.lang.Object[])
306      */
307     public void writeParameterLine(String message, Object... args) {
308         // Statistics header is only written if it hasn't been written before
309         if (!modeParameters) {
310             this.modeParameters = true;
311 
312             // If nothing has been written to the report we don't want to page break
313             if (!(page == initialPageNumber && line == INITIAL_LINE_NUMBER + 2)) {
314                 this.pageBreak();
315             }
316 
317             this.writeFormattedMessageLine("*********************************************************************************************************************************");
318             this.writeFormattedMessageLine("*********************************************************************************************************************************");
319             this.writeFormattedMessageLine("*******************" + getParametersLabel() + "*******************");
320             this.writeFormattedMessageLine("*********************************************************************************************************************************");
321             this.writeFormattedMessageLine("*********************************************************************************************************************************");
322         }
323 
324         this.writeFormattedMessageLine(getParametersLeftPadding() + message, args);
325     }
326 
327     /**
328      * @see org.kuali.kfs.sys.service.ReportWriterService#writeFormattedMessageLine(java.lang.String, java.lang.Object[])
329      */
330     public void writeFormattedMessageLine(String format, Object... args) {
331         if (format.indexOf("% s") > -1) {
332             LOG.warn("Cannot properly format: "+format);
333         }
334         else {
335             Object[] escapedArgs = escapeArguments(args);
336             if (LOG.isDebugEnabled()) {
337                 LOG.debug("writeFormattedMessageLine, format: "+format);
338             }
339 
340             String message = null;
341 
342             if (escapedArgs.length > 0) {
343                 message = String.format(format + newLineCharacter, escapedArgs);
344             } else {
345                 message = format+newLineCharacter;
346             }
347 
348             // Log we are writing out of bounds. Would be nice to show message here but not so sure if it's wise to dump that data into
349             // logs
350             if (message.length() > pageWidth) {
351                 if (LOG.isDebugEnabled()) {
352                     LOG.debug("message is out of bounds writing anyway");
353                 }
354             }
355 
356             printStream.print(message);
357             printStream.flush();
358 
359             line++;
360             if (line >= pageLength) {
361                 this.pageBreak();
362             }
363         }
364     }
365 
366     /**
367      * Determines if all formatting on the given String is escaped - ie, that it has no formatting
368      * @param format the format to test
369      * @return true if the String is without formatting, false otherwise
370      */
371     protected boolean allFormattingEscaped(String format) {
372         int currPoint = 0;
373         int currIndex = format.indexOf('%', currPoint);
374         while (currIndex > -1) {
375             char nextChar = format.charAt(currIndex+1);
376             if (nextChar != '%') {
377                 return false;
378             }
379             currPoint = currIndex + 2;
380         }
381         return true;
382     }
383 
384     /**
385      * @see org.kuali.kfs.sys.service.ReportWriterService#pageBreak()
386      */
387     public void pageBreak() {
388         // Intentionally not using writeFormattedMessageLine here since it would loop trying to page break ;)
389         // 12 represents the ASCII Form Feed character
390         printStream.printf("%c" + newLineCharacter, 12);
391         page++;
392         line = INITIAL_LINE_NUMBER;
393         newPage = true;
394 
395         this.writeHeader(title);
396     }
397 
398     /**
399      * Helper method to write a header for placement at top of new page
400      *
401      * @param title that should be printed on this header
402      */
403     protected void writeHeader(String title) {
404         String headerText = String.format("%1$tY-%1$tm-%1$td %1$tH:%1$tM", dateTimeService.getCurrentDate());
405         int reportTitlePadding = pageWidth / 2 - headerText.length() - title.length() / 2;
406         headerText = String.format("%s%" + (reportTitlePadding + title.length()) + "s%" + reportTitlePadding + "s", headerText, title, "");
407 
408         if (aggregationModeOn) {
409             this.writeFormattedMessageLine("%s%s%s", headerText, pageLabel, KFSConstants.REPORT_WRITER_SERVICE_PAGE_NUMBER_PLACEHOLDER);
410         }
411         else {
412             this.writeFormattedMessageLine("%s%s%,9d", headerText, pageLabel, page);
413         }
414         this.writeNewLines(1);
415     }
416 
417     /**
418      * Helper method to write the error header
419      *
420      * @param businessObject to print header for
421      */
422     protected void writeErrorHeader(BusinessObject businessObject) {
423         BusinessObjectReportHelper businessObjectReportHelper = getBusinessObjectReportHelper(businessObject);
424         List<String> errorHeader = businessObjectReportHelper.getTableHeader(pageWidth);
425 
426         // If we are at end of page and don't have space for table header, go ahead and page break
427         if (errorHeader.size() + line >= pageLength) {
428             this.pageBreak();
429         }
430 
431         // Print the header one by one. Note the last element is the formatter. So capture that seperately
432         for (Iterator<String> headers = errorHeader.iterator(); headers.hasNext();) {
433             String header = headers.next();
434 
435             if (headers.hasNext()) {
436                 this.writeFormattedMessageLine("%s", header);
437             }
438             else {
439                 errorFormat = header;
440             }
441         }
442     }
443 
444     /**
445      * @see org.kuali.kfs.sys.service.ReportWriterService#writeTableHeader(org.kuali.rice.krad.bo.BusinessObject)
446      */
447     public void writeTableHeader(BusinessObject businessObject) {
448         BusinessObjectReportHelper businessObjectReportHelper = getBusinessObjectReportHelper(businessObject);
449 
450         Map<String, String> tableDefinition = businessObjectReportHelper.getTableDefinition();
451         String tableHeaderFormat = tableDefinition.get(KFSConstants.ReportConstants.TABLE_HEADER_LINE_KEY);
452 
453         String[] headerLines = this.getMultipleFormattedMessageLines(tableHeaderFormat, new Object());
454         this.writeMultipleFormattedMessageLines(headerLines);
455     }
456 
457     /**
458      * Writes out the table header, based on a business object class
459      * @param businessObjectClass the class to write the header out for
460      */
461     public void writeTableHeader(Class<? extends BusinessObject> businessObjectClass) {
462         BusinessObjectReportHelper businessObjectReportHelper = getBusinessObjectReportHelper(businessObjectClass);
463 
464         Map<String, String> tableDefinition = businessObjectReportHelper.getTableDefinition();
465         String tableHeaderFormat = tableDefinition.get(KFSConstants.ReportConstants.TABLE_HEADER_LINE_KEY);
466 
467         String[] headerLines = this.getMultipleFormattedMessageLines(tableHeaderFormat, new Object());
468         this.writeMultipleFormattedMessageLines(headerLines);
469     }
470 
471     /**
472      * @see org.kuali.kfs.sys.service.ReportWriterService#writeTableRow(org.kuali.rice.krad.bo.BusinessObject)
473      */
474     public void writeTableRowSeparationLine(BusinessObject businessObject) {
475         BusinessObjectReportHelper businessObjectReportHelper = getBusinessObjectReportHelper(businessObject);
476         Map<String, String> tableDefinition = businessObjectReportHelper.getTableDefinition();
477 
478         String separationLine = tableDefinition.get(KFSConstants.ReportConstants.SEPARATOR_LINE_KEY);
479         this.writeFormattedMessageLine(separationLine);
480     }
481 
482     /**
483      * @see org.kuali.kfs.sys.service.ReportWriterService#writeTableRow(org.kuali.rice.krad.bo.BusinessObject)
484      */
485     public void writeTableRow(BusinessObject businessObject) {
486         BusinessObjectReportHelper businessObjectReportHelper = getBusinessObjectReportHelper(businessObject);
487         Map<String, String> tableDefinition = businessObjectReportHelper.getTableDefinition();
488 
489         String tableCellFormat = tableDefinition.get(KFSConstants.ReportConstants.TABLE_CELL_FORMAT_KEY);
490         List<String> tableCellValues = businessObjectReportHelper.getTableCellValuesPaddingWithEmptyCell(businessObject, false);
491 
492         String[] rowMessageLines = this.getMultipleFormattedMessageLines(tableCellFormat, tableCellValues.toArray());
493         this.writeMultipleFormattedMessageLines(rowMessageLines);
494     }
495 
496     /**
497      * @see org.kuali.kfs.sys.service.ReportWriterService#writeTableRowWithColspan(org.kuali.rice.krad.bo.BusinessObject)
498      */
499     public void writeTableRowWithColspan(BusinessObject businessObject) {
500         BusinessObjectReportHelper businessObjectReportHelper = getBusinessObjectReportHelper(businessObject);
501         Map<String, String> tableDefinition = businessObjectReportHelper.getTableDefinition();
502 
503         String tableCellFormat = businessObjectReportHelper.getTableCellFormat(true, true, StringUtils.EMPTY);
504         List<String> tableCellValues = businessObjectReportHelper.getTableCellValuesPaddingWithEmptyCell(businessObject, true);
505 
506         String[] rowMessageLines = this.getMultipleFormattedMessageLines(tableCellFormat, tableCellValues.toArray());
507         this.writeMultipleFormattedMessageLines(rowMessageLines);
508     }
509 
510     /**
511      * @see org.kuali.kfs.sys.service.ReportWriterService#writeTable(java.util.List, boolean, boolean)
512      */
513     public void writeTable(List<? extends BusinessObject> businessObjects, boolean isHeaderRepeatedInNewPage, boolean isRowBreakAcrossPageAllowed) {
514         if (ObjectUtils.isNull(businessObjects) || businessObjects.isEmpty()) {
515             return;
516         }
517 
518         BusinessObject firstBusinessObject = businessObjects.get(0);
519         this.writeTableHeader(firstBusinessObject);
520 
521         BusinessObjectReportHelper businessObjectReportHelper = getBusinessObjectReportHelper(businessObjects.get(0));
522         Map<String, String> tableDefinition = businessObjectReportHelper.getTableDefinition();
523         String tableHeaderFormat = tableDefinition.get(KFSConstants.ReportConstants.TABLE_HEADER_LINE_KEY);
524         String[] headerLines = this.getMultipleFormattedMessageLines(tableHeaderFormat, new Object());
525 
526         String tableCellFormat = tableDefinition.get(KFSConstants.ReportConstants.TABLE_CELL_FORMAT_KEY);
527 
528         for (BusinessObject businessObject : businessObjects) {
529 
530             List<String> tableCellValues = businessObjectReportHelper.getTableCellValuesPaddingWithEmptyCell(businessObject, false);
531             String[] messageLines = this.getMultipleFormattedMessageLines(tableCellFormat, tableCellValues.toArray());
532 
533             boolean hasEnoughLinesInPage = messageLines.length <= (this.pageLength - this.line);
534             if (!hasEnoughLinesInPage && !isRowBreakAcrossPageAllowed) {
535                 this.pageBreak();
536 
537                 if (isHeaderRepeatedInNewPage) {
538                     this.writeTableHeader(firstBusinessObject);
539                 }
540             }
541 
542             this.writeMultipleFormattedMessageLines(messageLines, headerLines, isRowBreakAcrossPageAllowed);
543         }
544 
545     }
546 
547     /**
548      * get the business report helper for the given business object
549      *
550      * @param businessObject the given business object
551      * @return the business report helper for the given business object
552      */
553     public BusinessObjectReportHelper getBusinessObjectReportHelper(BusinessObject businessObject) {
554         if (LOG.isDebugEnabled()) {
555             if (businessObject == null) {
556                 LOG.debug("reporting "+filePath+" but can't because null business object sent in");
557             } else if (businessObjectReportHelpers == null) {
558                 LOG.debug("Logging "+businessObject+" in report "+filePath+" but businessObjectReportHelpers are null");
559             }
560         }
561         BusinessObjectReportHelper businessObjectReportHelper = this.businessObjectReportHelpers.get(businessObject.getClass());
562         if (ObjectUtils.isNull(businessObjectReportHelper)) {
563             throw new RuntimeException(businessObject.getClass().toString() + " is not handled");
564         }
565 
566         return businessObjectReportHelper;
567     }
568 
569     /**
570      * get the business report helper for the given business object
571      *
572      * @param businessObject the given business object
573      * @return the business report helper for the given business object
574      */
575     public BusinessObjectReportHelper getBusinessObjectReportHelper(Class<? extends BusinessObject> businessObjectClass) {
576         BusinessObjectReportHelper businessObjectReportHelper = this.businessObjectReportHelpers.get(businessObjectClass);
577         if (ObjectUtils.isNull(businessObjectReportHelper)) {
578             throw new RuntimeException(businessObjectClass.getName() + " is not handled");
579         }
580 
581         return businessObjectReportHelper;
582     }
583 
584     /**
585      * write the given information as multiple lines if it contains more than one line breaks
586      *
587      * @param format the given text format definition
588      * @param messageLines the given information being written out
589      * @param headerLinesInNewPage the given header lines being written in the begin of a new page
590      */
591     protected void writeMultipleFormattedMessageLines(String[] messageLines, String[] headerLinesInNewPage, boolean isRowBreakAcrossPageAllowed) {
592         int currentPageNumber = this.page;
593 
594         for (String line : messageLines) {
595             boolean hasEnoughLinesInPage = messageLines.length <= (this.pageLength - this.line);
596             if (!hasEnoughLinesInPage && !isRowBreakAcrossPageAllowed) {
597                 this.pageBreak();
598             }
599 
600             if (currentPageNumber < this.page && ObjectUtils.isNotNull(headerLinesInNewPage)) {
601                 currentPageNumber = this.page;
602 
603                 for (String headerLine : headerLinesInNewPage) {
604                     this.writeFormattedMessageLine(headerLine);
605                 }
606             }
607 
608             this.writeFormattedMessageLine(line);
609         }
610     }
611 
612     /**
613      * write the given information as multiple lines if it contains more than one line breaks
614      *
615      * @param format the given text format definition
616      * @param args the given information being written out
617      */
618     public void writeMultipleFormattedMessageLines(String[] messageLines) {
619         this.writeMultipleFormattedMessageLines(messageLines, null, false);
620     }
621 
622     public void writeMultipleFormattedMessageLines(String format, Object... args) {
623         Object[] escapedArgs = escapeArguments(args);
624         String[] messageLines = getMultipleFormattedMessageLines(format, escapedArgs);
625         writeMultipleFormattedMessageLines(messageLines);
626     }
627 
628     /**
629      * This method...
630      *
631      * @param format
632      * @param args
633      * @return
634      */
635     public String[] getMultipleFormattedMessageLines(String format, Object... args) {
636         Object[] escapedArgs = escapeArguments(args);
637         String message = String.format(format, escapedArgs);
638         return StringUtils.split(message, newLineCharacter);
639     }
640 
641     /**
642      * Iterates through array and escapes special formatting characters
643      *
644      * @param args Object array to process
645      * @return Object array with String members escaped
646      */
647     protected Object[] escapeArguments(Object... args) {
648         Object[] escapedArgs = new Object[args.length];
649         for (int i = 0; i < args.length; i++) {
650             Object arg = args[i];
651             if (arg == null) {
652                 args[i] = "";
653             } else if (arg != null && arg instanceof String) {
654                 String escapedArg = escapeFormatCharacters((String)arg);
655                 escapedArgs[i] = escapedArg;
656             }
657             else {
658                 escapedArgs[i] = arg;
659             }
660         }
661 
662         return escapedArgs;
663     }
664 
665     /**
666      * Escapes characters in a string that have special meaning for formatting
667      *
668      * @param replacementString string to escape
669      * @return string with format characters escaped
670      * @see KFSConstants.ReportConstants.FORMAT_ESCAPE_CHARACTERS
671      */
672     protected String escapeFormatCharacters(String replacementString) {
673         String escapedString = replacementString;
674         for (int i = 0; i < KFSConstants.ReportConstants.FORMAT_ESCAPE_CHARACTERS.length; i++) {
675             String characterToEscape = KFSConstants.ReportConstants.FORMAT_ESCAPE_CHARACTERS[i];
676             escapedString = StringUtils.replace(escapedString, characterToEscape, characterToEscape + characterToEscape);
677         }
678 
679         return escapedString;
680     }
681 
682     /**
683      * Sets the filePath
684      *
685      * @param filePath The filePath to set.
686      */
687     public void setFilePath(String filePath) {
688         this.filePath = filePath;
689     }
690 
691     /**
692      * Sets the fileNamePrefix
693      *
694      * @param fileNamePrefix The fileNamePrefix to set.
695      */
696     public void setFileNamePrefix(String fileNamePrefix) {
697         this.fileNamePrefix = fileNamePrefix;
698     }
699 
700     /**
701      * Sets the fileNameSuffix
702      *
703      * @param fileNameSuffix The fileNameSuffix to set.
704      */
705     public void setFileNameSuffix(String fileNameSuffix) {
706         this.fileNameSuffix = fileNameSuffix;
707     }
708 
709     /**
710      * Sets the title
711      *
712      * @param title The title to set.
713      */
714     public void setTitle(String title) {
715         this.title = title;
716     }
717 
718     /**
719      * Sets the pageWidth
720      *
721      * @param pageWidth The pageWidth to set.
722      */
723     public void setPageWidth(int pageWidth) {
724         this.pageWidth = pageWidth;
725     }
726 
727     /**
728      * Sets the pageLength
729      *
730      * @param pageLength The pageLength to set.
731      */
732     public void setPageLength(int pageLength) {
733         this.pageLength = pageLength;
734     }
735 
736     /**
737      * Sets the initialPageNumber
738      *
739      * @param initialPageNumber The initialPageNumber to set.
740      */
741     public void setInitialPageNumber(int initialPageNumber) {
742         this.initialPageNumber = initialPageNumber;
743     }
744 
745     /**
746      * Sets the errorSubTitle
747      *
748      * @param errorSubTitle The errorSubTitle to set.
749      */
750     public void setErrorSubTitle(String errorSubTitle) {
751         this.errorSubTitle = errorSubTitle;
752     }
753 
754     /**
755      * Sets the statisticsLabel
756      *
757      * @param statisticsLabel The statisticsLabel to set.
758      */
759     public void setStatisticsLabel(String statisticsLabel) {
760         this.statisticsLabel = statisticsLabel;
761     }
762 
763     /**
764      * Sets the statisticsLeftPadding
765      *
766      * @param statisticsLeftPadding The statisticsLeftPadding to set.
767      */
768     public void setStatisticsLeftPadding(String statisticsLeftPadding) {
769         this.statisticsLeftPadding = statisticsLeftPadding;
770     }
771 
772     /**
773      * Sets the pageLabel
774      *
775      * @param pageLabel The pageLabel to set.
776      */
777     public void setPageLabel(String pageLabel) {
778         this.pageLabel = pageLabel;
779     }
780 
781     /**
782      * Sets the newLineCharacter
783      *
784      * @param newLineCharacter The newLineCharacter to set.
785      */
786     public void setNewLineCharacter(String newLineCharacter) {
787         this.newLineCharacter = newLineCharacter;
788     }
789 
790     /**
791      * Sets the DateTimeService
792      *
793      * @param dateTimeService The DateTimeService to set.
794      */
795     public void setDateTimeService(DateTimeService dateTimeService) {
796         this.dateTimeService = dateTimeService;
797     }
798 
799     /**
800      * Sets a map of BO classes to {@link BusinessObjectReportHelper} bean names, to configure which BO's will be rendered by which
801      * BusinessObjectReportHelper. This property should be configured via the spring bean definition
802      *
803      * @param classToBusinessObjectReportHelperBeanNames The classToBusinessObjectReportHelperBeanNames to set.
804      */
805     public void setClassToBusinessObjectReportHelperBeanNames(Map<Class<? extends BusinessObject>, String> classToBusinessObjectReportHelperBeanNames) {
806         this.classToBusinessObjectReportHelperBeanNames = classToBusinessObjectReportHelperBeanNames;
807     }
808 
809     /**
810      * Gets the parametersLabel attribute.
811      * @return Returns the parametersLabel.
812      */
813     public String getParametersLabel() {
814         return parametersLabel;
815     }
816 
817     /**
818      * Sets the parametersLabel attribute value.
819      * @param parametersLabel The parametersLabel to set.
820      */
821     public void setParametersLabel(String parametersLabel) {
822         this.parametersLabel = parametersLabel;
823     }
824 
825     /**
826      * Gets the parametersLeftPadding attribute.
827      * @return Returns the parametersLeftPadding.
828      */
829     public String getParametersLeftPadding() {
830         return parametersLeftPadding;
831     }
832 
833     /**
834      * Sets the parametersLeftPadding attribute value.
835      * @param parametersLeftPadding The parametersLeftPadding to set.
836      */
837     public void setParametersLeftPadding(String parametersLeftPadding) {
838         this.parametersLeftPadding = parametersLeftPadding;
839     }
840 
841     /**
842      * Gets the aggregationModeOn attribute.
843      * @return Returns the aggregationModeOn.
844      */
845     public boolean isAggregationModeOn() {
846         return aggregationModeOn;
847     }
848 
849     /**
850      * Sets the aggregationModeOn attribute value.
851      * @param aggregationModeOn The aggregationModeOn to set.
852      */
853     public void setAggregationModeOn(boolean aggregationModeOn) {
854         this.aggregationModeOn = aggregationModeOn;
855     }
856 
857 
858 }