/* * 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.sys.batch.service.impl; import java.io.File; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.sys.KFSConstants.SystemGroupParameterNames; import org.kuali.kfs.sys.batch.BatchInputFileType; import org.kuali.kfs.sys.batch.service.BatchInputFileService; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.exception.FileStorageException; import org.kuali.kfs.sys.exception.ParseException; import org.kuali.kfs.sys.service.impl.KfsParameterConstants; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kim.api.identity.Person; import org.kuali.rice.krad.exception.AuthorizationException; import org.kuali.rice.krad.util.ObjectUtils; /** * Provides batch input file management, including listing files, parsing, downloading, storing, and deleting. */ public class BatchInputFileServiceImpl implements BatchInputFileService { private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BatchInputFileServiceImpl.class); /** * Delegates to the batch input file type to parse the file. * * @see org.kuali.kfs.sys.batch.service.BatchInputFileService#parse(org.kuali.kfs.sys.batch.BatchInputFileType, byte[]) */ @Override public Object parse(BatchInputFileType batchInputFileType, byte[] fileByteContent) { try { return batchInputFileType.parse(fileByteContent); } catch (ParseException e) { LOG.error("Error encountered parsing file", e); throw e; } } /** * Defers to batch type to do any validation on the parsed contents. * * @see org.kuali.kfs.sys.batch.service.BatchInputFileService#validate(org.kuali.kfs.sys.batch.BatchInputFileType, java.lang.Object) */ @Override public boolean validate(BatchInputFileType batchInputFileType, Object parsedObject) { if (batchInputFileType == null || parsedObject == null) { LOG.error("an invalid(null) argument was given"); throw new IllegalArgumentException("an invalid(null) argument was given"); } boolean contentsValid = true; contentsValid = batchInputFileType.validate(parsedObject); return contentsValid; } /** * @see org.kuali.kfs.sys.batch.service.BatchInputFileService#save(org.kuali.rice.kim.api.identity.Person, * org.kuali.kfs.sys.batch.BatchInputFileType, java.lang.String, java.io.InputStream) */ @Override public String save(Person user, BatchInputFileType batchInputFileType, String fileUserIdentifier, InputStream fileContents, Object parsedObject) throws AuthorizationException, FileStorageException { if (user == null || batchInputFileType == null || fileContents == null) { LOG.error("an invalid(null) argument was given"); throw new IllegalArgumentException("an invalid(null) argument was given"); } if (!isFileUserIdentifierProperlyFormatted(fileUserIdentifier)) { LOG.error("The following file user identifer was not properly formatted: " + fileUserIdentifier); throw new IllegalArgumentException("The following file user identifer was not properly formatted: " + fileUserIdentifier); } // defer to batch input type to add any security or other needed information to the file name String saveFileName = batchInputFileType.getDirectoryPath() + "/" + batchInputFileType.getFileName(user.getPrincipalName(), parsedObject, fileUserIdentifier); if (!StringUtils.isBlank(batchInputFileType.getFileExtension())) { saveFileName += "." + batchInputFileType.getFileExtension(); } if (batchInputFileType.shouldSave()) { // consruct the file object and check for existence File fileToSave = new File(saveFileName); if (fileToSave.exists()) { LOG.error("cannot store file, name already exists " + saveFileName); throw new FileStorageException("Cannot store file because the name " + saveFileName + " already exists on the file system."); } try { FileOutputStream fos = new FileOutputStream(fileToSave); while (fileContents.available() > 0) { fos.write(fileContents.read()); } fos.flush(); fos.close(); createDoneFile(fileToSave, batchInputFileType); batchInputFileType.process(saveFileName, parsedObject); } catch (IOException e) { LOG.error("unable to save contents to file " + saveFileName, e); throw new RuntimeException("errors encountered while writing file " + saveFileName, e); } } else { batchInputFileType.process(saveFileName, parsedObject); } return saveFileName; } /** * Creates a '.done' file with the name of the batch file. */ protected void createDoneFile(File batchFile ,BatchInputFileType batchInputFileType ) { String fileExtension = batchInputFileType.getFileExtension(); File doneFile = generateDoneFileObject(batchFile, fileExtension); String doneFileName = doneFile.getName(); if (!doneFile.exists()) { boolean doneFileCreated = false; try { doneFileCreated = doneFile.createNewFile(); } catch (IOException e) { LOG.error("unable to create done file " + doneFileName, e); throw new RuntimeException("Errors encountered while saving the file: Unable to create .done file " + doneFileName, e); } if (!doneFileCreated) { LOG.error("unable to create done file " + doneFileName); throw new RuntimeException("Errors encountered while saving the file: Unable to create .done file " + doneFileName); } } } /** * This method is responsible for creating a File object that represents the done file. The real file represented on disk may * not exist * * @param batchInputFile * @return a File object representing the done file. The real file may not exist on disk, but the return value can be used to * create that file. */ protected File generateDoneFileObject(File batchInputFile, String fileExtension) { String doneFileName = fileExtension != null ? StringUtils.substringBeforeLast(batchInputFile.getPath(), ".") + ".done" : batchInputFile.getPath() + ".done" ; File doneFile = new File(doneFileName); return doneFile; } /** * @see org.kuali.kfs.sys.batch.service.BatchInputFileService#isBatchInputTypeActive(org.kuali.kfs.sys.batch.BatchInputFileType) */ @Override public boolean isBatchInputTypeActive(BatchInputFileType batchInputFileType) { if (batchInputFileType == null) { LOG.error("an invalid(null) argument was given"); throw new IllegalArgumentException("an invalid(null) argument was given"); } List<String> activeInputTypes = new ArrayList<String>( SpringContext.getBean(ParameterService.class).getParameterValuesAsString(KfsParameterConstants.FINANCIAL_SYSTEM_BATCH.class, SystemGroupParameterNames.ACTIVE_INPUT_TYPES_PARAMETER_NAME) ); boolean activeBatchType = false; if (activeInputTypes.size() > 0 && activeInputTypes.contains(batchInputFileType.getFileTypeIdentifer())) { activeBatchType = true; } return activeBatchType; } /** * Fetches workgroup for batch type from system parameter and verifies user is a member. Then a list of all files for the batch * type are retrieved. For each file, the file and user is sent through the checkAuthorization method of the batch input type * implementation for finer grained security. If the method returns true, the filename is added to the user's list. * * @see org.kuali.kfs.sys.batch.service.BatchInputFileService#listBatchTypeFilesForUser(org.kuali.kfs.sys.batch.BatchInputFileType, * org.kuali.rice.kim.api.identity.Person) */ @Override public List<String> listBatchTypeFilesForUser(BatchInputFileType batchInputFileType, Person user) throws AuthorizationException { if (batchInputFileType == null || user == null) { LOG.error("an invalid(null) argument was given"); throw new IllegalArgumentException("an invalid(null) argument was given"); } File[] filesInBatchDirectory = listFilesInBatchTypeDirectory(batchInputFileType); List<String> userFileNamesList = new ArrayList<String>(); List<File> userFileList = listBatchTypeFilesForUserAsFiles(batchInputFileType, user); for (File userFile : userFileList) { userFileNamesList.add(userFile.getAbsolutePath()); } return userFileNamesList; } protected List<File> listBatchTypeFilesForUserAsFiles(BatchInputFileType batchInputFileType, Person user) throws AuthorizationException { File[] filesInBatchDirectory = listFilesInBatchTypeDirectory(batchInputFileType); List<File> userFileList = new ArrayList<File>(); if (filesInBatchDirectory != null) { for (int i = 0; i < filesInBatchDirectory.length; i++) { File batchFile = filesInBatchDirectory[i]; String fileExtension = StringUtils.substringAfterLast(batchFile.getName(), "."); if (StringUtils.isBlank(batchInputFileType.getFileExtension()) || batchInputFileType.getFileExtension().equals(fileExtension)) { if (user.getPrincipalName().equals(batchInputFileType.getAuthorPrincipalName(batchFile))) { userFileList.add(batchFile); } } } } return userFileList; } /** * Returns List of filenames for existing files in the directory given by the batch input type. */ protected File[] listFilesInBatchTypeDirectory(BatchInputFileType batchInputFileType) { File batchTypeDirectory = new File(batchInputFileType.getDirectoryPath()); return batchTypeDirectory.listFiles(); } /** * @see org.kuali.kfs.sys.batch.service.BatchInputFileService#listInputFileNamesWithDoneFile(org.kuali.kfs.sys.batch.BatchInputFileType) */ @Override public List<String> listInputFileNamesWithDoneFile(BatchInputFileType batchInputFileType) { if (batchInputFileType == null) { LOG.error("an invalid(null) argument was given"); throw new IllegalArgumentException("an invalid(null) argument was given"); } File batchTypeDirectory = new File(batchInputFileType.getDirectoryPath()); File[] doneFiles = batchTypeDirectory.listFiles(new DoneFilenameFilter()); List<String> batchInputFiles = new ArrayList<String>(); for (int i = 0; i < doneFiles.length; i++) { File doneFile = doneFiles[i]; String dataFileName = StringUtils.substringBeforeLast(doneFile.getPath(), "."); if (!StringUtils.isBlank(batchInputFileType.getFileExtension())) { dataFileName += "." + batchInputFileType.getFileExtension(); } File dataFile = new File(dataFileName); if (dataFile.exists()) { batchInputFiles.add(dataFile.getPath()); } } return batchInputFiles; } /** * Retrieves files in a directory with the .done extension. */ protected class DoneFilenameFilter implements FilenameFilter { /** * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String) */ @Override public boolean accept(File dir, String name) { return name.endsWith(".done"); } } /** * For this implementation, a file user identifier must consist of letters and digits * * @see org.kuali.kfs.sys.batch.service.BatchInputFileService#isFileUserIdentifierProperlyFormatted(java.lang.String) */ @Override public boolean isFileUserIdentifierProperlyFormatted(String fileUserIdentifier) { if(ObjectUtils.isNull(fileUserIdentifier)) { return false; } for (int i = 0; i < fileUserIdentifier.length(); i++) { char c = fileUserIdentifier.charAt(i); if (!(Character.isLetterOrDigit(c))) { return false; } } return true; } }