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