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.ByteArrayInputStream;
019 import java.io.File;
020 import java.io.FileNotFoundException;
021 import java.io.FileWriter;
022 import java.io.FilenameFilter;
023 import java.io.IOException;
024 import java.io.InputStream;
025 import java.net.MalformedURLException;
026 import java.net.URL;
027 import java.util.ArrayList;
028 import java.util.List;
029
030 import javax.xml.XMLConstants;
031 import javax.xml.transform.Source;
032 import javax.xml.transform.stream.StreamSource;
033 import javax.xml.validation.Schema;
034 import javax.xml.validation.SchemaFactory;
035 import javax.xml.validation.Validator;
036
037 import org.apache.commons.digester.Digester;
038 import org.apache.commons.digester.Rules;
039 import org.apache.commons.digester.xmlrules.DigesterLoader;
040 import org.apache.commons.lang.StringUtils;
041 import org.kuali.kfs.sys.KFSConstants;
042 import org.kuali.kfs.sys.KFSKeyConstants;
043 import org.kuali.kfs.sys.KFSConstants.SystemGroupParameterNames;
044 import org.kuali.kfs.sys.batch.BatchInputFileType;
045 import org.kuali.kfs.sys.batch.service.BatchInputFileService;
046 import org.kuali.kfs.sys.context.SpringContext;
047 import org.kuali.kfs.sys.exception.FileStorageException;
048 import org.kuali.kfs.sys.exception.ParseException;
049 import org.kuali.kfs.sys.exception.XmlErrorHandler;
050 import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
051 import org.kuali.rice.kim.bo.Person;
052 import org.kuali.rice.kim.service.KIMServiceLocator;
053 import org.kuali.rice.kns.exception.AuthorizationException;
054 import org.kuali.rice.kns.service.ParameterService;
055 import org.kuali.rice.kns.util.GlobalVariables;
056 import org.kuali.rice.kns.util.ObjectUtils;
057 import org.springframework.core.io.UrlResource;
058 import org.xml.sax.SAXException;
059
060 /**
061 * Provides batch input file management, including listing files, parsing, downloading, storing, and deleting.
062 */
063 public class BatchInputFileServiceImpl implements BatchInputFileService {
064 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BatchInputFileServiceImpl.class);
065
066 /**
067 * Delegates to the batch input file type to parse the file.
068 *
069 * @see org.kuali.kfs.sys.batch.service.BatchInputFileService#parse(org.kuali.kfs.sys.batch.BatchInputFileType, byte[])
070 */
071 public Object parse(BatchInputFileType batchInputFileType, byte[] fileByteContent) {
072 try {
073 return batchInputFileType.parse(fileByteContent);
074 }
075 catch (ParseException e) {
076 LOG.error("Error encountered parsing file", e);
077 throw e;
078 }
079 }
080
081 /**
082 * Defers to batch type to do any validation on the parsed contents.
083 *
084 * @see org.kuali.kfs.sys.batch.service.BatchInputFileService#validate(org.kuali.kfs.sys.batch.BatchInputFileType, java.lang.Object)
085 */
086 public boolean validate(BatchInputFileType batchInputFileType, Object parsedObject) {
087 if (batchInputFileType == null || parsedObject == null) {
088 LOG.error("an invalid(null) argument was given");
089 throw new IllegalArgumentException("an invalid(null) argument was given");
090 }
091
092 boolean contentsValid = true;
093 contentsValid = batchInputFileType.validate(parsedObject);
094 return contentsValid;
095 }
096
097 /**
098 * @see org.kuali.kfs.sys.batch.service.BatchInputFileService#save(org.kuali.rice.kim.bo.Person,
099 * org.kuali.kfs.sys.batch.BatchInputFileType, java.lang.String, java.io.InputStream)
100 */
101 public String save(Person user, BatchInputFileType batchInputFileType, String fileUserIdentifier, InputStream fileContents, Object parsedObject) throws AuthorizationException, FileStorageException {
102 if (user == null || batchInputFileType == null || fileContents == null) {
103 LOG.error("an invalid(null) argument was given");
104 throw new IllegalArgumentException("an invalid(null) argument was given");
105 }
106
107 if (!isFileUserIdentifierProperlyFormatted(fileUserIdentifier)) {
108 LOG.error("The following file user identifer was not properly formatted: " + fileUserIdentifier);
109 throw new IllegalArgumentException("The following file user identifer was not properly formatted: " + fileUserIdentifier);
110 }
111
112 // defer to batch input type to add any security or other needed information to the file name
113 String saveFileName = batchInputFileType.getDirectoryPath() + "/" + batchInputFileType.getFileName(user.getPrincipalName(), parsedObject, fileUserIdentifier);
114 saveFileName += "." + batchInputFileType.getFileExtension();
115
116 // consruct the file object and check for existence
117 File fileToSave = new File(saveFileName);
118 if (fileToSave.exists()) {
119 LOG.error("cannot store file, name already exists " + saveFileName);
120 throw new FileStorageException("Cannot store file because the name " + saveFileName + " already exists on the file system.");
121 }
122
123 try {
124 FileWriter fileWriter = new FileWriter(fileToSave);
125 while (fileContents.available() > 0) {
126 fileWriter.write(fileContents.read());
127 }
128 fileWriter.flush();
129 fileWriter.close();
130
131 createDoneFile(fileToSave);
132
133 batchInputFileType.process(saveFileName, parsedObject);
134 }
135 catch (IOException e) {
136 LOG.error("unable to save contents to file " + saveFileName, e);
137 throw new RuntimeException("errors encountered while writing file " + saveFileName, e);
138 }
139
140 return saveFileName;
141 }
142
143 /**
144 * Creates a '.done' file with the name of the batch file.
145 */
146 protected void createDoneFile(File batchFile) {
147 File doneFile = generateDoneFileObject(batchFile);
148 String doneFileName = doneFile.getName();
149
150 if (!doneFile.exists()) {
151 boolean doneFileCreated = false;
152 try {
153 doneFileCreated = doneFile.createNewFile();
154 }
155 catch (IOException e) {
156 LOG.error("unable to create done file " + doneFileName, e);
157 throw new RuntimeException("Errors encountered while saving the file: Unable to create .done file " + doneFileName, e);
158 }
159
160 if (!doneFileCreated) {
161 LOG.error("unable to create done file " + doneFileName);
162 throw new RuntimeException("Errors encountered while saving the file: Unable to create .done file " + doneFileName);
163 }
164 }
165 }
166
167 /**
168 * This method is responsible for creating a File object that represents the done file. The real file represented on disk may
169 * not exist
170 *
171 * @param batchInputFile
172 * @return a File object representing the done file. The real file may not exist on disk, but the return value can be used to
173 * create that file.
174 */
175 protected File generateDoneFileObject(File batchInputFile) {
176 String doneFileName = StringUtils.substringBeforeLast(batchInputFile.getPath(), ".") + ".done";
177 File doneFile = new File(doneFileName);
178 return doneFile;
179 }
180
181 /**
182 * @see org.kuali.kfs.sys.batch.service.BatchInputFileService#isBatchInputTypeActive(org.kuali.kfs.sys.batch.BatchInputFileType)
183 */
184 public boolean isBatchInputTypeActive(BatchInputFileType batchInputFileType) {
185 if (batchInputFileType == null) {
186 LOG.error("an invalid(null) argument was given");
187 throw new IllegalArgumentException("an invalid(null) argument was given");
188 }
189
190 List<String> activeInputTypes = SpringContext.getBean(ParameterService.class).getParameterValues(KfsParameterConstants.FINANCIAL_SYSTEM_BATCH.class, SystemGroupParameterNames.ACTIVE_INPUT_TYPES_PARAMETER_NAME);
191
192 boolean activeBatchType = false;
193 if (activeInputTypes.size() > 0 && activeInputTypes.contains(batchInputFileType.getFileTypeIdentifer())) {
194 activeBatchType = true;
195 }
196
197 return activeBatchType;
198 }
199
200 /**
201 * Fetches workgroup for batch type from system parameter and verifies user is a member. Then a list of all files for the batch
202 * type are retrieved. For each file, the file and user is sent through the checkAuthorization method of the batch input type
203 * implementation for finer grained security. If the method returns true, the filename is added to the user's list.
204 *
205 * @see org.kuali.kfs.sys.batch.service.BatchInputFileService#listBatchTypeFilesForUser(org.kuali.kfs.sys.batch.BatchInputFileType,
206 * org.kuali.rice.kim.bo.Person)
207 */
208 public List<String> listBatchTypeFilesForUser(BatchInputFileType batchInputFileType, Person user) throws AuthorizationException {
209 if (batchInputFileType == null || user == null) {
210 LOG.error("an invalid(null) argument was given");
211 throw new IllegalArgumentException("an invalid(null) argument was given");
212 }
213
214 File[] filesInBatchDirectory = listFilesInBatchTypeDirectory(batchInputFileType);
215
216 List<String> userFileNamesList = new ArrayList<String>();
217 List<File> userFileList = listBatchTypeFilesForUserAsFiles(batchInputFileType, user);
218
219 for (File userFile : userFileList) {
220 userFileNamesList.add(userFile.getAbsolutePath());
221 }
222
223 return userFileNamesList;
224 }
225
226 protected List<File> listBatchTypeFilesForUserAsFiles(BatchInputFileType batchInputFileType, Person user) throws AuthorizationException {
227 File[] filesInBatchDirectory = listFilesInBatchTypeDirectory(batchInputFileType);
228
229 List<File> userFileList = new ArrayList<File>();
230 if (filesInBatchDirectory != null) {
231 for (int i = 0; i < filesInBatchDirectory.length; i++) {
232 File batchFile = filesInBatchDirectory[i];
233 String fileExtension = StringUtils.substringAfterLast(batchFile.getName(), ".");
234 if (batchInputFileType.getFileExtension().equals(fileExtension)) {
235 if (user.getPrincipalName().equals(batchInputFileType.getAuthorPrincipalName(batchFile))) {
236 userFileList.add(batchFile);
237 }
238 }
239 }
240 }
241 return userFileList;
242 }
243
244 /**
245 * Returns List of filenames for existing files in the directory given by the batch input type.
246 */
247 protected File[] listFilesInBatchTypeDirectory(BatchInputFileType batchInputFileType) {
248 File batchTypeDirectory = new File(batchInputFileType.getDirectoryPath());
249 return batchTypeDirectory.listFiles();
250 }
251
252 /**
253 * @see org.kuali.kfs.sys.batch.service.BatchInputFileService#listInputFileNamesWithDoneFile(org.kuali.kfs.sys.batch.BatchInputFileType)
254 */
255 public List<String> listInputFileNamesWithDoneFile(BatchInputFileType batchInputFileType) {
256 if (batchInputFileType == null) {
257 LOG.error("an invalid(null) argument was given");
258 throw new IllegalArgumentException("an invalid(null) argument was given");
259 }
260
261 File batchTypeDirectory = new File(batchInputFileType.getDirectoryPath());
262 File[] doneFiles = batchTypeDirectory.listFiles(new DoneFilenameFilter());
263
264 List<String> batchInputFiles = new ArrayList<String>();
265 for (int i = 0; i < doneFiles.length; i++) {
266 File doneFile = doneFiles[i];
267 File dataFile = new File(StringUtils.substringBeforeLast(doneFile.getPath(), ".") + "." + batchInputFileType.getFileExtension());
268 if (dataFile.exists()) {
269 batchInputFiles.add(dataFile.getPath());
270 }
271 }
272
273 return batchInputFiles;
274 }
275
276 /**
277 * Retrieves files in a directory with the .done extension.
278 */
279 protected class DoneFilenameFilter implements FilenameFilter {
280 /**
281 * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
282 */
283 public boolean accept(File dir, String name) {
284 return name.endsWith(".done");
285 }
286 }
287
288 /**
289 * For this implementation, a file user identifier must consist of letters and digits
290 *
291 * @see org.kuali.kfs.sys.batch.service.BatchInputFileService#isFileUserIdentifierProperlyFormatted(java.lang.String)
292 */
293 public boolean isFileUserIdentifierProperlyFormatted(String fileUserIdentifier) {
294 if(ObjectUtils.isNull(fileUserIdentifier)) {
295 return false;
296 }
297 for (int i = 0; i < fileUserIdentifier.length(); i++) {
298 char c = fileUserIdentifier.charAt(i);
299 if (!(Character.isLetterOrDigit(c))) {
300 return false;
301 }
302 }
303 return true;
304 }
305 }
306