/** * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at the * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Initial code contributed and copyrighted by<br> * frentix GmbH, http://www.frentix.com * <p> */ package org.olat.course.assessment.bulk; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.apache.commons.io.IOUtils; import org.olat.core.commons.modules.bc.vfs.OlatRootFileImpl; import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItemContainer; import org.olat.core.gui.components.form.flexible.elements.FileElement; import org.olat.core.gui.components.form.flexible.elements.SingleSelection; import org.olat.core.gui.components.form.flexible.elements.TextElement; import org.olat.core.gui.components.form.flexible.impl.Form; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.wizard.StepFormBasicController; import org.olat.core.gui.control.generic.wizard.StepsEvent; import org.olat.core.gui.control.generic.wizard.StepsRunContext; import org.olat.core.util.FileUtils; import org.olat.core.util.StringHelper; import org.olat.core.util.WebappHelper; import org.olat.core.util.vfs.LocalImpl; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSManager; import org.olat.course.assessment.model.BulkAssessmentDatas; import org.olat.course.assessment.model.BulkAssessmentRow; import org.olat.course.assessment.model.BulkAssessmentSettings; import org.olat.course.nodes.AssessableCourseNode; /** * * Initial date: 18.11.2013<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public class DataStepForm extends StepFormBasicController { private static final String[] keys = new String[] {"tab","comma"}; private TextElement dataEl; private FileElement returnFileEl; private SingleSelection delimiter; private OlatRootFileImpl targetArchive; private BulkAssessmentDatas savedDatas; private final AssessableCourseNode courseNode; private OlatRootFolderImpl bulkAssessmentTmpDir; public DataStepForm(UserRequest ureq, WindowControl wControl, StepsRunContext runContext, Form rootForm) { super(ureq, wControl, rootForm, runContext, LAYOUT_VERTICAL, null); courseNode = (AssessableCourseNode)getFromRunContext("courseNode"); initForm(ureq); } public DataStepForm(UserRequest ureq, WindowControl wControl, AssessableCourseNode courseNode, BulkAssessmentDatas savedDatas, StepsRunContext runContext, Form rootForm) { super(ureq, wControl, rootForm, runContext, LAYOUT_VERTICAL, null); this.savedDatas = savedDatas; this.courseNode = courseNode; addToRunContext("courseNode", courseNode); initForm(ureq); } @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { // hide data input field in case the element does not have any score, passed or comment field enabled BulkAssessmentSettings settings = new BulkAssessmentSettings(courseNode); boolean onlyReturnFiles = (!settings.isHasScore() && !settings.isHasPassed() && !settings.isHasUserComment()); setFormTitle("data.title"); if (!onlyReturnFiles) { setFormDescription("data.description"); } setFormContextHelp("Using Course Tools#bulkassessment_data"); String dataVal = ""; if(savedDatas != null && StringHelper.containsNonWhitespace(savedDatas.getDataBackupFile())) { OlatRootFileImpl file = new OlatRootFileImpl(savedDatas.getDataBackupFile(), null); InputStream in = file.getInputStream(); try { dataVal = IOUtils.toString(in); } catch (IOException e) { logError("", e); } finally { IOUtils.closeQuietly(in); } } dataEl = uifactory.addTextAreaElement("data", "data", -1, 6, 60, true, dataVal, formLayout); dataEl.showLabel(false); String[] values = new String[] {translate("form.step3.delimiter.tab"),translate("form.step3.delimiter.comma")}; delimiter = uifactory.addRadiosVertical("delimiter", "form.step3.delimiter", formLayout, keys, values); // preset delimiter type to first appearance of either tab or comma when data is available, default to tab for no data int firstComma = dataVal.indexOf(","); int firstTab = dataVal.indexOf("\t"); if (firstComma > -1 && (firstTab == -1 || firstTab > firstComma )) { delimiter.select("comma", true); } else { delimiter.select("tab", true); } // hide data input field in case the element does not have any score, passed or comment field enabled if (onlyReturnFiles) { dataEl.setVisible(false); delimiter.setVisible(false); } // return files only when configured if(settings.isHasReturnFiles()) { returnFileEl = uifactory.addFileElement(getWindowControl(), "returnfiles", "return.files", formLayout); Set<String> mimes = new HashSet<String>(); mimes.add(WebappHelper.getMimeType("file.zip")); returnFileEl.limitToMimeType(mimes, "return.mime", null); if(savedDatas != null && StringHelper.containsNonWhitespace(savedDatas.getReturnFiles())) { targetArchive = new OlatRootFileImpl(savedDatas.getReturnFiles(), null); if(targetArchive.exists()) { returnFileEl.setInitialFile(targetArchive.getBasefile()); } } } } @Override protected void doDispose() { // } @Override protected void formOK(UserRequest ureq) { String val = dataEl.getValue(); if (!StringHelper.containsNonWhitespace(val) && (returnFileEl != null ? !returnFileEl.isUploadSuccess() : true)) { // do not proceed when nothin in input field and no file uploaded setFormWarning("form.step2.error"); return; } setFormWarning(null); // reset error BulkAssessmentDatas datas = (BulkAssessmentDatas)getFromRunContext("datas"); if(datas == null) { datas = new BulkAssessmentDatas(); } if(bulkAssessmentTmpDir == null) { OlatRootFolderImpl bulkAssessmentDir = new OlatRootFolderImpl("/bulkassessment/", null); bulkAssessmentTmpDir = bulkAssessmentDir.createChildContainer(UUID.randomUUID().toString()); } backupInputDatas(val, datas, bulkAssessmentTmpDir); List<String[]> splittedRows = splitRawData(val); addToRunContext("splittedRows", splittedRows); List<BulkAssessmentRow> rows = new ArrayList<>(100); if(returnFileEl != null) { processReturnFiles(datas, rows, bulkAssessmentTmpDir); } datas.setRows(rows); addToRunContext("datas", datas); fireEvent(ureq, StepsEvent.ACTIVATE_NEXT); } private List<String[]> splitRawData(String idata) { String[] lines = idata.split("\r?\n"); int numOfLines = lines.length; List<String[]> rows = new ArrayList<String[]>(numOfLines); String d; if (delimiter.getSelectedKey().startsWith("t")) { d = "\t"; } else { d = ","; } for (int i = 0; i < numOfLines; i++) { String line = lines[i]; if(StringHelper.containsNonWhitespace(line)){ String[] values = line.split(d,-1); rows.add(values); } } return rows; } /** * Backup the input field for later editing purpose * @param val * @param datas */ private void backupInputDatas(String val, BulkAssessmentDatas datas, OlatRootFolderImpl tmpDir) { OlatRootFileImpl inputFile = null; if(StringHelper.containsNonWhitespace(datas.getDataBackupFile())) { inputFile = new OlatRootFileImpl(datas.getDataBackupFile(), null); } if(inputFile == null) { String inputFilename = UUID.randomUUID().toString() + ".csv"; inputFile = tmpDir.createChildLeaf(inputFilename); } OutputStream out = inputFile.getOutputStream(false); try { IOUtils.write(val, out); datas.setDataBackupFile(inputFile.getRelPath()); } catch (IOException e) { logError("", e); } finally { IOUtils.closeQuietly(out); } } private void processReturnFiles(BulkAssessmentDatas datas, List<BulkAssessmentRow> rows, OlatRootFolderImpl tmpDir) { File uploadedFile = returnFileEl.getUploadFile(); if(uploadedFile == null) { File initialFile = returnFileEl.getInitialFile(); if(initialFile != null && initialFile.exists()) { datas.setReturnFiles(targetArchive.getRelPath()); processReturnFiles(targetArchive, rows); } } else if(uploadedFile.exists()) { //transfer to secured try { String uploadedFilename = returnFileEl.getUploadFileName(); if(!StringHelper.containsNonWhitespace(uploadedFilename)) { uploadedFilename = "bulkAssessment.zip"; } VFSItem currentTarget = tmpDir.resolve(uploadedFilename); if(!isSame(currentTarget, uploadedFile)) { if(currentTarget != null && currentTarget.exists()) { currentTarget.deleteSilently(); } targetArchive = tmpDir.createChildLeaf(uploadedFilename); FileInputStream inStream = new FileInputStream(uploadedFile); if(VFSManager.copyContent(inStream, targetArchive)) { datas.setReturnFiles(targetArchive.getRelPath()); processReturnFiles(targetArchive, rows); } } else { datas.setReturnFiles(targetArchive.getRelPath()); processReturnFiles(targetArchive, rows); } } catch (FileNotFoundException e) { logError("", e); } } } private boolean isSame(VFSItem currentTarget, File uploadedFile) { if(currentTarget instanceof LocalImpl) { LocalImpl local = (LocalImpl)currentTarget; File currentFile = local.getBasefile(); if(currentFile.length() == uploadedFile.length()) { try { return org.apache.commons.io.FileUtils.contentEquals(currentFile, uploadedFile); } catch (IOException e) { logError("", e); //do nothing -> return false at the end } } } return false; } private void processReturnFiles(VFSLeaf target, List<BulkAssessmentRow> rows) { Map<String, BulkAssessmentRow> assessedIdToRow = new HashMap<>(); for(BulkAssessmentRow row:rows) { assessedIdToRow.put(row.getAssessedId(), row); } if(target.exists()) { InputStream is = target.getInputStream(); File parentTarget = ((LocalImpl)target).getBasefile().getParentFile(); ZipInputStream zis = new ZipInputStream(is); ZipEntry entry; try { byte[] b = new byte[FileUtils.BSIZE]; while ((entry = zis.getNextEntry()) != null) { if(!entry.isDirectory()) { while (zis.read(b) > 0) { //continue } Path op = new File(parentTarget, entry.getName()).toPath(); if(!Files.isHidden(op) && !Files.isDirectory(op)) { Path parentDir = op.getParent(); String assessedId = parentDir.getFileName().toString(); String filename = op.getFileName().toString(); BulkAssessmentRow row; if(assessedIdToRow.containsKey(assessedId)) { row = assessedIdToRow.get(assessedId); } else { row = new BulkAssessmentRow(); row.setAssessedId(assessedId); assessedIdToRow.put(assessedId, row); rows.add(row); } if(row.getReturnFiles() == null) { row.setReturnFiles(new ArrayList<String>(2)); } row.getReturnFiles().add(filename); } } } } catch(Exception e) { logError("", e); } finally { IOUtils.closeQuietly(is); IOUtils.closeQuietly(zis); } } } }