/*
* 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.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSConstants.SystemGroupParameterNames;
import org.kuali.kfs.sys.batch.BatchInputFileSetType;
import org.kuali.kfs.sys.batch.InitiateDirectoryBase;
import org.kuali.kfs.sys.batch.service.BatchInputFileSetService;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.exception.FileStorageException;
import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
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.exception.ValidationException;
import org.kuali.rice.krad.util.GlobalVariables;
/**
* Base implementation to manipulate batch input file sets from the batch upload screen
*/
public class BatchInputFileSetServiceImpl extends InitiateDirectoryBase implements BatchInputFileSetService {
private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BatchInputFileSetServiceImpl.class);
protected ConfigurationService kualiConfigurationService;
/**
* Generates the file name of a file (not the done file)
*
* @param user the user who uploaded or will upload the file
* @param inputType the file set type
* @param fileUserIdentifier the file identifier
* @param fileType the file type
* @return the file name, starting with the directory path
*/
protected String generateFileName(Person user, BatchInputFileSetType inputType, String fileUserIdentifier, String fileType, Date creationDate) {
if (!isFileUserIdentifierProperlyFormatted(fileUserIdentifier)) {
throw new IllegalArgumentException("The file set identifier is not properly formatted: " + fileUserIdentifier);
}
return inputType.getDirectoryPath(fileType) + File.separator + inputType.getFileName(fileType, user.getPrincipalName(), fileUserIdentifier, creationDate);
}
/**
* Generates the file name of a file (not the done file)
*
* @param user the user who uploaded or will upload the file
* @param inputType the file set type
* @param fileUserIdentifier the file identifier
* @param fileType the file type
* @return the file name, starting with the directory path
*/
protected String generateTempFileName(Person user, BatchInputFileSetType inputType, String fileUserIdentifier, String fileType, Date creationDate) {
if (!isFileUserIdentifierProperlyFormatted(fileUserIdentifier)) {
throw new IllegalArgumentException("The file set identifier is not properly formatted: " + fileUserIdentifier);
}
return getTempDirectoryName() + File.separator + inputType.getFileName(fileType, user.getPrincipalName(), fileUserIdentifier, creationDate);
}
/**
* Generates the file name of the done file, if supported by the underlying input type
*
* @param user the user who uploaded or will upload the file
* @param inputType the file set type
* @param fileUserIdentifier the file identifier
* @param fileType the file type
* @return the file name, starting with the directory path
*/
protected String generateDoneFileName(Person user, BatchInputFileSetType inputType, String fileUserIdentifier, Date creationDate) {
if (!isFileUserIdentifierProperlyFormatted(fileUserIdentifier)) {
throw new IllegalArgumentException("The file set identifier is not properly formatted: " + fileUserIdentifier);
}
return inputType.getDoneFileDirectoryPath() + File.separator + inputType.getDoneFileName(user, fileUserIdentifier, creationDate);
}
/**
* @see org.kuali.kfs.sys.batch.service.BatchInputFileSetService#isBatchInputTypeActive(org.kuali.kfs.sys.batch.BatchInputFileSetType)
*/
public boolean isBatchInputTypeActive(BatchInputFileSetType batchInputFileSetType) {
if (batchInputFileSetType == 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(batchInputFileSetType.getFileSetTypeIdentifer())) {
activeBatchType = true;
}
return activeBatchType;
}
/**
* @see org.kuali.kfs.sys.batch.service.BatchInputFileSetService#save(org.kuali.rice.kim.api.identity.Person,
* org.kuali.kfs.sys.batch.BatchInputFileSetType, java.lang.String, java.util.Map)
*/
public Map<String, String> save(Person user, BatchInputFileSetType inputType, String fileUserIdentifier, Map<String, InputStream> typeToStreamMap) throws AuthorizationException, FileStorageException {
//add a step for file directory checking
prepareDirectories(getRequiredDirectoryNames());
Date creationDate = SpringContext.getBean(DateTimeService.class).getCurrentDate();
// check user is authorized to upload a file for the batch type
Map<String, File> typeToTempFiles = copyStreamsToTemporaryDirectory(user, inputType, fileUserIdentifier, typeToStreamMap, creationDate);
// null the map, because it's full of exhausted input streams that are useless
typeToStreamMap = null;
if (!inputType.validate(typeToTempFiles)) {
deleteTempFiles(typeToTempFiles);
LOG.error("Upload file validation failed for user " + user.getName() + " identifier " + fileUserIdentifier);
throw new ValidationException("File validation failed: " + GlobalVariables.getMessageMap().getErrorMessages());
}
byte[] buf = new byte[1024];
Map<String, String> typeToFileNames = new LinkedHashMap<String, String>();
Map<String, File> typeToFiles = new LinkedHashMap<String, File>();
try {
for (String fileType : inputType.getFileTypes()) {
File tempFile = typeToTempFiles.get(fileType);
String saveFileName = inputType.getDirectoryPath(fileType) + File.separator + tempFile.getName();
try {
InputStream fileContents = new FileInputStream(tempFile);
File fileToSave = new File(saveFileName);
copyInputStreamToFile(fileContents, fileToSave, buf);
fileContents.close();
typeToFileNames.put(fileType, saveFileName);
typeToFiles.put(fileType, fileToSave);
}
catch (IOException e) {
LOG.error("unable to save contents to file " + saveFileName, e);
throw new RuntimeException("errors encountered while writing file " + saveFileName, e);
}
}
}
finally {
deleteTempFiles(typeToTempFiles);
}
String doneFileName = inputType.getDoneFileDirectoryPath() + File.separator + inputType.getDoneFileName(user, fileUserIdentifier, creationDate);
File doneFile = new File(doneFileName);
try {
doneFile.createNewFile();
typeToFiles.put(KFSConstants.DONE_FILE_TYPE, doneFile);
}
catch (IOException e) {
LOG.error("unable to create done file", e);
throw new RuntimeException("unable to create done file", e);
}
inputType.process(typeToFiles);
return typeToFileNames;
}
protected Map<String, File> copyStreamsToTemporaryDirectory(Person user, BatchInputFileSetType inputType,
String fileUserIdentifier, Map<String, InputStream> typeToStreamMap, Date creationDate) throws FileStorageException {
Map<String, File> tempFiles = new HashMap<String, File>();
String tempDirectoryName = getTempDirectoryName();
File tempDirectory = new File(tempDirectoryName);
if (!tempDirectory.exists() || !tempDirectory.isDirectory()) {
LOG.error("Temporary Directory " + tempDirectoryName + " does not exist or is not a directory");
throw new RuntimeException("Temporary Directory " + tempDirectoryName + " does not exist or is not a directory");
}
byte[] buf = new byte[1024];
try {
for (String fileType : inputType.getFileTypes()) {
String tempFileName = generateTempFileName(user, inputType, fileUserIdentifier, fileType, creationDate);
InputStream source = typeToStreamMap.get(fileType);
File tempFile = new File(tempFileName);
copyInputStreamToFile(source, tempFile, buf);
tempFiles.put(fileType, tempFile);
}
}
catch (IOException e) {
LOG.error("Error creating temporary files", e);
throw new FileStorageException("Error creating temporary files",e);
}
return tempFiles;
}
protected void copyInputStreamToFile(InputStream source, File outputFile, byte[] buf) throws IOException {
OutputStream out = new FileOutputStream(outputFile);
int readBytes = source.read(buf);
while (readBytes != -1) {
out.write(buf, 0, readBytes);
readBytes = source.read(buf);
}
out.flush();
out.close();
}
protected String getTempDirectoryName() {
return kualiConfigurationService.getPropertyValueAsString(KFSConstants.TEMP_DIRECTORY_KEY);
}
/**
* @see org.kuali.kfs.sys.batch.service.BatchInputFileSetService#isFileUserIdentifierProperlyFormatted(java.lang.String)
*/
public boolean isFileUserIdentifierProperlyFormatted(String fileUserIdentifier) {
if (fileUserIdentifier == null) {
return false;
}
for (int i = 0; i < fileUserIdentifier.length(); i++) {
char c = fileUserIdentifier.charAt(i);
if (!(Character.isLetterOrDigit(c))) {
return false;
}
}
return true;
}
public void setConfigurationService(ConfigurationService kualiConfigurationService) {
this.kualiConfigurationService = kualiConfigurationService;
}
protected void deleteTempFiles(Map<String, File> typeToTempFiles) {
for (File tempFile : typeToTempFiles.values()) {
if (tempFile.exists()) {
boolean deletionSuccessful = tempFile.delete();
if (!deletionSuccessful) {
LOG.error("Unable to delete file (possibly due to unclosed InputStream or Reader on the file): " + tempFile.getAbsolutePath());
}
}
}
}
/**
* @see org.kuali.kfs.sys.batch.service.impl.InitiateDirectoryImpl#getRequiredDirectoryNames()
*/
@Override
public List<String> getRequiredDirectoryNames() {
return new ArrayList<String>(){{add(getTempDirectoryName());}};
}
}