001/** 002 * Copyright 2005-2015 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.rice.krad.labs.fileUploads; 017 018import org.apache.commons.lang.StringUtils; 019import org.apache.commons.lang.exception.ExceptionUtils; 020import org.kuali.rice.core.api.CoreApiServiceLocator; 021import org.kuali.rice.core.api.impex.xml.CompositeXmlDocCollection; 022import org.kuali.rice.core.api.impex.xml.FileXmlDocCollection; 023import org.kuali.rice.core.api.impex.xml.XmlDoc; 024import org.kuali.rice.core.api.impex.xml.XmlDocCollection; 025import org.kuali.rice.core.api.impex.xml.ZipXmlDocCollection; 026import org.kuali.rice.krad.util.GlobalVariables; 027import org.kuali.rice.krad.web.controller.UifControllerBase; 028import org.kuali.rice.krad.web.form.UifFormBase; 029import org.springframework.stereotype.Controller; 030import org.springframework.validation.BindingResult; 031import org.springframework.web.bind.annotation.ModelAttribute; 032import org.springframework.web.bind.annotation.RequestMapping; 033import org.springframework.web.bind.annotation.RequestMethod; 034import org.springframework.web.multipart.MultipartFile; 035import org.springframework.web.servlet.ModelAndView; 036 037import javax.servlet.http.HttpServletRequest; 038import javax.servlet.http.HttpServletResponse; 039import java.io.File; 040import java.io.FileOutputStream; 041import java.io.IOException; 042import java.util.ArrayList; 043import java.util.Collection; 044import java.util.List; 045 046/** 047 * Controller for the XML Ingester View 048 * 049 * <p> 050 * Displays the initial Ingester view page and processes file upload requests. 051 * </p> 052 * 053 * @author Kuali Rice Team (rice.collab@kuali.org) 054 * 055 */ 056@Controller 057@RequestMapping(value = "/ingester") 058public class XmlIngesterController extends UifControllerBase { 059 060 /** 061 * @see org.kuali.rice.krad.web.controller.UifControllerBase#createInitialForm(javax.servlet.http.HttpServletRequest) 062 */ 063 @Override 064 protected XmlIngesterForm createInitialForm() { 065 return new XmlIngesterForm(); 066 } 067 068 @Override 069 @RequestMapping(params = "methodToCall=start") 070 public ModelAndView start(UifFormBase form) { 071 XmlIngesterForm ingesterForm = (XmlIngesterForm)form; 072 073 return super.start(ingesterForm); 074 } 075 076 @RequestMapping(method = RequestMethod.POST, params = "methodToCall=upload") 077 public ModelAndView upload(@ModelAttribute("KualiForm") XmlIngesterForm ingesterForm, BindingResult result, 078 HttpServletRequest request, HttpServletResponse response) { 079 List<File> tempFiles = new ArrayList<File>(); 080 List<XmlDocCollection> collections = copyInputFiles(ingesterForm.getFiles(), tempFiles); 081 try { 082 if (collections.size() == 0) { 083 String message = "No valid files to ingest"; 084 GlobalVariables.getMessageMap().putErrorForSectionId(XmlIngesterConstants.INGESTER_SECTION_ID, XmlIngesterConstants.ERROR_INGESTER_NO_VALID_FILES); 085 } else { 086 if (ingestFiles(collections) == 0) { 087 // String message = "No xml docs ingested"; 088 GlobalVariables.getMessageMap().putErrorForSectionId(XmlIngesterConstants.INGESTER_SECTION_ID, XmlIngesterConstants.ERROR_INGESTER_NO_XMLS); 089 } 090 } 091 } finally { 092 if (tempFiles.size() > 0) { 093 for (File tempFile : tempFiles) 094 { 095 if (!tempFile.delete()) 096 { 097 //LOG.warn("Error deleting temp file: " + tempFile); 098 } 099 } 100 } 101 } 102 return getModelAndView(ingesterForm); 103 } 104 105 /** 106 * Copies the MultipartFiles into an XmlDocCollection list 107 * 108 * <p> 109 * Reads each of the input files into temporary files to get File reference needed 110 * to create FileXmlDocCollection objects. Also verifies that only .xml or .zip files are 111 * to be processed. 112 * </p> 113 * 114 * @param fileList list of MultipartFiles selected for ingestion 115 * @param tempFiles temporary files used to get File reference 116 * 117 * @return uploaded files in a List of XmlDocCollections 118 */ 119 protected List<XmlDocCollection> copyInputFiles(List<MultipartFile> fileList, List<File> tempFiles){ 120 List<XmlDocCollection> collections = new ArrayList<XmlDocCollection>(); 121 for (MultipartFile file : fileList) { 122 if (file == null || StringUtils.isBlank(file.getOriginalFilename())) { 123 continue; 124 } 125 126 // Need to copy into temp file get File reference because XmlDocs based on ZipFile 127 // can't be constructed without a file reference. 128 FileOutputStream fos = null; 129 File temp = null; 130 try{ 131 temp = File.createTempFile("ingester", null); 132 tempFiles.add(temp); 133 fos = new FileOutputStream(temp); 134 fos.write(file.getBytes()); 135 } catch (IOException ioe) { 136 GlobalVariables.getMessageMap().putErrorForSectionId(XmlIngesterConstants.INGESTER_SECTION_ID, 137 XmlIngesterConstants.ERROR_INGESTER_COPY_FILE , file.getOriginalFilename(), ExceptionUtils.getFullStackTrace(ioe)); 138 continue; 139 } finally{ 140 if (fos != null) { 141 try{ 142 fos.close(); 143 } catch (IOException ioe){ 144 // LOG.error("Error closing temp file output stream: " + temp, ioe); 145 } 146 } 147 } 148 149 // only .zip and .xml files will be processed 150 if (file.getOriginalFilename().toLowerCase().endsWith(".zip")) 151 { 152 try { 153 collections.add(new ZipXmlDocCollection(temp)); 154 } catch (IOException ioe) { 155 GlobalVariables.getMessageMap().putErrorForSectionId(XmlIngesterConstants.INGESTER_SECTION_ID, XmlIngesterConstants.ERROR_INGESTER_LOAD_FILE, file.getOriginalFilename()); 156 } 157 } else if (file.getOriginalFilename().endsWith(".xml")) { 158 collections.add(new FileXmlDocCollection(temp, file.getOriginalFilename())); 159 } else { 160 GlobalVariables.getMessageMap().putErrorForSectionId(XmlIngesterConstants.INGESTER_SECTION_ID, XmlIngesterConstants.ERROR_INGESTER_EXTRANEOUS_FILE, file.getOriginalFilename()); 161 } 162 } 163 164 return collections; 165 } 166 167 /** 168 * Ingests the list of files into the system 169 * 170 * @param collections xml documents to be ingested 171 * @return the number of files successfully ingested 172 */ 173 protected int ingestFiles(List<XmlDocCollection> collections){ 174 // wrap in composite collection to make transactional 175 CompositeXmlDocCollection compositeCollection = new CompositeXmlDocCollection(collections); 176 int totalProcessed = 0; 177 List<XmlDocCollection> c = new ArrayList<XmlDocCollection>(1); 178 c.add(compositeCollection); 179 try { 180 // ingest the collection of files 181 Collection<XmlDocCollection> failed = CoreApiServiceLocator.getXmlIngesterService().ingest(c, GlobalVariables.getUserSession().getPrincipalId()); 182 boolean txFailed = failed.size() > 0; 183 if (txFailed) { 184 GlobalVariables.getMessageMap().putErrorForSectionId(XmlIngesterConstants.INGESTER_SECTION_ID, XmlIngesterConstants.ERROR_INGESTER_FAILED); 185 } 186 187 // loop through the results, collecting the error messages for each doc 188 collectIngestionMessages(collections, txFailed); 189 } catch (Exception e) { 190 GlobalVariables.getMessageMap().putErrorForSectionId(XmlIngesterConstants.INGESTER_SECTION_ID, XmlIngesterConstants.ERROR_INGESTER_DURING_INJECT, ExceptionUtils.getFullStackTrace(e)); 191 } 192 193 return totalProcessed; 194 } 195 196 /** 197 * loop through the results, returns the number of successfully processed files 198 * 199 * <p> 200 * Also collects the error messages for each doc 201 * </p> 202 * 203 * @param collections the list of processed documents 204 * @param txFailed flag whether upload contained errors 205 206 * @return the number of files successfully ingested 207 */ 208 protected int collectIngestionMessages(List<XmlDocCollection> collections, boolean txFailed){ 209 int totalProcessed = 0; 210 for (XmlDocCollection collection1 : collections) 211 { 212 List<? extends XmlDoc> docs = collection1.getXmlDocs(); 213 for (XmlDoc doc1 : docs) 214 { 215 if (doc1.isProcessed()) 216 { 217 if (!txFailed) 218 { 219 totalProcessed++; 220 GlobalVariables.getMessageMap().putInfoForSectionId(XmlIngesterConstants.INGESTER_SECTION_ID, XmlIngesterConstants.INFO_INGESTER_SUCCESS, doc1.getName(),doc1.getProcessingMessage()); 221 } else { 222 GlobalVariables.getMessageMap().putErrorForSectionId(XmlIngesterConstants.INGESTER_SECTION_ID, XmlIngesterConstants.ERROR_INGESTER_ROLLEDBACK, doc1.getName(),doc1.getProcessingMessage()); 223 } 224 } else 225 {GlobalVariables.getMessageMap().putErrorForSectionId(XmlIngesterConstants.INGESTER_SECTION_ID, XmlIngesterConstants.ERROR_INGESTER_FAILED_XML, doc1.getName(),doc1.getProcessingMessage()); 226 } 227 } 228 } 229 return totalProcessed; 230 } 231 232 @RequestMapping(method = RequestMethod.POST, params = "methodToCall=close") 233 public ModelAndView close(@ModelAttribute("KualiForm") XmlIngesterForm ingesterForm, BindingResult result, 234 HttpServletRequest request, HttpServletResponse response) { 235 236 return null; 237 } 238 239}