001/* 002 * Copyright 2007 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.batch.service.impl; 017 018import java.io.File; 019import java.io.FileOutputStream; 020import java.io.FileWriter; 021import java.io.FilenameFilter; 022import java.io.IOException; 023import java.io.InputStream; 024import java.util.ArrayList; 025import java.util.List; 026 027import org.apache.commons.lang.StringUtils; 028import org.kuali.ole.sys.OLEConstants.SystemGroupParameterNames; 029import org.kuali.ole.sys.batch.BatchInputFileType; 030import org.kuali.ole.sys.batch.service.BatchInputFileService; 031import org.kuali.ole.sys.context.SpringContext; 032import org.kuali.ole.sys.exception.FileStorageException; 033import org.kuali.ole.sys.exception.ParseException; 034import org.kuali.ole.sys.service.impl.OleParameterConstants; 035import org.kuali.rice.coreservice.framework.parameter.ParameterService; 036import org.kuali.rice.kim.api.identity.Person; 037import org.kuali.rice.krad.exception.AuthorizationException; 038import org.kuali.rice.krad.util.ObjectUtils; 039 040/** 041 * Provides batch input file management, including listing files, parsing, downloading, storing, and deleting. 042 */ 043public class BatchInputFileServiceImpl implements BatchInputFileService { 044 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BatchInputFileServiceImpl.class); 045 046 /** 047 * Delegates to the batch input file type to parse the file. 048 * 049 * @see org.kuali.ole.sys.batch.service.BatchInputFileService#parse(org.kuali.ole.sys.batch.BatchInputFileType, byte[]) 050 */ 051 @Override 052 public Object parse(BatchInputFileType batchInputFileType, byte[] fileByteContent) { 053 try { 054 return batchInputFileType.parse(fileByteContent); 055 } 056 catch (ParseException e) { 057 LOG.error("Error encountered parsing file", e); 058 throw e; 059 } 060 } 061 062 /** 063 * Defers to batch type to do any validation on the parsed contents. 064 * 065 * @see org.kuali.ole.sys.batch.service.BatchInputFileService#validate(org.kuali.ole.sys.batch.BatchInputFileType, java.lang.Object) 066 */ 067 @Override 068 public boolean validate(BatchInputFileType batchInputFileType, Object parsedObject) { 069 if (batchInputFileType == null || parsedObject == null) { 070 LOG.error("an invalid(null) argument was given"); 071 throw new IllegalArgumentException("an invalid(null) argument was given"); 072 } 073 074 boolean contentsValid = true; 075 contentsValid = batchInputFileType.validate(parsedObject); 076 return contentsValid; 077 } 078 079 /** 080 * @see org.kuali.ole.sys.batch.service.BatchInputFileService#save(org.kuali.rice.kim.api.identity.Person, 081 * org.kuali.ole.sys.batch.BatchInputFileType, java.lang.String, java.io.InputStream) 082 */ 083 @Override 084 public String save(Person user, BatchInputFileType batchInputFileType, String fileUserIdentifier, InputStream fileContents, Object parsedObject) throws AuthorizationException, FileStorageException { 085 if (user == null || batchInputFileType == null || fileContents == null) { 086 LOG.error("an invalid(null) argument was given"); 087 throw new IllegalArgumentException("an invalid(null) argument was given"); 088 } 089 090 if (!isFileUserIdentifierProperlyFormatted(fileUserIdentifier)) { 091 LOG.error("The following file user identifer was not properly formatted: " + fileUserIdentifier); 092 throw new IllegalArgumentException("The following file user identifer was not properly formatted: " + fileUserIdentifier); 093 } 094 095 // defer to batch input type to add any security or other needed information to the file name 096 String saveFileName = batchInputFileType.getDirectoryPath() + "/" + batchInputFileType.getFileName(user.getPrincipalName(), parsedObject, fileUserIdentifier); 097 if (!StringUtils.isBlank(batchInputFileType.getFileExtension())) { 098 saveFileName += "." + batchInputFileType.getFileExtension(); 099 } 100 101 // consruct the file object and check for existence 102 File fileToSave = new File(saveFileName); 103 if (fileToSave.exists()) { 104 LOG.error("cannot store file, name already exists " + saveFileName); 105 throw new FileStorageException("Cannot store file because the name " + saveFileName + " already exists on the file system."); 106 } 107 108 try { 109 FileOutputStream fos = new FileOutputStream(fileToSave); 110 while(fileContents.available() > 0) { 111 fos.write(fileContents.read()); 112 } 113 fos.flush(); 114 fos.close(); 115 116 createDoneFile(fileToSave, batchInputFileType); 117 118 batchInputFileType.process(saveFileName, parsedObject); 119 } 120 catch (IOException e) { 121 LOG.error("unable to save contents to file " + saveFileName, e); 122 throw new RuntimeException("errors encountered while writing file " + saveFileName, e); 123 } 124 125 return saveFileName; 126 } 127 128 /** 129 * @see org.kuali.ole.sys.batch.service.BatchInputFileService#save(org.kuali.rice.kim.api.identity.Person, 130 * org.kuali.ole.sys.batch.BatchInputFileType, java.lang.String, java.io.InputStream) 131 */ 132 @Override 133 public String save(Person user, BatchInputFileType batchInputFileType, String fileUserIdentifier, InputStream fileContents, Object parsedObject,String destinationFilePath,String extension) throws AuthorizationException, FileStorageException { 134 if (user == null || batchInputFileType == null || fileContents == null) { 135 LOG.error("an invalid(null) argument was given"); 136 throw new IllegalArgumentException("an invalid(null) argument was given"); 137 } 138 139 if (!isFileUserIdentifierProperlyFormatted(fileUserIdentifier)) { 140 LOG.error("The following file user identifer was not properly formatted: " + fileUserIdentifier); 141 throw new IllegalArgumentException("The following file user identifer was not properly formatted: " + fileUserIdentifier); 142 } 143 144 // defer to batch input type to add any security or other needed information to the file name 145 String os = System.getProperty("os.name"); 146 if (LOG.isDebugEnabled()){ 147 LOG.debug("batchInputFileType.getDirectoryPath()"+batchInputFileType.getDirectoryPath()); 148 LOG.debug("destinationFilePath"+destinationFilePath); 149 LOG.debug("--------------file.Separator---------------"+os.toUpperCase()); 150 } 151 String separator = ""; 152 if(os.toUpperCase().contains("WIN")){ 153 separator = "\\"; 154 }else { 155 separator = "/"; 156 } 157 String directory = batchInputFileType.getDirectoryPath(); 158 String[] directoryPath = directory.toLowerCase().split("staging"); 159 String orderType = directoryPath[0]+destinationFilePath; 160 if (LOG.isDebugEnabled()){ 161 LOG.debug("DirectoryPath" + directoryPath[0]); 162 LOG.debug("DirectoryPath" + batchInputFileType.getDirectoryPath()); 163 LOG.debug("orderType" + orderType); 164 } 165 String saveFileName = orderType + "/" + batchInputFileType.getFileName(user.getPrincipalName(), parsedObject, fileUserIdentifier,destinationFilePath); 166 saveFileName += "." + extension;//batchInputFileType.getFileExtension(); 167 168 // consruct the file object and check for existence 169 File fileToSave = new File(saveFileName); 170 if (fileToSave.exists()) { 171 LOG.error("cannot store file, name already exists " + saveFileName); 172 throw new FileStorageException("Cannot store file because the name " + saveFileName + " already exists on the file system."); 173 } 174 175 try { 176 FileWriter fileWriter = new FileWriter(fileToSave); 177 while (fileContents.available() > 0) { 178 fileWriter.write(fileContents.read()); 179 } 180 fileWriter.flush(); 181 fileWriter.close(); 182 183 createDoneFile(fileToSave, batchInputFileType); 184 185 batchInputFileType.process(saveFileName, parsedObject); 186 } 187 catch (IOException e) { 188 LOG.error("unable to save contents to file " + saveFileName, e); 189 throw new RuntimeException("errors encountered while writing file " + saveFileName, e); 190 } 191 192 return saveFileName; 193 } 194 195 /** 196 * Creates a '.done' file with the name of the batch file. 197 */ 198 protected void createDoneFile(File batchFile ,BatchInputFileType batchInputFileType ) { 199 String fileExtension = batchInputFileType.getFileExtension(); 200 File doneFile = generateDoneFileObject(batchFile, fileExtension); 201 String doneFileName = doneFile.getName(); 202 203 if (!doneFile.exists()) { 204 boolean doneFileCreated = false; 205 try { 206 doneFileCreated = doneFile.createNewFile(); 207 } 208 catch (IOException e) { 209 LOG.error("unable to create done file " + doneFileName, e); 210 throw new RuntimeException("Errors encountered while saving the file: Unable to create .done file " + doneFileName, e); 211 } 212 213 if (!doneFileCreated) { 214 LOG.error("unable to create done file " + doneFileName); 215 throw new RuntimeException("Errors encountered while saving the file: Unable to create .done file " + doneFileName); 216 } 217 } 218 } 219 220 /** 221 * This method is responsible for creating a File object that represents the done file. The real file represented on disk may 222 * not exist 223 * 224 * @param batchInputFile 225 * @return a File object representing the done file. The real file may not exist on disk, but the return value can be used to 226 * create that file. 227 */ 228 protected File generateDoneFileObject(File batchInputFile, String fileExtension) { 229 String doneFileName = fileExtension != null ? StringUtils.substringBeforeLast(batchInputFile.getPath(), ".") + ".done" : 230 batchInputFile.getPath() + ".done" ; 231 File doneFile = new File(doneFileName); 232 return doneFile; 233 } 234 235 /** 236 * @see org.kuali.ole.sys.batch.service.BatchInputFileService#isBatchInputTypeActive(org.kuali.ole.sys.batch.BatchInputFileType) 237 */ 238 @Override 239 public boolean isBatchInputTypeActive(BatchInputFileType batchInputFileType) { 240 if (batchInputFileType == null) { 241 LOG.error("an invalid(null) argument was given"); 242 throw new IllegalArgumentException("an invalid(null) argument was given"); 243 } 244 245 List<String> activeInputTypes = new ArrayList<String>( SpringContext.getBean(ParameterService.class).getParameterValuesAsString(OleParameterConstants.FINANCIAL_SYSTEM_BATCH.class, SystemGroupParameterNames.ACTIVE_INPUT_TYPES_PARAMETER_NAME) ); 246 247 boolean activeBatchType = false; 248 if (activeInputTypes.size() > 0 && activeInputTypes.contains(batchInputFileType.getFileTypeIdentifer())) { 249 activeBatchType = true; 250 } 251 252 return activeBatchType; 253 } 254 255 /** 256 * Fetches workgroup for batch type from system parameter and verifies user is a member. Then a list of all files for the batch 257 * type are retrieved. For each file, the file and user is sent through the checkAuthorization method of the batch input type 258 * implementation for finer grained security. If the method returns true, the filename is added to the user's list. 259 * 260 * @see org.kuali.ole.sys.batch.service.BatchInputFileService#listBatchTypeFilesForUser(org.kuali.ole.sys.batch.BatchInputFileType, 261 * org.kuali.rice.kim.api.identity.Person) 262 */ 263 @Override 264 public List<String> listBatchTypeFilesForUser(BatchInputFileType batchInputFileType, Person user) throws AuthorizationException { 265 if (batchInputFileType == null || user == null) { 266 LOG.error("an invalid(null) argument was given"); 267 throw new IllegalArgumentException("an invalid(null) argument was given"); 268 } 269 270 File[] filesInBatchDirectory = listFilesInBatchTypeDirectory(batchInputFileType); 271 272 List<String> userFileNamesList = new ArrayList<String>(); 273 List<File> userFileList = listBatchTypeFilesForUserAsFiles(batchInputFileType, user); 274 275 for (File userFile : userFileList) { 276 userFileNamesList.add(userFile.getAbsolutePath()); 277 } 278 279 return userFileNamesList; 280 } 281 282 protected List<File> listBatchTypeFilesForUserAsFiles(BatchInputFileType batchInputFileType, Person user) throws AuthorizationException { 283 File[] filesInBatchDirectory = listFilesInBatchTypeDirectory(batchInputFileType); 284 285 List<File> userFileList = new ArrayList<File>(); 286 if (filesInBatchDirectory != null) { 287 for (int i = 0; i < filesInBatchDirectory.length; i++) { 288 File batchFile = filesInBatchDirectory[i]; 289 String fileExtension = StringUtils.substringAfterLast(batchFile.getName(), "."); 290 if (StringUtils.isBlank(batchInputFileType.getFileExtension()) || batchInputFileType.getFileExtension().equals(fileExtension)) { 291 if (user.getPrincipalName().equals(batchInputFileType.getAuthorPrincipalName(batchFile))) { 292 userFileList.add(batchFile); 293 } 294 } 295 } 296 } 297 return userFileList; 298 } 299 300 /** 301 * Returns List of filenames for existing files in the directory given by the batch input type. 302 */ 303 protected File[] listFilesInBatchTypeDirectory(BatchInputFileType batchInputFileType) { 304 File batchTypeDirectory = new File(batchInputFileType.getDirectoryPath()); 305 return batchTypeDirectory.listFiles(); 306 } 307 308 /** 309 * @see org.kuali.ole.sys.batch.service.BatchInputFileService#listInputFileNamesWithDoneFile(org.kuali.ole.sys.batch.BatchInputFileType) 310 */ 311 @Override 312 public List<String> listInputFileNamesWithDoneFile(BatchInputFileType batchInputFileType) { 313 if (batchInputFileType == null) { 314 LOG.error("an invalid(null) argument was given"); 315 throw new IllegalArgumentException("an invalid(null) argument was given"); 316 } 317 318 File batchTypeDirectory = new File(batchInputFileType.getDirectoryPath()); 319 File[] doneFiles = batchTypeDirectory.listFiles(new DoneFilenameFilter()); 320 321 if(LOG.isDebugEnabled()){ 322 LOG.debug("batchTypeDirectory ------------------------------------------->" + batchTypeDirectory); 323 LOG.debug("batchInputFileType ------------------------------------------->" + batchInputFileType); 324 LOG.debug("----------------Done File ----------------------->" + doneFiles); 325 } 326 // LOG.info( "doneFiles.length --------------------------------------------->"+doneFiles.length); 327 List<String> batchInputFiles = new ArrayList<String>(); 328 if(doneFiles==null) { 329 return batchInputFiles; 330 } 331 if(doneFiles != null){ 332 LOG.debug("----------Done File Not Null-----------"); 333 for (int i = 0; i < doneFiles.length; i++) { 334 File doneFile = doneFiles[i]; 335 336 String dataFileName = StringUtils.substringBeforeLast(doneFile.getPath(), "."); 337 if (!StringUtils.isBlank(batchInputFileType.getFileExtension())) { 338 dataFileName += "." + batchInputFileType.getFileExtension(); 339 } 340 File dataFile = new File(dataFileName); 341 if(LOG.isDebugEnabled()){ 342 LOG.debug("doneFile ------------------------------------------->" + doneFile); 343 LOG.debug("DataFile being processed ------------------------------------------->" + dataFile); 344 } 345 346 if (dataFile.exists()) { 347 batchInputFiles.add(dataFile.getPath()); 348 } 349 } 350 } 351 352 return batchInputFiles; 353 } 354 355 /** 356 * Retrieves files in a directory with the .done extension. 357 */ 358 protected class DoneFilenameFilter implements FilenameFilter { 359 /** 360 * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String) 361 */ 362 @Override 363 public boolean accept(File dir, String name) { 364 return name.endsWith(".done"); 365 } 366 } 367 368 /** 369 * For this implementation, a file user identifier must consist of letters and digits 370 * 371 * @see org.kuali.ole.sys.batch.service.BatchInputFileService#isFileUserIdentifierProperlyFormatted(java.lang.String) 372 */ 373 @Override 374 public boolean isFileUserIdentifierProperlyFormatted(String fileUserIdentifier) { 375 if(ObjectUtils.isNull(fileUserIdentifier)) { 376 return false; 377 } 378 for (int i = 0; i < fileUserIdentifier.length(); i++) { 379 char c = fileUserIdentifier.charAt(i); 380 if (!(Character.isLetterOrDigit(c))) { 381 return false; 382 } 383 } 384 return true; 385 } 386} 387