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