/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.gl.document.service.impl; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.io.OutputStream; import java.sql.Date; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.PrefixFileFilter; import org.apache.commons.io.filefilter.SuffixFileFilter; import org.apache.log4j.Logger; import org.kuali.kfs.gl.GeneralLedgerConstants; import org.kuali.kfs.gl.batch.service.impl.OriginEntryFileIterator; import org.kuali.kfs.gl.businessobject.CorrectionChangeGroup; import org.kuali.kfs.gl.businessobject.OriginEntryFull; import org.kuali.kfs.gl.businessobject.OriginEntryStatistics; import org.kuali.kfs.gl.dataaccess.CorrectionChangeDao; import org.kuali.kfs.gl.dataaccess.CorrectionChangeGroupDao; import org.kuali.kfs.gl.dataaccess.CorrectionCriteriaDao; import org.kuali.kfs.gl.document.CorrectionDocumentUtils; import org.kuali.kfs.gl.document.GeneralLedgerCorrectionProcessDocument; import org.kuali.kfs.gl.document.dataaccess.CorrectionDocumentDao; import org.kuali.kfs.gl.document.service.CorrectionDocumentService; import org.kuali.kfs.gl.document.web.CorrectionDocumentEntryMetadata; import org.kuali.kfs.gl.report.CorrectionDocumentReport; import org.kuali.kfs.gl.service.OriginEntryGroupService; import org.kuali.kfs.gl.service.OriginEntryService; import org.kuali.kfs.sys.FileUtil; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.batch.InitiateDirectoryBase; import org.kuali.kfs.sys.service.DocumentNumberAwareReportWriterService; import org.kuali.kfs.sys.service.ReportAggregatorService; import org.kuali.rice.core.api.config.property.ConfigurationService; import org.kuali.rice.core.api.datetime.DateTimeService; import org.kuali.rice.kew.api.WorkflowDocument; import org.kuali.rice.kew.api.exception.WorkflowException; import org.kuali.rice.kns.web.ui.Column; import org.kuali.rice.krad.comparator.NumericValueComparator; import org.kuali.rice.krad.comparator.StringValueComparator; import org.kuali.rice.krad.comparator.TemporalValueComparator; import org.kuali.rice.krad.service.DocumentService; import org.springframework.transaction.annotation.Transactional; /** * The base implementaiton of CorrectionDocumentService */ @Transactional public class CorrectionDocumentServiceImpl extends InitiateDirectoryBase implements CorrectionDocumentService { protected static Logger LOG = Logger.getLogger(CorrectionDocumentServiceImpl.class); protected CorrectionChangeGroupDao correctionChangeGroupDao; protected CorrectionChangeDao correctionChangeDao; protected CorrectionCriteriaDao correctionCriteriaDao; protected DocumentService documentService; protected ConfigurationService kualiConfigurationService; protected OriginEntryService originEntryService; protected String glcpDirectoryName; protected OriginEntryGroupService originEntryGroupService; protected DocumentNumberAwareReportWriterService glCorrectionDocumentReportWriterService; protected DateTimeService dateTimeService; protected ReportAggregatorService reportAggregatorService; protected String temporaryReportsDirectory; protected String temporaryReportFilenameComponent; protected String temporaryReportFilenameSuffix; protected String reportsDirectory; protected String reportFilenamePrefix; protected String reportFilenameSuffix; protected static final String INPUT_ORIGIN_ENTRIES_FILE_SUFFIX = "-input.txt"; protected static final String OUTPUT_ORIGIN_ENTRIES_FILE_SUFFIX = "-output.txt"; protected static final String GLCP_OUTPUT_PREFIX = "glcp_output"; protected static final String CORRECTION_FILE_FILTER = "put.txt"; protected CorrectionDocumentDao correctionDocumentDao; protected String batchFileDirectoryName; /** * Returns a specific correction change group for a GLCP document. Defers to DAO. * * @param docId the document id of a GLCP document * @param i the number of the correction group within the document * @return a CorrectionChangeGroup * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#findByDocumentNumberAndCorrectionChangeGroupNumber(java.lang.String, * int) */ public CorrectionChangeGroup findByDocumentNumberAndCorrectionChangeGroupNumber(String docId, int i) { return correctionChangeGroupDao.findByDocumentNumberAndCorrectionChangeGroupNumber(docId, i); } /** * Finds CollectionChange records associated with a given document id and correction change group. Defers to DAO * * @param docId the document id of a GLCP document * @param i the number of the correction group within the document * @return a List of qualifying CorrectionChange records * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#findByDocumentHeaderIdAndCorrectionGroupNumber(java.lang.String, * int) */ public List findByDocumentHeaderIdAndCorrectionGroupNumber(String docId, int i) { return correctionChangeDao.findByDocumentHeaderIdAndCorrectionGroupNumber(docId, i); } /** * Finds Collection Criteria associated with the given GLCP document and group. Defers to DAO. * * @param docId the document id of a GLCP document * @param i the number of the correction group within the document * @return a List of qualifying CorrectionCriteria * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#findByDocumentNumberAndCorrectionGroupNumber(java.lang.String, * int) */ public List findByDocumentNumberAndCorrectionGroupNumber(String docId, int i) { return correctionCriteriaDao.findByDocumentNumberAndCorrectionGroupNumber(docId, i); } /** * Retrieves a correction document by the document id * * @param docId the document id of the GLCP to find * @return a CorrectionDocument if found * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#findByCorrectionDocumentHeaderId(java.lang.String) */ public GeneralLedgerCorrectionProcessDocument findByCorrectionDocumentHeaderId(String docId) { try { return (GeneralLedgerCorrectionProcessDocument) documentService.getByDocumentHeaderIdSessionless(docId); } catch (WorkflowException ex) { throw new RuntimeException( "Unable to retrieve document for GLCP process", ex ); } } public void setCorrectionChangeDao(CorrectionChangeDao correctionChangeDao) { this.correctionChangeDao = correctionChangeDao; } public void setCorrectionChangeGroupDao(CorrectionChangeGroupDao correctionChangeGroupDao) { this.correctionChangeGroupDao = correctionChangeGroupDao; } public void setCorrectionCriteriaDao(CorrectionCriteriaDao correctionCriteriaDao) { this.correctionCriteriaDao = correctionCriteriaDao; } private List<Column> cachedColumns = null; /** * Returns metadata to help render columns in the GLCP. Do not modify this list or the contents in this list. * * @param docId the document id of a GLCP document * @return a List of Columns to render * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#getTableRenderColumnMetadata(java.lang.String) * * KRAD Conversion: Service method creates metadata of different columns. * No use of data dictionary. */ public List<Column> getTableRenderColumnMetadata(String docId) { synchronized (this) { if (cachedColumns == null) { cachedColumns = new ArrayList<Column>(); Column columnToAdd; columnToAdd = new Column(); columnToAdd.setColumnTitle("Fiscal Year"); columnToAdd.setPropertyName(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR); columnToAdd.setValueComparator(NumericValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Chart Code"); columnToAdd.setPropertyName(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Account Number"); columnToAdd.setPropertyName(KFSPropertyConstants.ACCOUNT_NUMBER); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Sub Account Number"); columnToAdd.setPropertyName(KFSPropertyConstants.SUB_ACCOUNT_NUMBER); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Object Code"); columnToAdd.setPropertyName(KFSPropertyConstants.FINANCIAL_OBJECT_CODE); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Sub Object Code"); columnToAdd.setPropertyName(KFSPropertyConstants.FINANCIAL_SUB_OBJECT_CODE); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Balance Type"); columnToAdd.setPropertyName(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Object Type"); columnToAdd.setPropertyName(KFSPropertyConstants.FINANCIAL_OBJECT_TYPE_CODE); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Fiscal Period"); columnToAdd.setPropertyName(KFSPropertyConstants.UNIVERSITY_FISCAL_PERIOD_CODE); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Document Type"); columnToAdd.setPropertyName(KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Origin Code"); columnToAdd.setPropertyName(KFSPropertyConstants.FINANCIAL_SYSTEM_ORIGINATION_CODE); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Document Number"); columnToAdd.setPropertyName(KFSPropertyConstants.DOCUMENT_NUMBER); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Sequence Number"); columnToAdd.setValueComparator(NumericValueComparator.getInstance()); columnToAdd.setPropertyName(KFSPropertyConstants.TRANSACTION_ENTRY_SEQUENCE_NUMBER); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Description"); columnToAdd.setPropertyName(KFSPropertyConstants.TRANSACTION_LEDGER_ENTRY_DESC); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Amount"); columnToAdd.setValueComparator(NumericValueComparator.getInstance()); columnToAdd.setPropertyName(KFSPropertyConstants.TRANSACTION_LEDGER_ENTRY_AMOUNT); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Debit Credit Indicator"); columnToAdd.setPropertyName(KFSPropertyConstants.TRANSACTION_DEBIT_CREDIT_CODE); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Transaction Date"); columnToAdd.setPropertyName(KFSPropertyConstants.TRANSACTION_DATE); columnToAdd.setValueComparator(TemporalValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Org Doc Number"); columnToAdd.setPropertyName(KFSPropertyConstants.ORGANIZATION_DOCUMENT_NUMBER); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Project Code"); columnToAdd.setPropertyName(KFSPropertyConstants.PROJECT_CODE); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Org Ref ID"); columnToAdd.setPropertyName(KFSPropertyConstants.ORGANIZATION_REFERENCE_ID); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Ref Doc Type"); columnToAdd.setPropertyName(KFSPropertyConstants.REFERENCE_FIN_DOCUMENT_TYPE_CODE); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Ref Origin Code"); columnToAdd.setPropertyName(KFSPropertyConstants.REFERENCE_FINANCIAL_SYSTEM_ORIGINATION_CODE); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Ref Doc Number"); columnToAdd.setPropertyName(KFSPropertyConstants.FINANCIAL_DOCUMENT_REFERENCE_NBR); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Reversal Date"); columnToAdd.setPropertyName(KFSPropertyConstants.FINANCIAL_DOCUMENT_REVERSAL_DATE); columnToAdd.setValueComparator(TemporalValueComparator.getInstance()); cachedColumns.add(columnToAdd); columnToAdd = new Column(); columnToAdd.setColumnTitle("Enc Update Code"); columnToAdd.setPropertyName(KFSPropertyConstants.TRANSACTION_ENCUMBRANCE_UPDT_CD); columnToAdd.setValueComparator(StringValueComparator.getInstance()); cachedColumns.add(columnToAdd); cachedColumns = Collections.unmodifiableList(cachedColumns); } } return cachedColumns; } /** * Generates the file name that input origin entries should be retrieved from * * @param document a GLCP document * @return the name of the file to read */ protected String generateInputOriginEntryFileName(GeneralLedgerCorrectionProcessDocument document) { String docId = document.getDocumentHeader().getDocumentNumber(); return generateInputOriginEntryFileName(docId); } /** * Generates the file name that output origin entries should be written to * * @param document a GLCP document * @return the name of the file to write to */ protected String generateOutputOriginEntryFileName(GeneralLedgerCorrectionProcessDocument document) { String docId = document.getDocumentHeader().getDocumentNumber(); return generateOutputOriginEntryFileName(docId); } /** * Generates the file name that input origin entries should be retrieved from * * @param docId the document id of a GLCP document * @return the name of the file to read input origin entries in from */ protected String generateInputOriginEntryFileName(String docId) { return getOriginEntryStagingDirectoryPath() + File.separator + docId + INPUT_ORIGIN_ENTRIES_FILE_SUFFIX; } /** * Generates the file name that output origin entries should be written to * * @param docId the document id of a GLCP document * @return the name of the file to write output origin entries to */ public String generateOutputOriginEntryFileName(String docId) { return getOriginEntryStagingDirectoryPath() + File.separator + docId + OUTPUT_ORIGIN_ENTRIES_FILE_SUFFIX; } /** * This method persists an Iterator of input origin entries for a document that is in the initiated or saved state * * @param document an initiated or saved document * @param entries an Iterator of origin entries * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#persistOriginEntriesToFile(java.lang.String, * java.util.Iterator) */ public void persistInputOriginEntriesForInitiatedOrSavedDocument(GeneralLedgerCorrectionProcessDocument document, Iterator<OriginEntryFull> entries) { WorkflowDocument workflowDocument = document.getDocumentHeader().getWorkflowDocument(); if (!workflowDocument.isInitiated() && !workflowDocument.isSaved()) { LOG.error("This method may only be called when the document is in the initiated or saved state."); } String fullPathUniqueFileName = generateInputOriginEntryFileName(document); persistOriginEntries(fullPathUniqueFileName, entries); } /** * This method persists an Iterator of output origin entries for a document that is in the initiated or saved state * * @param document an initiated or saved document * @param entries an Iterator of origin entries * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#persistOutputOriginEntriesForInitiatedOrSavedDocument(org.kuali.kfs.gl.document.CorrectionDocument, * java.util.Iterator) */ public void persistOutputOriginEntriesForInitiatedOrSavedDocument(GeneralLedgerCorrectionProcessDocument document, Iterator<OriginEntryFull> entries) { WorkflowDocument workflowDocument = document.getDocumentHeader().getWorkflowDocument(); if (!workflowDocument.isInitiated() && !workflowDocument.isSaved()) { LOG.error("This method may only be called when the document is in the initiated or saved state."); } String fullPathUniqueFileName = generateOutputOriginEntryFileName(document); persistOriginEntries(fullPathUniqueFileName, entries); } /** * Saves an interator of Origin Entry records to the given file name * * @param fullPathUniqueFileName the name of the file to write entries to * @param entries entries to write */ protected void persistOriginEntries(String fullPathUniqueFileName, Iterator<OriginEntryFull> entries) { File fileOut = new File(fullPathUniqueFileName); FileOutputStream streamOut = null; BufferedOutputStream bufferedStreamOut = null; try { streamOut = new FileOutputStream(fileOut); bufferedStreamOut = new BufferedOutputStream(streamOut); byte[] newLine = "\n".getBytes(); while (entries.hasNext()) { OriginEntryFull entry = entries.next(); bufferedStreamOut.write(entry.getLine().getBytes()); bufferedStreamOut.write(newLine); } } catch (IOException e) { LOG.error("unable to persist origin entries to file: " + fullPathUniqueFileName, e); throw new RuntimeException("unable to persist origin entries to file."); } finally { try { bufferedStreamOut.close(); streamOut.close(); } catch (IOException e) { LOG.error("unable to close output streams for file: " + fullPathUniqueFileName, e); throw new RuntimeException("unable to close output streams"); } } } /** * Opens an Output Stream to write Origin Entries to * * @param document the GLCP document which has the origin entries to write * @return an OutputStream to write to * @throws IOException if the file cannot be successfully opened */ protected BufferedOutputStream openEntryOutputStreamForOutputGroup(GeneralLedgerCorrectionProcessDocument document) throws IOException { String fullPathUniqueFileName = generateOutputOriginEntryFileName(document); return new BufferedOutputStream(new FileOutputStream(fullPathUniqueFileName)); } /** * Removes input origin entries that were saved to the database associated with the given document * * @param document a GLCP document * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#removePersistedInputOriginEntriesForInitiatedOrSavedDocument(org.kuali.kfs.gl.document.CorrectionDocument) */ public void removePersistedInputOriginEntries(GeneralLedgerCorrectionProcessDocument document) { String fullPathUniqueFileName = generateInputOriginEntryFileName(document); removePersistedOriginEntries(fullPathUniqueFileName); } /** * Removes all output origin entries persisted in the database created by the given document * * @param document a GLCP document * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#removePersistedOutputOriginEntriesForInitiatedOrSavedDocument(org.kuali.kfs.gl.document.CorrectionDocument) */ public void removePersistedOutputOriginEntries(GeneralLedgerCorrectionProcessDocument document) { String fullPathUniqueFileName = generateOutputOriginEntryFileName(document); removePersistedOriginEntries(fullPathUniqueFileName); } /** * Removes input origin entries that were saved to the database associated with the given document * * @param docId the document id of a GLCP document * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#removePersistedInputOriginEntries(java.lang.String) */ public void removePersistedInputOriginEntries(String docId) { removePersistedOriginEntries(generateInputOriginEntryFileName(docId)); } /** * Removes all output origin entries persisted in the database created by the given document * * @param docId the document id of a GLCP document * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#removePersistedOutputOriginEntries(java.lang.String) */ public void removePersistedOutputOriginEntries(String docId) { removePersistedOriginEntries(generateOutputOriginEntryFileName(docId)); } /** * Removes a file of origin entries. Just deletes the whole thing! * * @param fullPathUniqueFileName the file name of the file holding origin entries */ protected void removePersistedOriginEntries(String fullPathUniqueFileName) { File fileOut = new File(fullPathUniqueFileName); if (fileOut.exists() && fileOut.isFile()) { fileOut.delete(); } } /** * retrieves input origin entries that have been persisted for this document * * @param document the document * @param abortThreshold if the file exceeds this number of rows, then null is returned. {@link UNLIMITED_ABORT_THRESHOLD} * signifies that there is no limit * @return the list, or null if there are too many origin entries * @throws RuntimeException several reasons, primarily relating to underlying persistence layer problems * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#retrievePersistedInputOriginEntries(org.kuali.kfs.gl.document.CorrectionDocument, * int) */ public List<OriginEntryFull> retrievePersistedInputOriginEntries(GeneralLedgerCorrectionProcessDocument document, int abortThreshold) { return retrievePersistedOriginEntries(generateInputOriginEntryFileName(document), abortThreshold); } /** * retrieves output origin entries that have been persisted for this document * * @param document the document * @param abortThreshold if the file exceeds this number of rows, then null is returned. {@link UNLIMITED_ABORT_THRESHOLD} * signifies that there is no limit * @return the list, or null if there are too many origin entries * @throws RuntimeException several reasons, primarily relating to underlying persistence layer problems * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#retrievePersistedOutputOriginEntries(org.kuali.kfs.gl.document.CorrectionDocument, * int) */ public List<OriginEntryFull> retrievePersistedOutputOriginEntries(GeneralLedgerCorrectionProcessDocument document, int abortThreshold) { return retrievePersistedOriginEntries(generateOutputOriginEntryFileName(document), abortThreshold); } /** * Reads a file of origin entries and returns a List of those entry records * * @param fullPathUniqueFileName the file name of the file to read * @param abortThreshold if more entries than this need to be read...well, they just won't get read * @return a List of OriginEntryFulls */ protected List<OriginEntryFull> retrievePersistedOriginEntries(String fullPathUniqueFileName, int abortThreshold) { File fileIn = new File(fullPathUniqueFileName); if (!fileIn.exists()) { LOG.error("File " + fullPathUniqueFileName + " does not exist."); throw new RuntimeException("File does not exist"); } BufferedReader reader = null; FileReader fReader = null; List<OriginEntryFull> entries = new ArrayList<OriginEntryFull>(); int lineNumber = 0; try { fReader = new FileReader(fileIn); reader = new BufferedReader(fReader); String line; while ((line = reader.readLine()) != null) { OriginEntryFull entry = new OriginEntryFull(); entry.setFromTextFileForBatch(line, lineNumber); if (abortThreshold != UNLIMITED_ABORT_THRESHOLD && lineNumber >= abortThreshold) { return null; } lineNumber++; entries.add(entry); } } catch (IOException e) { LOG.error("retrievePersistedOriginEntries() Error reading file " + fileIn.getAbsolutePath(), e); throw new RuntimeException("Error reading file"); } finally { try { if (fReader != null) { fReader.close(); } if (reader != null) { reader.close(); } } catch (IOException e) { LOG.error("Unable to close file " + fileIn.getAbsolutePath(), e); throw new RuntimeException("Error closing file"); } } return entries; } /** * Retrieves input origin entries that have been persisted for this document in an iterator. Implementations of this method may * choose to implement this method in a way that consumes very little memory. * * @param document the document * @return the iterator * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#retrievePersistedInputOriginEntriesAsIterator(org.kuali.kfs.gl.document.CorrectionDocument) */ public Iterator<OriginEntryFull> retrievePersistedInputOriginEntriesAsIterator(GeneralLedgerCorrectionProcessDocument document) { String fullPathUniqueFileName = generateInputOriginEntryFileName(document); return retrievePersistedOriginEntriesAsIterator(fullPathUniqueFileName); } /** * Retrieves output origin entries that have been persisted for this document in an iterator. Implementations of this method may * choose to implement this method in a way that consumes very little memory. * * @param document the document * @return the iterator * @throws RuntimeException several reasons, primarily relating to underlying persistence layer problems * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#retrievePersistedOutputOriginEntriesAsIterator(org.kuali.kfs.gl.document.CorrectionDocument) */ public Iterator<OriginEntryFull> retrievePersistedOutputOriginEntriesAsIterator(GeneralLedgerCorrectionProcessDocument document) { String fullPathUniqueFileName = generateOutputOriginEntryFileName(document); return retrievePersistedOriginEntriesAsIterator(fullPathUniqueFileName); } /** * Reads origin entries from a file to an iterator * * @param fullPathUniqueFileName the file name to read from * @return an Iterator of OriginEntries */ protected Iterator<OriginEntryFull> retrievePersistedOriginEntriesAsIterator(String fullPathUniqueFileName) { File fileIn = new File(fullPathUniqueFileName); if (!fileIn.exists()) { LOG.error("File " + fullPathUniqueFileName + " does not exist."); throw new RuntimeException("File does not exist"); } BufferedReader reader = null; FileReader fReader = null; try { fReader = new FileReader(fileIn); reader = new BufferedReader(fReader); return new OriginEntryFileIterator(reader); } catch (IOException e) { LOG.error("retrievePersistedOriginEntries() Error opening file " + fileIn.getAbsolutePath(), e); throw new RuntimeException("Error opening file"); } // don't close the reader, the iterator will take care of that } /** * Returns true if and only if the file corresponding to this document's input origin entries are on the file system. * * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#areInputOriginEntriesPersisted(org.kuali.kfs.gl.document.CorrectionDocument) */ public boolean areInputOriginEntriesPersisted(GeneralLedgerCorrectionProcessDocument document) { String fullPathUniqueFileName = generateInputOriginEntryFileName(document); File file = new File(fullPathUniqueFileName); return file.exists(); } /** * Returns true if and only if the file corresponding to this document's output origin entries are on the file system. * * @param document a GLCP document to query * @return true if origin entries are stored to the system, false otherwise * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#areOutputOriginEntriesPersisted(org.kuali.kfs.gl.document.CorrectionDocument) */ public boolean areOutputOriginEntriesPersisted(GeneralLedgerCorrectionProcessDocument document) { String fullPathUniqueFileName = generateOutputOriginEntryFileName(document); File file = new File(fullPathUniqueFileName); return file.exists(); } /** * Writes out the persisted input origin entries in an {@link OutputStream} in a flat file format\ * * @param document a GLCP document * @param out axn open and ready output stream * @throws IOException thrown if IOExceptions occurred in writing the persisted origin entries * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#writePersistedInputOriginEntriesToStream(java.io.OutputStream) */ public void writePersistedInputOriginEntriesToStream(GeneralLedgerCorrectionProcessDocument document, OutputStream out) throws IOException { String fullPathUniqueFileName = generateInputOriginEntryFileName(document); writePersistedOriginEntriesToStream(fullPathUniqueFileName, out); } /** * Writes out the persisted output origin entries in an {@link OutputStream} in a flat file format\ * * @param document a GLCP document * @param out axn open and ready output stream * @throws IOException thrown if IOExceptions occurred in writing the persisted origin entries * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#writePersistedOutputOriginEntriesToStream(java.io.OutputStream) */ public void writePersistedOutputOriginEntriesToStream(GeneralLedgerCorrectionProcessDocument document, OutputStream out) throws IOException { String fullPathUniqueFileName = generateOutputOriginEntryFileName(document); writePersistedOriginEntriesToStream(fullPathUniqueFileName, out); } /** * Writes origin entries to an output stream * * @param fullPathUniqueFileName the name of the file to write to * @param out an output stream to write to * @throws IOException thrown if problems occur during writing */ protected void writePersistedOriginEntriesToStream(String fullPathUniqueFileName, OutputStream out) throws IOException { FileInputStream fileIn = new FileInputStream(fullPathUniqueFileName); try { byte[] buf = new byte[1000]; int bytesRead; while ((bytesRead = fileIn.read(buf)) != -1) { out.write(buf, 0, bytesRead); } } finally { fileIn.close(); } } public String createOutputFileForProcessing(String docId, java.util.Date today) { File outputFile = new File(glcpDirectoryName + File.separator + docId + OUTPUT_ORIGIN_ENTRIES_FILE_SUFFIX); String newFileName = batchFileDirectoryName + File.separator + GLCP_OUTPUT_PREFIX + "." + docId + buildFileExtensionWithDate(today); File newFile = new File(newFileName); FileReader inputFileReader; FileWriter newFileWriter; try { // copy output file and put in OriginEntryInformation directory inputFileReader = new FileReader(outputFile); newFileWriter = new FileWriter(newFile); int c; while ((c = inputFileReader.read()) != -1) { newFileWriter.write(c); } inputFileReader.close(); newFileWriter.close(); // create done file, after successfully copying output file String doneFileName = newFileName.replace(GeneralLedgerConstants.BatchFileSystem.EXTENSION, GeneralLedgerConstants.BatchFileSystem.DONE_FILE_EXTENSION); File doneFile = new File(doneFileName); if (!doneFile.exists()) { doneFile.createNewFile(); } } catch (IOException e) { throw new RuntimeException(e); } return newFileName; } protected String buildFileExtensionWithDate(java.util.Date date) { String formattedDateTime = dateTimeService.toDateTimeStringForFilename(date); return "." + formattedDateTime + GeneralLedgerConstants.BatchFileSystem.EXTENSION; } /** * Saves the input and output origin entry groups for a document prior to saving the document * * @param document a GLCP document * @param correctionDocumentEntryMetadata metadata about this GLCP document * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#persistOriginEntryGroupsForDocumentSave(org.kuali.kfs.gl.document.CorrectionDocument, * org.kuali.kfs.gl.document.web.CorrectionDocumentEntryMetadata) */ public void persistOriginEntryGroupsForDocumentSave(GeneralLedgerCorrectionProcessDocument document, CorrectionDocumentEntryMetadata correctionDocumentEntryMetadata) { if (correctionDocumentEntryMetadata.getAllEntries() == null && !correctionDocumentEntryMetadata.isRestrictedFunctionalityMode()) { // if we don't have origin entries loaded and not in restricted functionality mode, then there's nothing worth // persisting removePersistedInputOriginEntries(document); removePersistedOutputOriginEntries(document); return; } if (!correctionDocumentEntryMetadata.getDataLoadedFlag() && !correctionDocumentEntryMetadata.isRestrictedFunctionalityMode()) { // data is not loaded (maybe user selected a new group with no rows) // clear out existing data removePersistedInputOriginEntries(document); removePersistedOutputOriginEntries(document); return; } // reload the group from the origin entry service Iterator<OriginEntryFull> inputGroupEntries; WorkflowDocument workflowDocument = document.getDocumentHeader().getWorkflowDocument(); if ((workflowDocument.isSaved() && !(correctionDocumentEntryMetadata.getInputGroupIdFromLastDocumentLoad() != null && correctionDocumentEntryMetadata.getInputGroupIdFromLastDocumentLoad().equals(document.getCorrectionInputFileName()))) || workflowDocument.isInitiated()) { // we haven't saved the origin entry group yet, so let's load the entries from the DB and persist them for the document // this could be because we've previously saved the doc, but now we are now using a new input group, so we have to // repersist the input group // OriginEntryGroup group = originEntryGroupService.getExactMatchingEntryGroup(document.getCorrectionInputGroupId()); File file = new File(document.getCorrectionInputFileName()); inputGroupEntries = new OriginEntryFileIterator(file); persistInputOriginEntriesForInitiatedOrSavedDocument(document, inputGroupEntries); // we've exhausted the iterator for the origin entries group // reload the iterator from the file inputGroupEntries = retrievePersistedInputOriginEntriesAsIterator(document); } else if (workflowDocument.isSaved() && correctionDocumentEntryMetadata.getInputGroupIdFromLastDocumentLoad().equals(document.getCorrectionInputFileName())) { // we've saved the origin entries before, so just retrieve them inputGroupEntries = retrievePersistedInputOriginEntriesAsIterator(document); } else { LOG.error("Unexpected state while trying to persist/retrieve GLCP origin entries during document save: document status is " + workflowDocument.getStatus() + " selected input group: " + document.getCorrectionInputFileName() + " last saved input group: " + correctionDocumentEntryMetadata.getInputGroupIdFromLastDocumentLoad()); throw new RuntimeException("Error persisting GLCP document origin entries."); } OriginEntryStatistics statistics; if (CorrectionDocumentService.CORRECTION_TYPE_MANUAL.equals(correctionDocumentEntryMetadata.getEditMethod())) { // persist the allEntries element as the output group, since it has all of the modifications made by during the manual // edits persistOutputOriginEntriesForInitiatedOrSavedDocument(document, correctionDocumentEntryMetadata.getAllEntries().iterator()); // even though the struts action handler may have computed the doc totals, let's recompute them statistics = CorrectionDocumentUtils.getStatistics(correctionDocumentEntryMetadata.getAllEntries()); } else if (CorrectionDocumentService.CORRECTION_TYPE_CRITERIA.equals(correctionDocumentEntryMetadata.getEditMethod())) { // we want to persist the values of the output group. So reapply all of the criteria on each entry, one at a time BufferedOutputStream bufferedOutputStream = null; try { bufferedOutputStream = openEntryOutputStreamForOutputGroup(document); statistics = new OriginEntryStatistics(); byte[] newLine = "\n".getBytes(); while (inputGroupEntries.hasNext()) { OriginEntryFull entry = inputGroupEntries.next(); entry = CorrectionDocumentUtils.applyCriteriaToEntry(entry, correctionDocumentEntryMetadata.getMatchCriteriaOnly(), document.getCorrectionChangeGroup()); if (entry != null) { CorrectionDocumentUtils.updateStatisticsWithEntry(entry, statistics); bufferedOutputStream.write(entry.getLine().getBytes()); bufferedOutputStream.write(newLine); } // else it was null, which means that the match criteria only flag was set, and the entry didn't match the // criteria } } catch (IOException e) { LOG.error("Unable to persist persisted output entry", e); throw new RuntimeException("Unable to persist output entry"); } finally { if (bufferedOutputStream != null) { try { bufferedOutputStream.close(); } catch (IOException e) { LOG.error("Unable to close output stream for persisted output entries", e); throw new RuntimeException("Unable to close output entry file"); } } } } else if (CorrectionDocumentService.CORRECTION_TYPE_REMOVE_GROUP_FROM_PROCESSING.equals(correctionDocumentEntryMetadata.getEditMethod())) { // just wipe out the previous output entries removePersistedOutputOriginEntries(document); statistics = new OriginEntryStatistics(); } else { throw new RuntimeException("Unrecognized edit method: " + correctionDocumentEntryMetadata.getEditMethod()); } CorrectionDocumentUtils.copyStatisticsToDocument(statistics, document); } protected class CorrectionFileFilter implements FilenameFilter { /** * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String) */ public boolean accept(File dir, String name) { return name.contains(CORRECTION_FILE_FILTER); } } /** * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#generateCorrectionReport(org.kuali.kfs.gl.document.GeneralLedgerCorrectionProcessDocument) */ public void generateCorrectionReport(GeneralLedgerCorrectionProcessDocument document) { CorrectionDocumentReport correctionReport = new CorrectionDocumentReport(); correctionReport.generateReport(glCorrectionDocumentReportWriterService, document); } /** * Gets the name of the directory to save all these temporary files in * * @return the name of a directory path */ protected String getOriginEntryStagingDirectoryPath() { return getGlcpDirectoryName(); } /** * Gets the kualiConfigurationService attribute. * * @return Returns the kualiConfigurationService. */ public ConfigurationService getConfigurationService() { return kualiConfigurationService; } /** * Sets the kualiConfigurationService attribute value. * * @param kualiConfigurationService The kualiConfigurationService to set. */ public void setConfigurationService(ConfigurationService kualiConfigurationService) { this.kualiConfigurationService = kualiConfigurationService; } /** * Gets the originEntryService attribute. * * @return Returns the originEntryService. */ public OriginEntryService getOriginEntryService() { return originEntryService; } /** * Sets the originEntryService attribute value. * * @param originEntryService The originEntryService to set. */ public void setOriginEntryService(OriginEntryService originEntryService) { this.originEntryService = originEntryService; } /** * Gets the glcpDirectoryName attribute. * * @return Returns the glcpDirectoryName. */ public String getGlcpDirectoryName() { return glcpDirectoryName; } /** * Sets the glcpDirectoryName attribute value. * * @param glcpDirectoryName The glcpDirectoryName to set. */ public void setGlcpDirectoryName(String glcpDirectoryName) { this.glcpDirectoryName = glcpDirectoryName; FileUtil.createDirectory(glcpDirectoryName); } /** * Gets the originEntryGroupService attribute. * * @return Returns the originEntryGroupService. */ public OriginEntryGroupService getOriginEntryGroupService() { return originEntryGroupService; } /** * Sets the originEntryGroupService attribute value. * * @param originEntryGroupService The originEntryGroupService to set. */ public void setOriginEntryGroupService(OriginEntryGroupService originEntryGroupService) { this.originEntryGroupService = originEntryGroupService; } /** * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#getCorrectionDocumentsFinalizedOn(java.sql.Date) */ public Collection<GeneralLedgerCorrectionProcessDocument> getCorrectionDocumentsFinalizedOn(Date date) { return correctionDocumentDao.getCorrectionDocumentsFinalizedOn(date); } public void setCorrectionDocumentDao(CorrectionDocumentDao correctionDocumentDao) { this.correctionDocumentDao = correctionDocumentDao; } public void setBatchFileDirectoryName(String batchFileDirectoryName) { this.batchFileDirectoryName = batchFileDirectoryName; } public String getBatchFileDirectoryName() { return batchFileDirectoryName; } /** * @return Returns the glCorrectionDocumentReportWriterService. */ protected DocumentNumberAwareReportWriterService getGlCorrectionDocumentReportWriterService() { return glCorrectionDocumentReportWriterService; } /** * @param glCorrectionDocumentReportWriterService The glCorrectionDocumentReportWriterService to set. */ public void setGlCorrectionDocumentReportWriterService(DocumentNumberAwareReportWriterService glCorrectionDocumentReportWriterService) { this.glCorrectionDocumentReportWriterService = glCorrectionDocumentReportWriterService; } /** * @see org.kuali.kfs.gl.document.service.CorrectionDocumentService#aggregateCorrectionDocumentReports() */ public void aggregateCorrectionDocumentReports(GeneralLedgerCorrectionProcessDocument document) { File outputFile = getAggregatedReportFile(document.getDocumentNumber()); List<File> inputFiles = getReportsToAggregateIntoReport(document.getDocumentNumber()); reportAggregatorService.aggregateReports(outputFile, inputFiles); } protected File getAggregatedReportFile(String documentNumber) { String dateTimeStamp = dateTimeService.toDateTimeStringForFilename(dateTimeService.getCurrentDate()); String outputFilename = reportsDirectory + File.separator + reportFilenamePrefix + documentNumber + "_" + dateTimeStamp + reportFilenameSuffix; return new File(outputFilename); } protected List<File> getReportsToAggregateIntoReport(String documentNumber) { File inputDirectory = new File(temporaryReportsDirectory); if (!inputDirectory.exists() || !inputDirectory.isDirectory()) { LOG.error(temporaryReportsDirectory + " does not exist or is not a directory."); throw new RuntimeException("Unable to locate temporary reports directory"); } String filePrefix = documentNumber + "_" + temporaryReportFilenameComponent; FileFilter filter = FileFilterUtils.andFileFilter( new PrefixFileFilter(filePrefix), new SuffixFileFilter(temporaryReportFilenameSuffix)); // FSKD-244, KFSMI-5424 sort with filename, just in case List<File> fileList = Arrays.asList(inputDirectory.listFiles(filter)); Comparator fileNameComparator = new Comparator() { public int compare(Object obj1, Object obj2) { if (obj1 == null) { return -1; } if (obj2 == null) { return 1; } File file1 = (File) obj1; File file2 = (File) obj2; return ((Comparable) file1.getName()).compareTo(file2.getName()); } }; Collections.sort(fileList, fileNameComparator); return fileList ; } /** * Sets the dateTimeService attribute value. * @param dateTimeService The dateTimeService to set. */ public void setDateTimeService(DateTimeService dateTimeService) { this.dateTimeService = dateTimeService; } /** * Sets the reportAggregatorService attribute value. * @param reportAggregatorService The reportAggregatorService to set. */ public void setReportAggregatorService(ReportAggregatorService reportAggregatorService) { this.reportAggregatorService = reportAggregatorService; } /** * Sets the temporaryReportsDirectory attribute value. * @param temporaryReportsDirectory The temporaryReportsDirectory to set. */ public void setTemporaryReportsDirectory(String temporaryReportsDirectory) { this.temporaryReportsDirectory = temporaryReportsDirectory; } /** * Sets the temporaryReportFilenameComponent attribute value. * @param temporaryReportFilenameComponent The temporaryReportFilenameComponent to set. */ public void setTemporaryReportFilenameComponent(String temporaryReportFilenameComponent) { this.temporaryReportFilenameComponent = temporaryReportFilenameComponent; } /** * Sets the temporaryReportFilenameSuffix attribute value. * @param temporaryReportFilenameSuffix The temporaryReportFilenameSuffix to set. */ public void setTemporaryReportFilenameSuffix(String temporaryReportFilenameSuffix) { this.temporaryReportFilenameSuffix = temporaryReportFilenameSuffix; } /** * Sets the reportsDirectory attribute value. * @param reportsDirectory The reportsDirectory to set. */ public void setReportsDirectory(String reportsDirectory) { this.reportsDirectory = reportsDirectory; } /** * Sets the reportFilenamePrefix attribute value. * @param reportFilenamePrefix The reportFilenamePrefix to set. */ public void setReportFilenamePrefix(String reportFilenamePrefix) { this.reportFilenamePrefix = reportFilenamePrefix; } /** * Sets the reportFilenameSuffix attribute value. * @param reportFilenameSuffix The reportFilenameSuffix to set. */ public void setReportFilenameSuffix(String reportFilenameSuffix) { this.reportFilenameSuffix = reportFilenameSuffix; } protected static class GlcpFilenameFilter implements FilenameFilter { String documentNumber; public GlcpFilenameFilter( String documentNumber ) { this.documentNumber = documentNumber; } public boolean accept(File dir, String name) { return name.startsWith(GLCP_OUTPUT_PREFIX + "." + documentNumber); } } public String[] findExistingCorrectionOutputFilesForDocument( String documentNumber ) { return new File(batchFileDirectoryName).list( new GlcpFilenameFilter(documentNumber)); } /** * @see org.kuali.kfs.sys.batch.service.impl.InitiateDirectoryImpl#getRequiredDirectoryNames() */ @Override public List<String> getRequiredDirectoryNames() { return new ArrayList<String>() {{add(getOriginEntryStagingDirectoryPath()); }}; } public void setDocumentService(DocumentService documentService) { this.documentService = documentService; } }