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