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