1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
43
44
45
46
47
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
53
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
75
76
77 protected Map<Class<? extends BusinessObject>, String> classToBusinessObjectReportHelperBeanNames;
78
79
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
88
89
90 protected boolean modeStatistics = false;
91
92
93
94
95 protected boolean modeParameters = false;
96
97
98 protected boolean newPage = true;
99
100
101 protected Class<? extends BusinessObject> businessObjectClass;
102
103 public ReportWriterTextServiceImpl() {
104
105 }
106
107
108
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
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
150
151 public void destroy() {
152 if(printStream != null) {
153 printStream.close();
154 printStream = null;
155 }
156
157
158 page = initialPageNumber;
159 line = INITIAL_LINE_NUMBER;
160 modeStatistics = false;
161 modeParameters = false;
162 newPage = true;
163 businessObjectClass = null;
164 }
165
166
167
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
182
183 public void writeError(BusinessObject businessObject, Message message) {
184 this.writeError(businessObject, message, true);
185 }
186
187
188
189
190
191 public void writeError(BusinessObject businessObject, Message message, boolean printBusinessObjectValues) {
192
193
194 if (newPage || businessObjectClass == null || !businessObjectClass.getName().equals(businessObject.getClass().getName())) {
195 if (businessObjectClass == null) {
196
197 this.writeSubTitle(errorSubTitle);
198 }
199 else if (!businessObjectClass.getName().equals(businessObject.getClass().getName())) {
200
201 this.writeNewLines(1);
202 }
203
204 this.writeErrorHeader(businessObject);
205 newPage = false;
206 businessObjectClass = businessObject.getClass();
207 }
208
209
210 BusinessObjectReportHelper businessObjectReportHelper = getBusinessObjectReportHelper(businessObject);
211
212
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
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
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
261 this.writeError(businessObject, message, true);
262 }
263 else {
264
265 this.writeError(businessObject, message, false);
266 }
267
268 i++;
269 }
270 }
271
272
273
274
275 public void writeNewLines(int lines) {
276 for (int i = 0; i < lines; i++) {
277 this.writeFormattedMessageLine("");
278 }
279 }
280
281
282
283
284 public void writeStatisticLine(String message, Object... args) {
285
286 if (!modeStatistics) {
287 this.modeStatistics = true;
288
289
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
306
307 public void writeParameterLine(String message, Object... args) {
308
309 if (!modeParameters) {
310 this.modeParameters = true;
311
312
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
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
349
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
368
369
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
386
387 public void pageBreak() {
388
389
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
400
401
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
419
420
421
422 protected void writeErrorHeader(BusinessObject businessObject) {
423 BusinessObjectReportHelper businessObjectReportHelper = getBusinessObjectReportHelper(businessObject);
424 List<String> errorHeader = businessObjectReportHelper.getTableHeader(pageWidth);
425
426
427 if (errorHeader.size() + line >= pageLength) {
428 this.pageBreak();
429 }
430
431
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
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
459
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
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
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
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
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
549
550
551
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
571
572
573
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
586
587
588
589
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
614
615
616
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
630
631
632
633
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
643
644
645
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
667
668
669
670
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
684
685
686
687 public void setFilePath(String filePath) {
688 this.filePath = filePath;
689 }
690
691
692
693
694
695
696 public void setFileNamePrefix(String fileNamePrefix) {
697 this.fileNamePrefix = fileNamePrefix;
698 }
699
700
701
702
703
704
705 public void setFileNameSuffix(String fileNameSuffix) {
706 this.fileNameSuffix = fileNameSuffix;
707 }
708
709
710
711
712
713
714 public void setTitle(String title) {
715 this.title = title;
716 }
717
718
719
720
721
722
723 public void setPageWidth(int pageWidth) {
724 this.pageWidth = pageWidth;
725 }
726
727
728
729
730
731
732 public void setPageLength(int pageLength) {
733 this.pageLength = pageLength;
734 }
735
736
737
738
739
740
741 public void setInitialPageNumber(int initialPageNumber) {
742 this.initialPageNumber = initialPageNumber;
743 }
744
745
746
747
748
749
750 public void setErrorSubTitle(String errorSubTitle) {
751 this.errorSubTitle = errorSubTitle;
752 }
753
754
755
756
757
758
759 public void setStatisticsLabel(String statisticsLabel) {
760 this.statisticsLabel = statisticsLabel;
761 }
762
763
764
765
766
767
768 public void setStatisticsLeftPadding(String statisticsLeftPadding) {
769 this.statisticsLeftPadding = statisticsLeftPadding;
770 }
771
772
773
774
775
776
777 public void setPageLabel(String pageLabel) {
778 this.pageLabel = pageLabel;
779 }
780
781
782
783
784
785
786 public void setNewLineCharacter(String newLineCharacter) {
787 this.newLineCharacter = newLineCharacter;
788 }
789
790
791
792
793
794
795 public void setDateTimeService(DateTimeService dateTimeService) {
796 this.dateTimeService = dateTimeService;
797 }
798
799
800
801
802
803
804
805 public void setClassToBusinessObjectReportHelperBeanNames(Map<Class<? extends BusinessObject>, String> classToBusinessObjectReportHelperBeanNames) {
806 this.classToBusinessObjectReportHelperBeanNames = classToBusinessObjectReportHelperBeanNames;
807 }
808
809
810
811
812
813 public String getParametersLabel() {
814 return parametersLabel;
815 }
816
817
818
819
820
821 public void setParametersLabel(String parametersLabel) {
822 this.parametersLabel = parametersLabel;
823 }
824
825
826
827
828
829 public String getParametersLeftPadding() {
830 return parametersLeftPadding;
831 }
832
833
834
835
836
837 public void setParametersLeftPadding(String parametersLeftPadding) {
838 this.parametersLeftPadding = parametersLeftPadding;
839 }
840
841
842
843
844
845 public boolean isAggregationModeOn() {
846 return aggregationModeOn;
847 }
848
849
850
851
852
853 public void setAggregationModeOn(boolean aggregationModeOn) {
854 this.aggregationModeOn = aggregationModeOn;
855 }
856
857
858 }