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