001 /* 002 * Copyright 2011 The Kuali Foundation. 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.kfs.sys.batch.service.impl; 017 018 import java.io.File; 019 import java.io.FileInputStream; 020 import java.io.FileNotFoundException; 021 import java.io.FileOutputStream; 022 import java.io.IOException; 023 import java.io.InputStream; 024 import java.io.OutputStream; 025 import java.util.ArrayList; 026 import java.util.Collections; 027 import java.util.Date; 028 import java.util.HashMap; 029 import java.util.LinkedHashMap; 030 import java.util.List; 031 import java.util.Map; 032 import java.util.Set; 033 034 import org.apache.commons.lang.StringUtils; 035 import org.kuali.kfs.sys.KFSConstants; 036 import org.kuali.kfs.sys.KFSKeyConstants; 037 import org.kuali.kfs.sys.KFSConstants.SystemGroupParameterNames; 038 import org.kuali.kfs.sys.batch.BatchInputFileSetType; 039 import org.kuali.kfs.sys.batch.service.BatchInputFileSetService; 040 import org.kuali.kfs.sys.context.SpringContext; 041 import org.kuali.kfs.sys.exception.FileStorageException; 042 import org.kuali.kfs.sys.service.impl.KfsParameterConstants; 043 import org.kuali.rice.kim.bo.Person; 044 import org.kuali.rice.kim.service.KIMServiceLocator; 045 import org.kuali.rice.kns.exception.AuthorizationException; 046 import org.kuali.rice.kns.exception.ValidationException; 047 import org.kuali.rice.kns.service.DateTimeService; 048 import org.kuali.rice.kns.service.KualiConfigurationService; 049 import org.kuali.rice.kns.service.ParameterService; 050 import org.kuali.rice.kns.util.GlobalVariables; 051 052 /** 053 * Base implementation to manipulate batch input file sets from the batch upload screen 054 */ 055 public class BatchInputFileSetServiceImpl implements BatchInputFileSetService { 056 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BatchInputFileSetServiceImpl.class); 057 058 private KualiConfigurationService kualiConfigurationService; 059 060 /** 061 * Generates the file name of a file (not the done file) 062 * 063 * @param user the user who uploaded or will upload the file 064 * @param inputType the file set type 065 * @param fileUserIdentifier the file identifier 066 * @param fileType the file type 067 * @return the file name, starting with the directory path 068 */ 069 protected String generateFileName(Person user, BatchInputFileSetType inputType, String fileUserIdentifier, String fileType, Date creationDate) { 070 if (!isFileUserIdentifierProperlyFormatted(fileUserIdentifier)) { 071 throw new IllegalArgumentException("The file set identifier is not properly formatted: " + fileUserIdentifier); 072 } 073 return inputType.getDirectoryPath(fileType) + File.separator + inputType.getFileName(fileType, user.getPrincipalName(), fileUserIdentifier, creationDate); 074 } 075 076 /** 077 * Generates the file name of a file (not the done file) 078 * 079 * @param user the user who uploaded or will upload the file 080 * @param inputType the file set type 081 * @param fileUserIdentifier the file identifier 082 * @param fileType the file type 083 * @return the file name, starting with the directory path 084 */ 085 protected String generateTempFileName(Person user, BatchInputFileSetType inputType, String fileUserIdentifier, String fileType, Date creationDate) { 086 if (!isFileUserIdentifierProperlyFormatted(fileUserIdentifier)) { 087 throw new IllegalArgumentException("The file set identifier is not properly formatted: " + fileUserIdentifier); 088 } 089 return getTempDirectoryName() + File.separator + inputType.getFileName(fileType, user.getPrincipalName(), fileUserIdentifier, creationDate); 090 } 091 /** 092 * Generates the file name of the done file, if supported by the underlying input type 093 * 094 * @param user the user who uploaded or will upload the file 095 * @param inputType the file set type 096 * @param fileUserIdentifier the file identifier 097 * @param fileType the file type 098 * @return the file name, starting with the directory path 099 */ 100 protected String generateDoneFileName(Person user, BatchInputFileSetType inputType, String fileUserIdentifier, Date creationDate) { 101 if (!isFileUserIdentifierProperlyFormatted(fileUserIdentifier)) { 102 throw new IllegalArgumentException("The file set identifier is not properly formatted: " + fileUserIdentifier); 103 } 104 return inputType.getDoneFileDirectoryPath() + File.separator + inputType.getDoneFileName(user, fileUserIdentifier, creationDate); 105 } 106 107 /** 108 * @see org.kuali.kfs.sys.batch.service.BatchInputFileSetService#isBatchInputTypeActive(org.kuali.kfs.sys.batch.BatchInputFileSetType) 109 */ 110 public boolean isBatchInputTypeActive(BatchInputFileSetType batchInputFileSetType) { 111 if (batchInputFileSetType == null) { 112 LOG.error("an invalid(null) argument was given"); 113 throw new IllegalArgumentException("an invalid(null) argument was given"); 114 } 115 List<String> activeInputTypes = SpringContext.getBean(ParameterService.class).getParameterValues(KfsParameterConstants.FINANCIAL_SYSTEM_BATCH.class, SystemGroupParameterNames.ACTIVE_INPUT_TYPES_PARAMETER_NAME); 116 117 boolean activeBatchType = false; 118 if (activeInputTypes.size() > 0 && activeInputTypes.contains(batchInputFileSetType.getFileSetTypeIdentifer())) { 119 activeBatchType = true; 120 } 121 122 return activeBatchType; 123 } 124 125 /** 126 * @see org.kuali.kfs.sys.batch.service.BatchInputFileSetService#save(org.kuali.rice.kim.bo.Person, 127 * org.kuali.kfs.sys.batch.BatchInputFileSetType, java.lang.String, java.util.Map) 128 */ 129 public Map<String, String> save(Person user, BatchInputFileSetType inputType, String fileUserIdentifier, Map<String, InputStream> typeToStreamMap) throws AuthorizationException, FileStorageException { 130 Date creationDate = SpringContext.getBean(DateTimeService.class).getCurrentDate(); 131 // check user is authorized to upload a file for the batch type 132 Map<String, File> typeToTempFiles = copyStreamsToTemporaryDirectory(user, inputType, fileUserIdentifier, typeToStreamMap, creationDate); 133 134 // null the map, because it's full of exhausted input streams that are useless 135 typeToStreamMap = null; 136 137 if (!inputType.validate(typeToTempFiles)) { 138 deleteTempFiles(typeToTempFiles); 139 LOG.error("Upload file validation failed for user " + user.getName() + " identifier " + fileUserIdentifier); 140 throw new ValidationException("File validation failed"); 141 } 142 143 byte[] buf = new byte[1024]; 144 145 Map<String, String> typeToFileNames = new LinkedHashMap<String, String>(); 146 Map<String, File> typeToFiles = new LinkedHashMap<String, File>(); 147 try { 148 for (String fileType : inputType.getFileTypes()) { 149 File tempFile = typeToTempFiles.get(fileType); 150 String saveFileName = inputType.getDirectoryPath(fileType) + File.separator + tempFile.getName(); 151 try { 152 InputStream fileContents = new FileInputStream(tempFile); 153 File fileToSave = new File(saveFileName); 154 155 copyInputStreamToFile(fileContents, fileToSave, buf); 156 fileContents.close(); 157 typeToFileNames.put(fileType, saveFileName); 158 typeToFiles.put(fileType, fileToSave); 159 } 160 catch (IOException e) { 161 LOG.error("unable to save contents to file " + saveFileName, e); 162 throw new RuntimeException("errors encountered while writing file " + saveFileName, e); 163 } 164 } 165 } 166 finally { 167 deleteTempFiles(typeToTempFiles); 168 } 169 170 String doneFileName = inputType.getDoneFileDirectoryPath() + File.separator + inputType.getDoneFileName(user, fileUserIdentifier, creationDate); 171 File doneFile = new File(doneFileName); 172 try { 173 doneFile.createNewFile(); 174 175 typeToFiles.put(KFSConstants.DONE_FILE_TYPE, doneFile); 176 } 177 catch (IOException e) { 178 LOG.error("unable to create done file", e); 179 throw new RuntimeException("unable to create done file", e); 180 } 181 182 inputType.process(typeToFiles); 183 184 return typeToFileNames; 185 } 186 187 protected Map<String, File> copyStreamsToTemporaryDirectory(Person user, BatchInputFileSetType inputType, 188 String fileUserIdentifier, Map<String, InputStream> typeToStreamMap, Date creationDate) throws FileStorageException { 189 Map<String, File> tempFiles = new HashMap<String, File>(); 190 191 String tempDirectoryName = getTempDirectoryName(); 192 File tempDirectory = new File(tempDirectoryName); 193 if (!tempDirectory.exists() || !tempDirectory.isDirectory()) { 194 LOG.error("Temporary Directory " + tempDirectoryName + " does not exist or is not a directory"); 195 throw new RuntimeException("Temporary Directory " + tempDirectoryName + " does not exist or is not a directory"); 196 } 197 198 byte[] buf = new byte[1024]; 199 200 try { 201 for (String fileType : inputType.getFileTypes()) { 202 String tempFileName = generateTempFileName(user, inputType, fileUserIdentifier, fileType, creationDate); 203 InputStream source = typeToStreamMap.get(fileType); 204 File tempFile = new File(tempFileName); 205 copyInputStreamToFile(source, tempFile, buf); 206 tempFiles.put(fileType, tempFile); 207 } 208 } 209 catch (IOException e) { 210 LOG.error("Error creating temporary files", e); 211 throw new FileStorageException("Error creating temporary files"); 212 213 } 214 return tempFiles; 215 } 216 217 protected void copyInputStreamToFile(InputStream source, File outputFile, byte[] buf) throws IOException { 218 OutputStream out = new FileOutputStream(outputFile); 219 int readBytes = source.read(buf); 220 while (readBytes != -1) { 221 out.write(buf, 0, readBytes); 222 readBytes = source.read(buf); 223 } 224 out.flush(); 225 out.close(); 226 } 227 228 protected String getTempDirectoryName() { 229 String tempDirectoryName = getKualiConfigurationService().getPropertyString(KFSConstants.TEMP_DIRECTORY_KEY); 230 return tempDirectoryName; 231 } 232 233 /** 234 * @see org.kuali.kfs.sys.batch.service.BatchInputFileSetService#isFileUserIdentifierProperlyFormatted(java.lang.String) 235 */ 236 public boolean isFileUserIdentifierProperlyFormatted(String fileUserIdentifier) { 237 if (fileUserIdentifier == null) { 238 return false; 239 } 240 241 for (int i = 0; i < fileUserIdentifier.length(); i++) { 242 char c = fileUserIdentifier.charAt(i); 243 if (!(Character.isLetterOrDigit(c))) { 244 return false; 245 } 246 } 247 return true; 248 } 249 250 protected KualiConfigurationService getKualiConfigurationService() { 251 return kualiConfigurationService; 252 } 253 254 public void setKualiConfigurationService(KualiConfigurationService kualiConfigurationService) { 255 this.kualiConfigurationService = kualiConfigurationService; 256 } 257 258 protected void deleteTempFiles(Map<String, File> typeToTempFiles) { 259 for (File tempFile : typeToTempFiles.values()) { 260 if (tempFile.exists()) { 261 boolean deletionSuccessful = tempFile.delete(); 262 if (!deletionSuccessful) { 263 LOG.error("Unable to delete file (possibly due to unclosed InputStream or Reader on the file): " + tempFile.getAbsolutePath()); 264 } 265 } 266 } 267 } 268 } 269