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