/******************************************************************************* * Copyright (c) 2006, 2007, 2008, 2009 The Sakai Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package org.sakaiproject.tool.gradebook.ui; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Serializable; import java.text.DateFormat; import java.text.NumberFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.faces.component.UIComponent; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.faces.event.ActionEvent; import javax.faces.event.ValueChangeEvent; import javax.faces.model.SelectItem; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.myfaces.custom.fileupload.UploadedFile; import org.apache.poi.hssf.OldExcelFormatException; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFCell; import org.apache.poi.xssf.usermodel.XSSFRow; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.sakaiproject.component.cover.ComponentManager; import org.sakaiproject.component.cover.ServerConfigurationService; import org.sakaiproject.content.api.ContentResource; import org.sakaiproject.content.api.FilePickerHelper; import org.sakaiproject.entity.api.Entity; import org.sakaiproject.entity.api.Reference; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.entity.cover.EntityManager; import org.sakaiproject.exception.ServerOverloadException; import org.sakaiproject.jsf.util.JsfTool; import org.sakaiproject.section.api.coursemanagement.EnrollmentRecord; import org.sakaiproject.section.api.coursemanagement.User; import org.sakaiproject.section.api.facade.Role; import org.sakaiproject.service.gradebook.shared.ConflictingAssignmentNameException; import org.sakaiproject.service.gradebook.shared.ConflictingSpreadsheetNameException; import org.sakaiproject.tool.api.Tool; import org.sakaiproject.tool.api.ToolSession; import org.sakaiproject.tool.cover.SessionManager; import org.sakaiproject.tool.gradebook.Assignment; import org.sakaiproject.tool.gradebook.AssignmentGradeRecord; import org.sakaiproject.tool.gradebook.Category; import org.sakaiproject.tool.gradebook.Comment; import org.sakaiproject.tool.gradebook.Gradebook; import org.sakaiproject.tool.gradebook.LetterGradePercentMapping; import org.sakaiproject.tool.gradebook.jsf.FacesUtil; import org.sakaiproject.util.ResourceLoader; import org.sakaiproject.util.Validator; public class SpreadsheetUploadBean extends GradebookDependentBean implements Serializable { private String title; private UploadedFile upFile; private String pickedFileReference; private String pickedFileDesc; private static final Log logger = LogFactory.getLog(SpreadsheetUploadBean.class); private Spreadsheet spreadsheet; private Map rosterMap; private List assignmentList; private List studentRows; private List assignmentHeaders; private Map selectedAssignment; private List assignmentColumnSelectItems; private boolean saved = false; private String columnCount; private String rowCount; private boolean hasUnknownUser; private boolean hasUnknownAssignments; private Long spreadsheetId; private Map scores; private Assignment assignment; private Long assignmentId; private Integer selectedCommentsColumnId = 0; private List categoriesSelectList; private Category assignmentCategory; private String selectedCategory; private Gradebook localGradebook; private StringBuilder externallyMaintainedImportMsg; private UIComponent uploadButton; private String csvDelimiter; private NumberFormat numberFormat; private boolean selectedCategoryDropsScores; private String date_entry_format_description; // Used for bulk upload of gradebook items // Holds list of unknown user ids private List unknownUsers = new ArrayList(); private List unknownAssignments = new ArrayList(); private static final String POINTS_POSSIBLE_STRING = "export_points_possible"; private static final String CUMULATIVE_GRADE_STRING = "roster_course_grade_column_name"; private static final String IMPORT_SUCCESS_STRING = "import_entire_success"; private static final String IMPORT_SOME_SUCCESS_STRING = "import_entire_some_success"; private static final String IMPORT_NO_CHANGES = "import_entire_no_changes"; private static final String IMPORT_ASSIGNMENT_NOTSUPPORTED= "import_assignment_entire_notsupported"; private static final String IMPORT_ASSIGNMENT_NEG_VALUE = "import_assignment_entire_negative_score"; private static final int MAX_FILE_PICKER_UPLOADS = 1; static final String PICKED_FILE_REFERENCE = "pickedFileReference"; static final String PICKED_FILE_DESC = "pickedFileDesc"; static final String IMPORT_TITLE = "gradebookImportTitle"; public static final String UNASSIGNED_CATEGORY = "unassigned"; /** * Property set via sakai.properties to limit the file size allowed for spreadsheet * uploads. This property is in MB and defaults to 1 MB. */ public static final String PROPERTY_FILE_SIZE_MAX = "gradebook.upload.max"; /** * The default max file size, in MB, for a spreadsheet upload. */ public static final int FILE_SIZE_DEFAULT = 1; /** * If an upload contains more than the number of students in the class plus * this value, the upload will fail. */ private static final int MAX_NUM_ROWS_OVER_CLASS_SIZE = 50; private String pageName; public SpreadsheetUploadBean() { } public void init() { localGradebook = getGradebook(); initializeRosterMap(); if (assignment == null) { if (assignmentId != null) { assignment = getGradebookManager().getAssignment(assignmentId); } if (assignment == null) { // it is a new assignment assignment = new Assignment(); assignment.setReleased(true); } } // initialization; shouldn't enter here after category drop down changes if(assignmentCategory == null && !getLocalizedString("cat_unassigned").equalsIgnoreCase(selectedCategory)) { Category assignCategory = assignment.getCategory(); if (assignCategory != null) { selectedCategory = assignCategory.getId().toString(); selectedCategoryDropsScores = assignCategory.isDropScores(); assignCategory.setAssignmentList(retrieveCategoryAssignmentList(assignCategory)); assignmentCategory = assignCategory; } else { selectedCategory = getLocalizedString("cat_unassigned"); } } if (selectedCategory==null) selectedCategory = AssignmentBean.UNASSIGNED_CATEGORY; categoriesSelectList = new ArrayList(); // The first choice is always "Unassigned" categoriesSelectList.add(new SelectItem(AssignmentBean.UNASSIGNED_CATEGORY, FacesUtil.getLocalizedString("cat_unassigned"))); List gbCategories = getViewableCategories(); if (gbCategories != null && gbCategories.size() > 0) { Iterator catIter = gbCategories.iterator(); while (catIter.hasNext()) { Category cat = (Category) catIter.next(); categoriesSelectList.add(new SelectItem(cat.getId().toString(), cat.getName())); } } DateFormat df = DateFormat.getDateInstance( DateFormat.SHORT, (new ResourceLoader()).getLocale() ); date_entry_format_description = ((SimpleDateFormat)df).toPattern(); } private void initializeRosterMap() { //initialize rosteMap which is map of displayid and user objects rosterMap = new HashMap(); List enrollments = new ArrayList(findMatchingEnrollmentsForAllItems(null, null).keySet()); if(logger.isDebugEnabled()) logger.debug("enrollment size " +enrollments.size()); Iterator iter; iter = enrollments.iterator(); while(iter.hasNext()){ EnrollmentRecord enr; enr = (EnrollmentRecord)iter.next(); if(logger.isDebugEnabled()) logger.debug("displayid "+enr.getUser().getDisplayId() + " userid "+enr.getUser().getUserUid()); rosterMap.put(enr.getUser().getDisplayId().toLowerCase(),enr.getUser()); } } public String getTitle() { if (title == null || "".equals(title)) { // check the session attribute ToolSession session = SessionManager.getCurrentToolSession(); if (session.getAttribute(IMPORT_TITLE) != null) { title = (String) session.getAttribute(IMPORT_TITLE); } } return title; } public void setTitle(String title) { this.title = title; } public UploadedFile getUpFile() { return upFile; } public void setUpFile(UploadedFile upFile) { if(logger.isDebugEnabled()) logger.debug("upload file name " + upFile.getName()); this.upFile = upFile; } public Spreadsheet getSpreadsheet() { return spreadsheet; } public void setSpreadsheet(Spreadsheet spreadsheet) { this.spreadsheet = spreadsheet; } public Map getRosterMap() { return rosterMap; } public void setRosterMap(Map rosterMap) { this.rosterMap = rosterMap; } public Long getSpreadsheetId() { return spreadsheetId; } public void setSpreadsheetId(Long spreadsheetId) { this.spreadsheetId = spreadsheetId; } public List getSpreadsheets() { this.setPageName("spreadsheetListing"); return getGradebookManager().getSpreadsheets(getGradebookId()); } public String deleteItem(){ return "spreadsheetRemove"; } public List getAssignmentList() { return assignmentList; } public void setAssignmentList(List assignmentList) { this.assignmentList = assignmentList; } public List getStudentRows() { return studentRows; } public void setStudentRows(List studentRows) { this.studentRows = studentRows; } public List getAssignmentHeaders() { return assignmentHeaders; } public void setAssignmentHeaders(List assignmentHeaders) { this.assignmentHeaders = assignmentHeaders; } public Map getSelectedAssignment() { return selectedAssignment; } public void setSelectedAssignment(Map selectedAssignment) { this.selectedAssignment = selectedAssignment; } public List getAssignmentColumnSelectItems() { return assignmentColumnSelectItems; } public void setAssignmentColumnSelectItems(List assignmentColumnSelectItems) { this.assignmentColumnSelectItems = assignmentColumnSelectItems; } public boolean isSaved() { return saved; } public void setSaved(boolean saved) { this.saved = saved; } /** * Returns formatted string with count of assignments imported (columns - 1 {student name}) */ public String getVerifyColumnCount() { final int intColumnCount = new Integer(columnCount == null ? "0" : columnCount).intValue() - 1; return FacesUtil.getLocalizedString("import_verify_column_count",new String[] {Integer.toString(intColumnCount)}); } public String getColumnCount() { return FacesUtil.getLocalizedString("upload_preview_column_count",new String[] {columnCount}); } public void setColumnCount(String columnCount) { this.columnCount = columnCount; } /** * Returns formatted string with count of number of students imported * Added points possible row so need to subtract one from count */ public String getVerifyRowCount() { return FacesUtil.getLocalizedString("import_verify_row_count",new String[] {rowCount}); } public String getRowCount() { return FacesUtil.getLocalizedString("upload_preview_row_count",new String[] {rowCount}); } public void setRowCount(String rowCount) { this.rowCount = rowCount; } public String getRowStyles() { StringBuilder sb = new StringBuilder(); if (studentRows != null) { for(Iterator iter = studentRows.iterator(); iter.hasNext();){ SpreadsheetRow row = (SpreadsheetRow)iter.next(); if(row.isKnown()){ sb.append("internal,"); }else{ sb.append("external,"); } } } if(logger.isDebugEnabled())logger.debug(sb.toString()); return sb.toString(); } /** * Returns List of unknown user names */ public boolean getHasUnknownUser() { return hasUnknownUser; } public Map getScores() { return scores; } public void setScores(Map scores) { this.scores = scores; } public Assignment getAssignment() { return assignment; } public void setAssignment(Assignment assignment) { this.assignment = assignment; } public Long getAssignmentId() { return assignmentId; } public void setAssignmentId(Long assignmentId) { this.assignmentId = assignmentId; } /** * Returns list of unknown users */ public List getUnknownUsers() { return unknownUsers; } /** * Sets the list of unknown users */ public void setUnknownUsers(List unknownUsers) { this.unknownUsers = unknownUsers; } public boolean isHasUnknownAssignments() { return hasUnknownAssignments; } public void setHasUnknownAssignments(boolean hasUnknownAssignments) { this.hasUnknownAssignments = hasUnknownAssignments; } public List getUnknownAssignments() { return unknownAssignments; } public void setUnknownAssignments(List unknownAssignments) { this.unknownAssignments = unknownAssignments; } /** * Returns the count of unknown users */ public int getUnknownSize() { this.setPageName("spreadsheetAll"); return unknownUsers.size(); } public List getCategoriesSelectList() { return categoriesSelectList; } public String getSelectedCategory() { return selectedCategory; } public void setSelectedCategory(String selectedCategory) { this.selectedCategory = selectedCategory; } public Gradebook getLocalGradebook() { return localGradebook; } /** * For retaining the pageName variable throughout import process */ public String getPageName() { return pageName; } public void setPageName(String pageName) { this.pageName = pageName; } public String getExternallyMaintainedImportMsg() { if (externallyMaintainedImportMsg == null || externallyMaintainedImportMsg.length() < 1) return null; return externallyMaintainedImportMsg.toString(); } /** * Returns the Category associated with selectedCategory * If unassigned or not found, returns null * @return */ private Category retrieveSelectedCategory() { Long catId = null; Category category = null; if (selectedCategory != null && !selectedCategory.equals(AssignmentBean.UNASSIGNED_CATEGORY)) { try { catId = new Long(selectedCategory); } catch (Exception e) { catId = null; } if (catId != null) { // check to make sure there is a corresponding category category = getGradebookManager().getCategory(catId); //populate assignments list category.setAssignmentList(retrieveCategoryAssignmentList(category)); } } return category; } private List retrieveCategoryAssignmentList(Category cat){ List assignmentsToUpdate = new ArrayList(); if(cat != null){ List assignments = cat.getAssignmentList(); if(cat.isDropScores() && (assignments == null || assignments.size() == 0)) { // don't populate, if assignments are already in category (to improve performance) assignments = getGradebookManager().getAssignmentsForCategory(cat.getId()); // only include assignments which are not adjustments must not update adjustment item pointsPossible for(Object o : assignments) { if(o instanceof Assignment) { Assignment assignment = (Assignment)o; if(!Assignment.item_type_adjustment.equals(assignment.getItemType())) { assignmentsToUpdate.add(assignment); } } } } } return assignmentsToUpdate; } //view file from db public String viewItem(){ if(rosterMap == null){ init(); } if(logger.isDebugEnabled())logger.debug("loading viewItem()"); org.sakaiproject.tool.gradebook.Spreadsheet sp = getGradebookManager().getSpreadsheet(spreadsheetId); StringBuilder sb = new StringBuilder(); sb.append(sp.getContent()); List contents = new ArrayList(); String lineitems[] = sb.toString().split("\n"); for(int i = 0;i<lineitems.length;i++){ if(logger.isDebugEnabled())logger.debug("line item contents \n" + lineitems[i]); contents.add(lineitems[i]); } if(logger.isDebugEnabled())logger.debug(sp.toString()); spreadsheet = new Spreadsheet(); spreadsheet.setTitle(sp.getName()); spreadsheet.setDate(sp.getDateCreated()); spreadsheet.setUserId(sp.getCreator()); spreadsheet.setLineitems(contents); assignmentList = new ArrayList(); studentRows = new ArrayList(); assignmentColumnSelectItems = new ArrayList(); // // assignmentHeaders = new ArrayList(); SpreadsheetHeader header = new SpreadsheetHeader((String) spreadsheet.getLineitems().get(0)); assignmentHeaders = header.getHeaderWithoutUser(); //generate spreadsheet rows Iterator it = spreadsheet.getLineitems().iterator(); int rowcount = 0; int unknownusers = 0; while(it.hasNext()){ String line = (String) it.next(); if(rowcount > 0){ SpreadsheetRow row = new SpreadsheetRow(line); studentRows.add(row); //check the number of unkonw users in spreadsheet if(!row.isKnown())unknownusers = unknownusers + 1; if(logger.isDebugEnabled()) logger.debug("row added" + rowcount); } rowcount++; } rowCount = String.valueOf(rowcount - 1); if(unknownusers > 0){ this.hasUnknownUser = true; } //create a numeric list of assignment headers if(logger.isDebugEnabled())logger.debug("creating assignment List ---------"); for(int i = 0;i<assignmentHeaders.size();i++){ assignmentList.add(new Integer(i)); if(logger.isDebugEnabled()) logger.debug("col added" + i); } columnCount = String.valueOf(assignmentHeaders.size()); for(int i = 0;i<assignmentHeaders.size();i++){ SelectItem item = new SelectItem(new Integer(i + 1),(String)assignmentHeaders.get(i)); if(logger.isDebugEnabled()) logger.debug("creating selectItems "+ item.getValue()); assignmentColumnSelectItems.add(item); } if(logger.isDebugEnabled()) logger.debug("Map initialized " +studentRows.size()); if(logger.isDebugEnabled()) logger.debug("assignmentList " +assignmentList.size()); return "spreadsheetPreview"; } public boolean isSelectedCategoryDropsScores() { return selectedCategoryDropsScores; } public void setSelectedCategoryDropsScores(boolean selectedCategoryDropsScores) { this.selectedCategoryDropsScores = selectedCategoryDropsScores; } public Category getAssignmentCategory() { return assignmentCategory; } public void setAssignmentCategory(Category assignmentCategory) { this.assignmentCategory = assignmentCategory; } public String processCategoryChangeInImport(ValueChangeEvent vce) { String changeCategory = (String) vce.getNewValue(); selectedCategory = changeCategory; if(vce.getOldValue() != null && vce.getNewValue() != null && !vce.getOldValue().equals(vce.getNewValue())) { if(changeCategory.equals(UNASSIGNED_CATEGORY)) { selectedCategoryDropsScores = false; assignmentCategory = null; selectedCategory = getLocalizedString("cat_unassigned"); } else { List<Category> categories = getGradebookManager().getCategories(getGradebookId()); if (categories != null && categories.size() > 0) { for (Category category : categories) { if(changeCategory.equals(category.getId().toString())) { selectedCategoryDropsScores = category.isDropScores(); category.setAssignmentList(retrieveCategoryAssignmentList(category)); assignmentCategory = category; assignment.setPointsPossible(assignmentCategory.getItemValue()); selectedCategory = category.getId().toString(); break; } } } } } return "spreadsheetImport"; } /** * Bulk import of grades from a freshly imported spreadsheet * * @return String for navigation * * @throws Exception */ public String processFileEntire() throws Exception { InputStream inputStream = null; String fileName = null; List contents = null; int maxFileSizeInMB; try { maxFileSizeInMB = ServerConfigurationService.getInt(PROPERTY_FILE_SIZE_MAX, FILE_SIZE_DEFAULT); } catch (NumberFormatException nfe) { if (logger.isDebugEnabled()) logger.debug("Invalid property set for gradebook max file size"); maxFileSizeInMB = FILE_SIZE_DEFAULT; } long maxFileSizeInBytes = 1024L * 1024L * maxFileSizeInMB; boolean isXlsImport = false; if (upFile != null) { if (upFile != null && logger.isDebugEnabled()) { logger.debug("file size " + upFile.getSize() + "file name " + upFile.getName() + "file Content Type " + upFile.getContentType() + ""); } if(logger.isDebugEnabled()) logger.debug("check that the file type is allowed"); if (upFile.getName().endsWith("csv")) { isXlsImport = false; } else if (upFile.getName().endsWith("xls")) { isXlsImport = true; } else { FacesUtil.addErrorMessage(getLocalizedString("import_entire_filetype_error",new String[] {upFile.getName()})); return null; } if (upFile.getSize() > maxFileSizeInBytes) { FacesUtil.addErrorMessage(getLocalizedString("upload_view_filesize_error",new String[] {maxFileSizeInMB + ""})); return null; } fileName = upFile.getName(); inputStream = new BufferedInputStream(upFile.getInputStream()); } else { savePickedUploadFile(); if (pickedFileReference != null) { if (logger.isDebugEnabled()) logger.debug("check that the file is csv file"); if (pickedFileDesc == null) { FacesUtil.addErrorMessage(getLocalizedString("import_entire_filetype_error", new String[] {pickedFileDesc})); return null; } if(logger.isDebugEnabled()) logger.debug("check that the file type is allowed"); if (pickedFileDesc.endsWith("csv")) { isXlsImport = false; } else if (pickedFileDesc.endsWith("xls")) { isXlsImport = true; } else { FacesUtil.addErrorMessage(getLocalizedString("import_entire_filetype_error",new String[] {pickedFileDesc})); return null; } fileName = pickedFileDesc; ContentResource resource = getPickedContentResource(); if (resource != null) { // double check the file size does not exceed our limit if (resource.getContentLength() > maxFileSizeInBytes) { FacesUtil.addErrorMessage(getLocalizedString("upload_view_filesize_error", new String[] {maxFileSizeInMB + ""})); return null; } } inputStream = getPickedFileStream(resource); clearPickedFile(); } else { // all null - no uploaded or picked file if (logger.isDebugEnabled()) logger.debug("uploaded file not initialized"); FacesUtil.addErrorMessage(getLocalizedString("import_entire_file_missing")); return null; } } if (inputStream == null) { FacesUtil.addErrorMessage(getLocalizedString("upload_view_config_error")); return null; } try { if (isXlsImport) { contents = excelToArray(inputStream); } else { contents = csvtoArray(inputStream); } } catch(IOException ioe) { FacesUtil.addErrorMessage(getLocalizedString("upload_view_config_error")); return null; } finally { if (inputStream != null) { inputStream.close(); } } // double check that the number of rows in this spreadsheet is reasonable int numStudentsInSite = getNumStudentsInSite(); if (contents.size() > (numStudentsInSite + MAX_NUM_ROWS_OVER_CLASS_SIZE)) { FacesUtil.addErrorMessage(getLocalizedString("upload_view_filerows_error", new String[] {contents.size() + "", numStudentsInSite + ""})); return null; } // reset error lists unknownUsers = new ArrayList(); unknownAssignments = new ArrayList(); spreadsheet = new Spreadsheet(); spreadsheet.setDate(new Date()); spreadsheet.setTitle(this.getTitle()); spreadsheet.setFilename(fileName); spreadsheet.setLineitems(contents); assignmentList = new ArrayList(); studentRows = new ArrayList(); assignmentColumnSelectItems = new ArrayList(); SpreadsheetHeader header; try{ header = new SpreadsheetHeader((String) spreadsheet.getLineitems().get(0)); assignmentHeaders = header.getHeaderWithoutUserAndCumulativeGrade(); } catch(IndexOutOfBoundsException ioe) { if(logger.isDebugEnabled())logger.debug(ioe + " there is a problem with the uploaded spreadsheet"); FacesUtil.addErrorMessage(getLocalizedString("upload_view_filecontent_error")); return null; } // check for blank header titles - don't want assignments added w/ blank titles Iterator headerIter = assignmentHeaders.iterator(); // skip the name col headerIter.next(); while (headerIter.hasNext()) { String itemTitle = (String) headerIter.next(); if (itemTitle == null || itemTitle.trim().length() == 0) { FacesUtil.addErrorMessage(getLocalizedString("import_assignment_entire_missing_title")); return null; } } //generate spreadsheet rows Iterator it = spreadsheet.getLineitems().iterator(); int rowcount = 0; int unknownusers = 0; int headerCount = assignmentHeaders.size(); while(it.hasNext()){ String line = (String) it.next(); if(rowcount > 0){ SpreadsheetRow row = new SpreadsheetRow(line); List rowData = row.getRowcontent(); // if Cumulative column was in spreadsheet, need to filter it out // here if (header.isHasCumulative()) rowData.remove(rowData.size()-1); // if the number of cols in the student row is less than the # headers, // we need to append blank placeholders if (rowData.size() < headerCount) { while (rowData.size() < (headerCount)) { rowData.add(""); } } studentRows.add(row); //check the number of unknown users in spreadsheet if(!row.isKnown()) { unknownusers = unknownusers + 1; unknownUsers.add(row.getUserId()); } if(logger.isDebugEnabled()) logger.debug("row added" + rowcount); } rowcount++; } rowCount = String.valueOf(rowcount - 1); // subtract header if(unknownusers > 0){ this.hasUnknownUser = true; return null; } //create a numeric list of assignment headers if(logger.isDebugEnabled())logger.debug("creating assignment List ---------"); for(int i = 0;i<assignmentHeaders.size()-1;i++){ assignmentList.add(new Integer(i)); if(logger.isDebugEnabled()) logger.debug("col added" + i); } columnCount = String.valueOf(assignmentHeaders.size()); for(int i = 0;i<assignmentHeaders.size();i++){ SelectItem item = new SelectItem(new Integer(i + 1),(String)assignmentHeaders.get(i)); if(logger.isDebugEnabled()) logger.debug("creating selectItems "+ item.getValue()); assignmentColumnSelectItems.add(item); } if(logger.isDebugEnabled()) logger.debug("Map initialized " +studentRows.size()); if(logger.isDebugEnabled()) logger.debug("assignmentList " +assignmentList.size()); if(studentRows.size() < 1){ FacesUtil.addErrorMessage(getLocalizedString("upload_view_filecontent_error")); return null; } return "spreadsheetVerify"; } public String processFile() throws Exception { InputStream inputStream = null; String fileName = null; List contents = null; int maxFileSizeInMB; try { maxFileSizeInMB = ServerConfigurationService.getInt(PROPERTY_FILE_SIZE_MAX, FILE_SIZE_DEFAULT); } catch (NumberFormatException nfe) { if (logger.isDebugEnabled()) logger.debug("Invalid property set for gradebook max file size"); maxFileSizeInMB = FILE_SIZE_DEFAULT; } long maxFileSizeInBytes = 1024L * 1024L * maxFileSizeInMB; boolean isXlsImport = false; boolean isOOXMLimport = false; if (upFile != null) { if (upFile != null && logger.isDebugEnabled()) { logger.debug("file size " + upFile.getSize() + "file name " + upFile.getName() + "file Content Type " + upFile.getContentType() + ""); } if(logger.isDebugEnabled()) logger.debug("check that the file type is allowed"); if (upFile.getName().endsWith("csv")) { isXlsImport = false; } else if (upFile.getName().endsWith("xls")) { isXlsImport = true; } else if (upFile.getName().endsWith("xlsx")) { isOOXMLimport = true; } else { FacesUtil.addErrorMessage(getLocalizedString("upload_view_filetype_error",new String[] {upFile.getName()})); return null; } if (upFile.getSize() > maxFileSizeInBytes) { FacesUtil.addErrorMessage(getLocalizedString("upload_view_filesize_error",new String[] {maxFileSizeInMB + ""})); return null; } fileName = upFile.getName(); inputStream = new BufferedInputStream(upFile.getInputStream()); } else { savePickedUploadFile(); if (pickedFileReference != null) { if (logger.isDebugEnabled()) logger.debug("check that the file is csv file"); if (pickedFileDesc == null) { FacesUtil.addErrorMessage(getLocalizedString("upload_view_filetype_error", new String[] {pickedFileDesc})); return null; } if(logger.isDebugEnabled()) logger.debug("check that the file type is allowed"); if (pickedFileDesc.endsWith("csv")) { isXlsImport = false; } else if (pickedFileDesc.endsWith("xls")) { isXlsImport = true; } else if (pickedFileDesc.endsWith("xlsx")) { isOOXMLimport = true; } else { FacesUtil.addErrorMessage(getLocalizedString("import_entire_filetype_error",new String[] {pickedFileDesc})); return null; } fileName = pickedFileDesc; ContentResource resource = getPickedContentResource(); if (resource != null) { // double check the file size does not exceed our limit if (resource.getContentLength() > maxFileSizeInBytes) { FacesUtil.addErrorMessage(getLocalizedString("upload_view_filesize_error", new String[] {maxFileSizeInMB + ""})); return null; } } inputStream = getPickedFileStream(resource); clearPickedFile(); } else { // all null - no uploaded or picked file if (logger.isDebugEnabled()) logger.debug("uploaded file not initialized"); FacesUtil.addErrorMessage(getLocalizedString("upload_view_failure")); return null; } } if (inputStream == null) { FacesUtil.addErrorMessage(getLocalizedString("upload_view_config_error")); return null; } try { if (isXlsImport) { contents = excelToArray(inputStream); } else if (isOOXMLimport) { contents = excelOOXMLToArray(inputStream); } else { contents = csvtoArray(inputStream); } } catch(IOException ioe) { FacesUtil.addErrorMessage(getLocalizedString("upload_view_config_error")); return null; } //SAK-23610 POI doesn't support Excel 5 catch (OldExcelFormatException oex) { FacesUtil.addErrorMessage(getLocalizedString("upload_view_oldformat_error")); return null; } finally { if (inputStream != null) { inputStream.close(); } } // double check that the number of rows in this spreadsheet is reasonable int numStudentsInSite = getNumStudentsInSite(); if (contents.size() > (numStudentsInSite + MAX_NUM_ROWS_OVER_CLASS_SIZE)) { FacesUtil.addErrorMessage(getLocalizedString("upload_view_filerows_error",new String[] {contents.size() + "", numStudentsInSite + ""})); return null; } spreadsheet = new Spreadsheet(); spreadsheet.setDate(new Date()); // SAK-14173 - get title from session if available // and then clear the value from the session spreadsheet.setTitle(this.getTitle()); clearImportTitle(); spreadsheet.setFilename(fileName); spreadsheet.setLineitems(contents); assignmentList = new ArrayList(); studentRows = new ArrayList(); assignmentColumnSelectItems = new ArrayList(); // // assignmentHeaders = new ArrayList(); SpreadsheetHeader header; try{ header = new SpreadsheetHeader((String) spreadsheet.getLineitems().get(0)); assignmentHeaders = header.getHeaderWithoutUser(); }catch(IndexOutOfBoundsException ioe){ if(logger.isDebugEnabled())logger.debug(ioe + " there is a problem with the uploaded spreadsheet"); FacesUtil.addErrorMessage(getLocalizedString("upload_view_filecontent_error")); return null; } //generate spreadsheet rows Iterator it = spreadsheet.getLineitems().iterator(); int rowcount = 0; int unknownusers = 0; while(it.hasNext()){ String line = (String) it.next(); if(rowcount > 0){ SpreadsheetRow row = new SpreadsheetRow(line); studentRows.add(row); //check the number of unkonw users in spreadsheet if(!row.isKnown())unknownusers = unknownusers + 1; if(logger.isDebugEnabled()) logger.debug("row added" + rowcount); } rowcount++; } rowCount = String.valueOf(rowcount - 1); if(unknownusers > 0){ this.hasUnknownUser = true; } //create a numeric list of assignment headers if(logger.isDebugEnabled())logger.debug("creating assignment List ---------"); for(int i = 0;i<assignmentHeaders.size();i++){ assignmentList.add(new Integer(i)); if(logger.isDebugEnabled()) logger.debug("col added" + i); } columnCount = String.valueOf(assignmentHeaders.size()); for(int i = 0;i<assignmentHeaders.size();i++){ SelectItem item = new SelectItem(new Integer(i + 1),(String)assignmentHeaders.get(i)); if(logger.isDebugEnabled()) logger.debug("creating selectItems "+ item.getValue()); assignmentColumnSelectItems.add(item); } if(logger.isDebugEnabled()) logger.debug("Map initialized " +studentRows.size()); if(logger.isDebugEnabled()) logger.debug("assignmentList " +assignmentList.size()); if(studentRows.size() < 1){ FacesUtil.addErrorMessage(getLocalizedString("upload_view_filecontent_error")); return null; } return "spreadsheetUploadPreview"; } /** * method converts an input stream to an List consist of strings * representing a line * * @param inputStream * @return contents */ private List csvtoArray(InputStream inputStream) throws IOException{ /** * TODO this well probably be removed */ if(logger.isDebugEnabled()) logger.debug("csvtoArray()"); List contents = new ArrayList(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String line; while((line = reader.readLine())!=null){ //logger.debug("contents of line: "+line); if (line.replaceAll(",", "").replaceAll("\"", "").equals("")) { continue; } contents.add(line); } return contents; } /** * method to save CSV to database * * @return String */ public String saveFile(){ StringBuilder sb = new StringBuilder(); List contents = spreadsheet.getLineitems(); Iterator it = contents.iterator(); while(it.hasNext()){ String line = (String) it.next(); sb.append(line + '\n'); } String filename = spreadsheet.getFilename(); /** temporary presistence code * */ if(logger.isDebugEnabled())logger.debug("string to save "+sb.toString()); try{ getGradebookManager().createSpreadsheet(getGradebookId(),spreadsheet.getTitle(),this.getUserDirectoryService().getUserDisplayName(getUserUid()),new Date(),sb.toString()); }catch(ConflictingSpreadsheetNameException e){ if(logger.isDebugEnabled())logger.debug(e); FacesUtil.addErrorMessage(getLocalizedString("upload_preview_save_failure")); return null; } FacesUtil.addRedirectSafeMessage(getLocalizedString("upload_preview_save_confirmation", new String[] {filename})); this.setPageName("spreadsheetListing"); return "spreadsheetListing"; } /** * If user has selected column to import, grab the values and */ public String importData(){ FacesContext facesContext = FacesContext.getCurrentInstance(); HttpServletRequest request = (HttpServletRequest) facesContext.getExternalContext().getRequest(); if(logger.isDebugEnabled())logger.debug("processFile()"); String selectedColumn = request.getParameter("form:assignment"); if(logger.isDebugEnabled())logger.debug("the selected column is " + selectedColumn); selectedAssignment = new HashMap(); try{ selectedAssignment.put("Assignment", assignmentHeaders.get(Integer.parseInt(selectedColumn) - 1)); }catch(Exception e){ if(logger.isDebugEnabled())logger.debug("no assignment selected"); FacesUtil.addErrorMessage(getLocalizedString("import_preview_assignment_selection_failure")); return null; } Iterator it = studentRows.iterator(); if(logger.isDebugEnabled())logger.debug("number of student rows "+studentRows.size() ); int i = 0; while(it.hasNext()){ if(logger.isDebugEnabled())logger.debug("row " + i); SpreadsheetRow row = (SpreadsheetRow) it.next(); List line = row.getRowcontent(); String userid = ""; String user = (String)line.get(0); try{ userid = ((User)rosterMap.get(line.get(0))).getUserUid(); }catch(Exception e){ if(logger.isDebugEnabled())logger.debug("user "+ user + "is not known to the system"); userid = ""; } String points; try{ int index = Integer.parseInt(selectedColumn); if(line.size() > index) { points = (String) line.get(index); } else { logger.info("unable to find any points for " + userid + " in spreadsheet"); points = ""; } }catch(NumberFormatException e){ if(logger.isDebugEnabled())logger.debug(e); points = ""; } if(logger.isDebugEnabled())logger.debug("user "+user + " userid " + userid +" points "+points); if(!"".equals(points) && (!"".equals(userid))){ selectedAssignment.put(userid,points); } i++; } if(logger.isDebugEnabled())logger.debug("scores to import "+ i); spreadsheet.setSelectedAssignment(selectedAssignment); if(assignment == null) { assignment = new Assignment(); assignment.setReleased(true); } try{ scores = spreadsheet.getSelectedAssignment(); assignment.setName((String) scores.get("Assignment")); }catch(NullPointerException npe){ if(logger.isDebugEnabled()) logger.debug("scores not set"); } return "spreadsheetImport"; } /** * Takes the spreadsheet data imported and updates/adds to what is in the database */ public String importDataAndSaveAll(){ boolean gbUpdated = false; hasUnknownAssignments = false; externallyMaintainedImportMsg = new StringBuilder(); LetterGradePercentMapping lgpm = new LetterGradePercentMapping(); if (getGradeEntryByLetter()) { lgpm = getGradebookManager().getLetterGradePercentMapping(localGradebook); } if(logger.isDebugEnabled())logger.debug("importDataAll()"); // first, verify imported data is valid if (!verifyImportedData(lgpm)) return "spreadsheetVerify"; List grAssignments = getGradebookManager().getAssignments(getGradebook().getId()); Iterator assignIter = assignmentHeaders.iterator(); // since the first two columns are user ids and name, skip over int index = 1; if (assignIter.hasNext()) assignIter.next(); while (assignIter.hasNext()) { String assignmentName = (String) assignIter.next(); String pointsPossibleAsString = null; String [] parsedAssignmentName = assignmentName.split(" \\["); assignmentName = parsedAssignmentName[0].trim(); Double pointsPossible = null; if (parsedAssignmentName.length > 1) { String [] parsedPointsPossible = parsedAssignmentName[1].split("\\]"); if (parsedPointsPossible.length > 0) { pointsPossibleAsString = parsedPointsPossible[0].trim(); try{ pointsPossible = convertStringToDouble(pointsPossibleAsString); pointsPossible = new Double(FacesUtil.getRoundDown(pointsPossible.doubleValue(), 2)); if(pointsPossible <= 0) pointsPossibleAsString = null; }catch(ParseException e){ pointsPossibleAsString = null; } } } // probably last column but not sure, so continue if (getLocalizedString(CUMULATIVE_GRADE_STRING).equals(assignmentName)) { continue; } index++; // Get Assignment object from assignment name Assignment assignment = getAssignmentByName(grAssignments, assignmentName); List gradeRecords = new ArrayList(); // if assignment == null, need to create a new one plus all the grade records // if exists, need find those that are changed and only apply those if (assignment == null) { if (pointsPossible != null) { // params: gradebook id, name of assignment, points possible, due date, NOT counted, is released assignmentId = getGradebookManager().createAssignment(getGradebookId(), assignmentName, pointsPossible, null, Boolean.FALSE, Boolean.TRUE, Boolean.FALSE); assignment = getGradebookManager().getAssignment(assignmentId); } else { // for this version, display error message saying non-match between import // and current gradebook assignments hasUnknownAssignments = true; unknownAssignments.add(assignmentName); continue; } Iterator it = studentRows.iterator(); if(logger.isDebugEnabled())logger.debug("number of student rows "+studentRows.size() ); int i = 1; while(it.hasNext()){ if(logger.isDebugEnabled())logger.debug("row " + i); SpreadsheetRow row = (SpreadsheetRow) it.next(); List line = row.getRowcontent(); String userid = ""; String user = (String)line.get(0); try { userid = ((User)rosterMap.get(user)).getUserUid(); } catch(Exception e) { if(logger.isDebugEnabled())logger.debug("user "+ user + "is not known to the system"); userid = ""; // checked when imported from user's system so should not happen at this point. } if(line.size() > index) { String inputScore = (String)line.get(index); if (inputScore != null && inputScore.trim().length() > 0) { Double scoreAsDouble = null; String scoreAsString = inputScore.trim(); AssignmentGradeRecord asnGradeRecord = new AssignmentGradeRecord(assignment,userid, null); // truncate input points/% to 2 decimal places if (getGradeEntryByPoints() || getGradeEntryByPercent()) { try { scoreAsDouble = convertStringToDouble(scoreAsString); scoreAsDouble = new Double(FacesUtil.getRoundDown(scoreAsDouble.doubleValue(), 2)); asnGradeRecord.setPercentEarned(scoreAsDouble); asnGradeRecord.setPointsEarned(scoreAsDouble); gradeRecords.add(asnGradeRecord); if(logger.isDebugEnabled())logger.debug("user "+user + " userid " + userid +" score "+inputScore.toString()); } catch (ParseException pe) { // this should have been caught during validation, so there is a problem logger.error("ParseException encountered parsing " + scoreAsString); } } else if (getGradeEntryByLetter()){ scoreAsString = (String)inputScore; asnGradeRecord.setLetterEarned(scoreAsString.trim()); gradeRecords.add(asnGradeRecord); if(logger.isDebugEnabled())logger.debug("user "+user + " userid " + userid +" score "+inputScore.toString()); } } } i++; } gbUpdated = true; } else { if (! assignment.getPointsPossible().equals(pointsPossible)) { if (assignment.isExternallyMaintained()) { externallyMaintainedImportMsg.append(getLocalizedString("import_assignment_externally_maintained_settings", new String[] {Validator.escapeHtml(assignment.getName()), Validator.escapeHtml(assignment.getExternalAppName())}) + "<br />"); } else if (pointsPossible != null) { assignment.setPointsPossible(pointsPossible); getGradebookManager().updateAssignment(assignment); gbUpdated = true; } } gradeRecords = gradeChanges(assignment, studentRows, index+1, lgpm); if (gradeRecords.size() == 0) continue; // no changes to current grade record so go to next one else { gbUpdated = true; } } getGradebookManager().updateAssignmentGradeRecords(assignment, gradeRecords, getGradebook().getGrade_type()); getGradebookBean().getEventTrackingService().postEvent("gradebook.importEntire","/gradebook/"+getGradebookId()+"/"+assignment.getName()+"/"+getAuthzLevel()); } // just in case previous attempt had unknown users hasUnknownUser = false; if (gbUpdated) { if (! hasUnknownAssignments) { FacesUtil.addRedirectSafeMessage(getLocalizedString(IMPORT_SUCCESS_STRING)); } else { FacesUtil.addRedirectSafeMessage(getLocalizedString(IMPORT_SOME_SUCCESS_STRING)); } } else if (! hasUnknownAssignments) { FacesUtil.addRedirectSafeMessage(getLocalizedString(IMPORT_NO_CHANGES)); } this.setPageName("spreadsheetAll"); return "spreadsheetAll"; } /** * Returns TRUE if specific value imported from spreadsheet is valid * * @return false if the spreadsheet contains a invalid points possible, an invalid * score value, more than one column for the same gb item, more than one row for the same student */ private boolean verifyImportedData(LetterGradePercentMapping lgpm) { if (studentRows == null || studentRows.isEmpty()) { return true; } if (getGradeEntryByLetter() && (lgpm == null || lgpm.getGradeMap() == null)) { FacesUtil.addErrorMessage(getLocalizedString("gb_setup_no_grade_entry_scale")); return false; } // determine the index of the "cumulative" column int indexOfCumColumn = -1; if (assignmentHeaders != null && !assignmentHeaders.isEmpty()) { // add one b/c assignmentHeaders does not include the username col but studentRows does indexOfCumColumn = assignmentHeaders.indexOf(getLocalizedString(CUMULATIVE_GRADE_STRING)) + 1; // we need to double check that there aren't any duplicate assignments // in the spreadsheet. the first column is the student name, so skip List<String> assignmentNames = new ArrayList<String>(); if (assignmentHeaders.size() > 1) { for (int i=1; i < assignmentHeaders.size(); i++) { if (i==indexOfCumColumn) { continue; } String header = (String)assignmentHeaders.get(i); String [] parsedAssignmentName = header.split(" \\["); String assignmentName = parsedAssignmentName[0].trim(); if (assignmentNames.contains(assignmentName)) { FacesUtil.addErrorMessage(getLocalizedString("import_assignment_duplicate_titles", new String[] {assignmentName})); return false; } assignmentNames.add(assignmentName); } } } List<String> uploadedStudents = new ArrayList<String>(); for (int row=0; row < studentRows.size(); row++) { SpreadsheetRow scoreRow = (SpreadsheetRow) studentRows.get(row); List studentScores = scoreRow.getRowcontent(); // verify that a student doesn't appear more than once in the ss if(studentScores != null) { String username = (String)studentScores.get(0); if (username != null) { if (uploadedStudents.contains(username)) { FacesUtil.addErrorMessage(getLocalizedString("import_assignment_duplicate_student", new String[] {username})); return false; } uploadedStudents.add(username); } } // start with col 2 b/c the first two are eid and name if (studentScores != null && studentScores.size() > 2) { for (int i=2; i < studentScores.size(); i++) { if (i == indexOfCumColumn) continue; String scoreAsString = ((String)studentScores.get(i)).trim(); if (scoreAsString != null && scoreAsString.length() > 0) { if (getGradeEntryByPoints() || getGradeEntryByPercent()) { Double scoreAsDouble; try { if(logger.isDebugEnabled()) logger.debug("checking if " +scoreAsString +" is a numeric value"); scoreAsDouble = convertStringToDouble(scoreAsString); // check for negative values if (scoreAsDouble.doubleValue() < 0) { if(logger.isDebugEnabled()) logger.debug(scoreAsString + " is not a positive value"); FacesUtil.addErrorMessage(getLocalizedString(IMPORT_ASSIGNMENT_NEG_VALUE)); return false; } } catch(ParseException e){ if(logger.isDebugEnabled()) logger.debug(scoreAsString + " is not a numeric value"); FacesUtil.addErrorMessage(getLocalizedString(IMPORT_ASSIGNMENT_NOTSUPPORTED)); return false; } } else if (getGradeEntryByLetter()) { String standardizedLetterGrade = lgpm.standardizeInputGrade(scoreAsString); if (standardizedLetterGrade == null) { FacesUtil.addErrorMessage(getLocalizedString("import_entire_invalid_letter")); return false; } } } } } } return true; } /** * Returns a list of AssignmentGradeRecords that are different than in current * Gradebook. Returns empty List if no differences found. * * @param assignment * The Assignment object whose grades need to be checked * @param fromSpreadsheet * The rows of grades from the imported spreadsheet * @param index * The column of spreadsheet to check * * @return * List containing AssignmentGradeRecords for those student's grades that * have changed */ private List gradeChanges(Assignment assignment, List fromSpreadsheet, int index, LetterGradePercentMapping lgpm) { List updatedGradeRecords = new ArrayList(); List studentUids = new ArrayList(); List studentRowsWithUids = new ArrayList(); Iterator it = fromSpreadsheet.iterator(); while(it.hasNext()) { final SpreadsheetRow row = (SpreadsheetRow) it.next(); List line = row.getRowcontent(); String userid = ""; final String user = ((String)line.get(0)).toLowerCase(); try { userid = ((User)rosterMap.get(user)).getUserUid(); // create list of uids to get current grades from gradebook studentUids.add(userid); // add uid to each row so can check spreadsheet value against currently stored List linePlus = new ArrayList(); linePlus.add(userid); linePlus.addAll(line); studentRowsWithUids.add(linePlus); } catch(Exception e) { // Weirdness. Should be caught when importing, not here } } List gbGrades = getGradebookManager().getAssignmentGradeRecordsConverted(assignment, studentUids); // now do the actual comparison it = studentRowsWithUids.iterator(); boolean updatingExternalGrade = false; while(it.hasNext()) { final List aRow = (List) it.next(); final String userid = (String) aRow.get(0); final String user = (String) aRow.get(1); AssignmentGradeRecord gr = findGradeRecord(gbGrades, userid); String score = null; if (index < aRow.size()) { score = ((String) aRow.get(index)).trim(); } if (getGradeEntryByPercent() || getGradeEntryByPoints()) { Double scoreEarned = null; boolean updateScore = true; if (score != null && !"".equals(score)) { try { scoreEarned = convertStringToDouble(score); } catch (ParseException pe) { // this should have already been validated at this point, so there is // something wrong if we made it here logger.error("ParseException encountered while checking for grade updates with score: " + score); updateScore = false; } // truncate to 2 decimal places if (scoreEarned != null) scoreEarned = new Double(FacesUtil.getRoundDown(scoreEarned.doubleValue(), 2)); } if (updateScore) { if (gr == null) { if (scoreEarned != null) { if (!assignment.isExternallyMaintained()) { gr = new AssignmentGradeRecord(assignment,userid,scoreEarned); gr.setPercentEarned(scoreEarned); // manager will handle if % vs point grading updatedGradeRecords.add(gr); } else { updatingExternalGrade = true; } } } else { // we need to truncate points earned to 2 decimal places to more accurately // see if it was changed - scores that are entered as % can be stored with // unlimited decimal places in db Double gbScoreEarned = null; if (getGradeEntryByPercent()) gbScoreEarned = gr.getPercentEarned(); else gbScoreEarned = gr.getPointsEarned(); if (gbScoreEarned != null) gbScoreEarned = new Double(FacesUtil.getRoundDown(gbScoreEarned.doubleValue(), 2)); // 3 ways points earned different: 1 null other not (both ways) or actual // values different if ((gbScoreEarned == null && scoreEarned != null) || (gbScoreEarned != null && scoreEarned == null) || (gbScoreEarned != null && scoreEarned != null && gbScoreEarned.doubleValue() != scoreEarned.doubleValue())) { gr.setPointsEarned(scoreEarned); //manager will use correct field depending on grade entry method gr.setPercentEarned(scoreEarned); if (!assignment.isExternallyMaintained()) updatedGradeRecords.add(gr); else updatingExternalGrade = true; } } } } else if (getGradeEntryByLetter()) { if (lgpm == null || lgpm.getGradeMap() == null) return null; if (score != null && score.length() > 0) { score = lgpm.standardizeInputGrade(score); } if (gr == null) { if (score != null && score.length() > 0) { if (!assignment.isExternallyMaintained()) { gr = new AssignmentGradeRecord(assignment,userid,null); gr.setLetterEarned(score); updatedGradeRecords.add(gr); } else { updatingExternalGrade = true; } } } else { String gbLetterEarned = gr.getLetterEarned(); if ((gbLetterEarned != null && !gbLetterEarned.equals(score)) || (gbLetterEarned == null && score != null)) { gr.setLetterEarned(score); if (!assignment.isExternallyMaintained()) updatedGradeRecords.add(gr); else updatingExternalGrade = true; } } } } if (updatingExternalGrade) externallyMaintainedImportMsg.append(getLocalizedString("import_assignment_externally_maintained_grades", new String[] {Validator.escapeHtml(assignment.getName()), Validator.escapeHtml(assignment.getExternalAppName())}) + "<br/>"); return updatedGradeRecords; } /** * Finds the AssignmentGradeRecord for userid passed in (if it exists). * * @param gbGrades * List of AssignmentGradeRecord objects. * @param userid * String of user id to find. * * @return * AssignmentGradeRecord if it's student id matches id passed in. NULL otherwise. */ private AssignmentGradeRecord findGradeRecord(List gbGrades, String userid) { Iterator it = gbGrades.iterator(); while (it.hasNext()) { AssignmentGradeRecord agr = (AssignmentGradeRecord) it.next(); if (agr.getStudentId().equals(userid)) { it.remove(); // for efficiency return agr; } } return null; } /** * Used to find assignment in gradebook by its name * * @param list The list of gradebook assignments * @param name The String to look for * * @return Assignment object if found, null otherwise */ private Assignment getAssignmentByName(List assignList, String name) { for (Iterator assignIter = assignList.iterator(); assignIter.hasNext(); ) { Assignment assignment = (Assignment) assignIter.next(); if (assignment.getName().trim().equalsIgnoreCase(name.trim())) { // remove for performance assignIter.remove(); return assignment; } } return null; } /** * Cancel import and return to Import Grades page. * * @return String to navigate to Import Grades page. */ public String processImportAllCancel() { hasUnknownUser = false; hasUnknownAssignments = false; return "spreadsheetAll"; } //save grades and comments public String saveGrades(){ if(logger.isDebugEnabled())logger.debug("create assignment and save grades"); if(logger.isDebugEnabled()) logger.debug("first check if all variables are numeric"); logger.debug("********************" + scores); LetterGradePercentMapping lgpm = new LetterGradePercentMapping(); if (getGradeEntryByLetter()) { lgpm = getGradebookManager().getLetterGradePercentMapping(getGradebook()); } Iterator iter = scores.entrySet().iterator(); while(iter.hasNext()){ Map.Entry entry = (Map.Entry) iter.next(); if(!entry.getKey().equals("Assignment")) { if (getGradeEntryByPoints() || getGradeEntryByPercent()) { String points = (String) entry.getValue(); try{ if(logger.isDebugEnabled()) logger.debug("checking if " +points +" is a numeric value"); double score = convertStringToDouble(points); if (score < 0) { FacesUtil.addErrorMessage(getLocalizedString("import_assignment_negative")); return "spreadsheetPreview"; } }catch(ParseException e){ if(logger.isDebugEnabled()) logger.debug(points + " is not a numeric value"); FacesUtil.addErrorMessage(getLocalizedString("import_assignment_notsupported")); return "spreadsheetPreview"; } } else if (getGradeEntryByLetter()) { if (lgpm.getGradeMap() == null) { FacesUtil.addErrorMessage(getLocalizedString("gb_setup_no_grade_entry_scale")); return "spreadsheetPreview"; } String letterScore = (String) entry.getValue(); if (letterScore != null && letterScore.length() > 0) { String formattedLetterScore = lgpm.standardizeInputGrade(letterScore); if (formattedLetterScore == null) { FacesUtil.addErrorMessage(getLocalizedString("import_assignment_invalid_letter")); return "spreadsheetPreview"; } } } } } try { Category newCategory = retrieveSelectedCategory(); if (newCategory != null) { assignmentId = getGradebookManager().createAssignmentForCategory(getGradebookId(), newCategory.getId(), assignment.getName(), assignment.getPointsPossible(), assignment.getDueDate(), new Boolean(assignment.isNotCounted()),new Boolean(assignment.isReleased()), new Boolean(assignment.isExtraCredit())); } else { assignmentId = getGradebookManager().createAssignment(getGradebookId(), assignment.getName(), assignment.getPointsPossible(), assignment.getDueDate(), new Boolean(assignment.isNotCounted()),new Boolean(assignment.isReleased()), new Boolean(assignment.isExtraCredit())); } FacesUtil.addRedirectSafeMessage(getLocalizedString("add_assignment_save", new String[] {assignment.getName()})); assignment = getGradebookManager().getAssignment(assignmentId); List gradeRecords = new ArrayList(); //initialize comment List List comments = new ArrayList(); //check if a comments column is selected for the defalt select item value is // 0 which mean no comments to be imported if(selectedCommentsColumnId!=null && selectedCommentsColumnId > 0) comments = createCommentList(assignment); if(logger.isDebugEnabled())logger.debug("remove title entry form map"); scores.remove("Assignment"); if(logger.isDebugEnabled())logger.debug("iterate through scores and and save assignment grades"); Iterator it = scores.entrySet().iterator(); while(it.hasNext()){ Map.Entry entry = (Map.Entry) it.next(); String uid = (String) entry.getKey(); String scoreAsString = (String) entry.getValue(); if (scoreAsString != null && scoreAsString.trim().length() > 0) { if (getGradeEntryByPercent() || getGradeEntryByPoints()) { try { Double scoreAsDouble; scoreAsDouble = convertStringToDouble(scoreAsString); if (scoreAsDouble != null) scoreAsDouble = new Double(FacesUtil.getRoundDown(scoreAsDouble.doubleValue(), 2)); AssignmentGradeRecord asnGradeRecord = new AssignmentGradeRecord(assignment,uid,scoreAsDouble); asnGradeRecord.setPercentEarned(scoreAsDouble); // in case gb entry by % - sorted out in manager gradeRecords.add(asnGradeRecord); if(logger.isDebugEnabled())logger.debug("added grades for " + uid + " - score " + scoreAsString); } catch (ParseException pe) { // the score should have already been validated at this point, so // there is something wrong logger.error("ParseException encountered while parsing value: " + scoreAsString + " Score was not updated."); } } else if (getGradeEntryByLetter()) { AssignmentGradeRecord asnGradeRecord = new AssignmentGradeRecord(assignment,uid,null); asnGradeRecord.setLetterEarned(lgpm.standardizeInputGrade(scoreAsString)); gradeRecords.add(asnGradeRecord); } } } if(logger.isDebugEnabled())logger.debug("persist grade records to database"); getGradebookManager().updateAssignmentGradesAndComments(assignment,gradeRecords,comments); getGradebookBean().getEventTrackingService().postEvent("gradebook.importItem","/gradebook/"+getGradebookId()+"/"+assignment.getName()+"/"+getAuthzLevel()); return "spreadsheetListing"; } catch (ConflictingAssignmentNameException e) { if(logger.isErrorEnabled())logger.error(e); FacesUtil.addErrorMessage(getLocalizedString("add_assignment_name_conflict_failure")); } return null; } /** * method creates a collection of comment * objects from a the saved spreadsheet and * selected column. requires an assignment as parameter to set the * gradableObject property of the comment * * @param assignmentTobeCommented * @return List of comment comment objects */ public List createCommentList(Assignment assignmentTobeCommented){ List comments = new ArrayList(); Iterator it = studentRows.iterator(); while(it.hasNext()){ SpreadsheetRow row = (SpreadsheetRow) it.next(); List line = row.getRowcontent(); String userid = ""; String user = (String)line.get(0); try{ userid = ((User)rosterMap.get(line.get(0))).getUserUid(); String commentText = (String) line.get( selectedCommentsColumnId.intValue()); if((!commentText.equals(""))){ Comment comment = new Comment(userid,commentText,assignmentTobeCommented); comments.add(comment); } }catch(Exception e){ if(logger.isDebugEnabled())logger.debug("student is required and "+ user + "is not known to this gradebook"); } } return comments; } public Integer getSelectedCommentsColumnId() { return selectedCommentsColumnId; } public void setSelectedCommentsColumnId(Integer selectedCommentsColumnId) { this.selectedCommentsColumnId = selectedCommentsColumnId; } /** * */ public class Spreadsheet implements Serializable { private String title; private Date date; private String userId; private String contents; private String displayName; private Long gradebookId; private String filename; private List lineitems; private Map selectedAssignment; public Spreadsheet(String title, Date date, String userId, String contents) { if(logger.isDebugEnabled())logger.debug("loading Spreadsheet()"); this.title = title; this.date = date; this.userId = userId; this.contents = contents; } public Spreadsheet() { if(logger.isDebugEnabled())logger.debug("loading Spreadsheet()"); } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContents() { return contents; } public void setContents(String contents) { this.contents = contents; } public String getDisplayName() { return displayName; } public void setDisplayName(String displayName) { this.displayName = displayName; } public Long getGradebookId() { return gradebookId; } public void setGradebookId(Long gradebookId) { this.gradebookId = gradebookId; } public String getFilename() { return filename; } public void setFilename(String filename) { this.filename = filename; } public List getLineitems() { return lineitems; } public void setLineitems(List lineitems) { this.lineitems = lineitems; } public Map getSelectedAssignment() { //logger.debug(this.selectedAssignment); return selectedAssignment; } public void setSelectedAssignment(Map selectedAssignment) { this.selectedAssignment = selectedAssignment; //logger.debug(selectedAssignment); } public String toString() { return "Spreadsheet{" + "title='" + title + '\'' + ", date=" + date + ", userId='" + userId + '\'' + ", contents='" + contents + '\'' + ", displayName='" + displayName + '\'' + ", gradebookId=" + gradebookId + ", filename='" + filename + '\'' + ", lineitems=" + lineitems + ", selectedAssignment=" + selectedAssignment + '}'; } } /** * spreadsheet header class */ public class SpreadsheetHeader implements Serializable{ private List header; private int columnCount; private boolean hasCumulative; public List getHeader() { return header; } public void setHeader(List header) { this.header = header; } public int getColumnCount() { return columnCount; } public void setColumnCount(int columnCount) { this.columnCount = columnCount; } public boolean isHasCumulative() { return hasCumulative; } public void setHasCumulative(boolean hasCumulative) { this.hasCumulative = hasCumulative; } public List getHeaderWithoutUser() { List head = header; head.remove(0); return head; } public List getHeaderWithoutUserAndCumulativeGrade() { List head = getHeaderWithoutUser(); // If CSV from Roster page, last column will be Cumulative Grade // so remove from header list if (head.get(head.size()-1).equals("Cumulative")) { head.remove(head.size()-1); } return head; } public SpreadsheetHeader(String source) { if(logger.isDebugEnabled()) logger.debug("creating header from "+source); header = new ArrayList(); CSV csv = new CSV(); header = csv.parse(source); columnCount = header.size(); hasCumulative = header.get(columnCount-1).equals("Cumulative"); } } /** * spreadsheetRow class */ public class SpreadsheetRow implements Serializable { private List rowcontent; private int columnCount; private String userDisplayName; private String userId; private String userUid; private boolean isKnown; public SpreadsheetRow(String source) { // this may be instantiated before SpreadsheetUploadBean is initialized, so make sure // the rosterMap is populated if (rosterMap == null) { initializeRosterMap(); } if(logger.isDebugEnabled()) logger.debug("creating row from string " + source); rowcontent = new ArrayList(); CSV csv = new CSV(); rowcontent = csv.parse(source); // derive the user information String userContent = (String) rowcontent.get(0); if (userContent != null) { userId = userContent.toLowerCase(); // check to see if this student is in the roster if (rosterMap.containsKey(userId)) { isKnown = true; User user = (User)rosterMap.get(userId); userDisplayName = user.getDisplayName(); userUid = user.getUserUid(); if(logger.isDebugEnabled())logger.debug("get userid "+ userId + "username is "+userDisplayName); } else { isKnown = false; userDisplayName = getLocalizedString("import_preview_unknown_name"); userUid = null; if (logger.isDebugEnabled()) logger.debug("User " + userId + " is unknown to this gradebook"); } } else { isKnown = false; userDisplayName = getLocalizedString("import_preview_unknown_name"); userUid = null; userId = null; if(logger.isDebugEnabled())logger.debug("Null userId in spreadsheet"); } } public List getRowcontent() { return rowcontent; } public void setRowcontent(List rowcontent) { this.rowcontent = rowcontent; } public int getColumnCount() { return columnCount; } public void setColumnCount(int columnCount) { this.columnCount = columnCount; } public String getUserDisplayName() { return userDisplayName; } public void setUserDisplayName(String userDisplayName) { this.userDisplayName = userDisplayName; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getUserUid() { return userUid; } public void setUserUid(String userUid) { this.userUid = userUid; } public boolean isKnown() { return isKnown; } public void setKnown(boolean known) { isKnown = known; } } //csv class to handle /** Parse comma-separated values (CSV), a common Windows file format. * Sample input: "LU",86.25,"11/4/1998","2:19PM",+4.0625 * <p> * Inner logic adapted from a C++ original that was * Copyright (C) 1999 Lucent Technologies * Excerpted from 'The Practice of Programming' * by Brian W. Kernighan and Rob Pike. * <p> * Included by permission of the http://tpop.awl.com/ web site, * which says: * "You may use this code for any purpose, as long as you leave * the copyright notice and book citation attached." I have done so. * @author Brian W. Kernighan and Rob Pike (C++ original) * @author Ian F. Darwin (translation into Java and removal of I/O) * @author Ben Ballard (rewrote advQuoted to handle '""' and for readability) */ class CSV { public static final char DEFAULT_SEP = ','; /** Construct a CSV parser, with the default separator (`,'). */ public CSV() { this(DEFAULT_SEP); } /** Construct a CSV parser with a given separator. * @param sep The single char for the separator (not a list of * separator characters) */ public CSV(char sep) { fieldSep = sep; } /** The fields in the current String */ protected List list = new ArrayList(); /** the separator char for this parser */ protected char fieldSep; /** parse: break the input String into fields * @return java.util.Iterator containing each field * from the original as a String, in order. */ public List parse(String line) { StringBuilder sb = new StringBuilder(); list.clear(); // recycle to initial state int i = 0; if (line.length() == 0) { list.add(line); return list; } do { sb.setLength(0); if (i < line.length() && line.charAt(i) == '"') i = advQuoted(line, sb, ++i); // skip quote else i = advPlain(line, sb, i); list.add(sb.toString()); i++; } while (i < line.length()); if(logger.isDebugEnabled()) { StringBuilder logBuffer = new StringBuilder("Parsed " + line + " as: "); for(Iterator iter = list.iterator(); iter.hasNext();) { logBuffer.append(iter.next()); if(iter.hasNext()) { logBuffer.append(", "); } } logger.debug("Parsed source string " + line + " as " + logBuffer.toString() + ", length=" + list.size()); } return list; } /** advQuoted: quoted field; return index of next separator */ protected int advQuoted(String s, StringBuilder sb, int i) { int j; int len= s.length(); for (j=i; j<len; j++) { if (s.charAt(j) == '"' && j+1 < len) { if (s.charAt(j+1) == '"') { j++; // skip escape char } else if (s.charAt(j+1) == fieldSep) { //next delimeter j++; // skip end quotes break; } } else if (s.charAt(j) == '"' && j+1 == len) { // end quotes at end of line break; //done } sb.append(s.charAt(j)); // regular character. } return j; } /** advPlain: unquoted field; return index of next separator */ protected int advPlain(String s, StringBuilder sb, int i) { int j; j = s.indexOf(fieldSep, i); // look for separator if (j == -1) { // none found sb.append(s.substring(i)); return s.length(); } else { sb.append(s.substring(i, j)); return j; } } } //************************ EXCEL file parsing ***************************** /** * method converts an input stream to an List consisting of strings * representing a line. The input stream must be for an xls file. * * @param inputStream * @return contents */ private List excelToArray(InputStream inputStreams) throws IOException { HSSFWorkbook wb = new HSSFWorkbook(inputStreams); //Convert an Excel file to csv HSSFSheet sheet = wb.getSheetAt(0); List array = new ArrayList(); Iterator it = sheet.rowIterator(); while (it.hasNext()){ HSSFRow row = (HSSFRow) it.next(); String rowAsString = fromHSSFRowtoCSV(row); if (rowAsString.replaceAll(",", "").replaceAll("\"", "").equals("")) { continue; } array.add(fromHSSFRowtoCSV(row)); } return array; } private String fromHSSFRowtoCSV(HSSFRow row){ StringBuffer csvRow = new StringBuffer(); int l = row.getLastCellNum(); for (int i=0;i<l;i++){ HSSFCell cell = row.getCell((short)i); String cellValue = ""; if (cell == null || cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) { cellValue = ""; } else if (cell.getCellType()== HSSFCell.CELL_TYPE_STRING){ cellValue = "\"" + cell.getStringCellValue() + "\""; } else if (cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC){ double value = cell.getNumericCellValue(); cellValue = getNumberFormat().format(value); cellValue = "\"" + cellValue + "\""; } csvRow.append(cellValue); if (i<l){ csvRow.append(getCsvDelimiter().toCharArray()[0]); } } return csvRow.toString(); } /** * Parse newer OOXML Excel Spreadsheets * @param inputStreams * @return * @throws IOException */ private List<String> excelOOXMLToArray(InputStream inputStreams) throws IOException { XSSFWorkbook wb = new XSSFWorkbook(inputStreams); //Convert an Excel OOXML (.xslx) file to csv XSSFSheet sheet = wb.getSheetAt(0); List<String> array = new ArrayList<String>(); Iterator it = sheet.rowIterator(); while (it.hasNext()){ XSSFRow row = (XSSFRow) it.next(); String rowAsString = fromXSSFRowtoCSV(row); if (rowAsString.replaceAll(",", "").replaceAll("\"", "").equals("")) { continue; } array.add(fromXSSFRowtoCSV(row)); } return array; } private String fromXSSFRowtoCSV(XSSFRow row){ StringBuffer csvRow = new StringBuffer(); int l = row.getLastCellNum(); for (int i=0;i<l;i++){ XSSFCell cell = row.getCell((short)i); String cellValue = ""; if (cell == null || cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) { cellValue = ""; } else if (cell.getCellType()== HSSFCell.CELL_TYPE_STRING){ cellValue = "\"" + cell.getStringCellValue() + "\""; } else if (cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC){ double value = cell.getNumericCellValue(); cellValue = getNumberFormat().format(value); cellValue = "\"" + cellValue + "\""; } csvRow.append(cellValue); if (i<l){ csvRow.append(getCsvDelimiter().toCharArray()[0]); } } return csvRow.toString(); } /** * Process an upload ActionEvent from spreadsheetUpload.jsp or spreadsheetEntireGBImport.jsp * Source of this action is the Upload button on either page * * @param event */ public void launchFilePicker(ActionEvent event) { try { String titleText = FacesUtil.getLocalizedString("upload_view_page_title"); String instructionText = FacesUtil.getLocalizedString("upload_view_instructions_text"); ToolSession currentToolSession = SessionManager.getCurrentToolSession(); // SAK-14173 - store title currentToolSession.setAttribute(IMPORT_TITLE, this.title); currentToolSession.setAttribute(FilePickerHelper.FILE_PICKER_MAX_ATTACHMENTS, FilePickerHelper.CARDINALITY_SINGLE); currentToolSession.setAttribute(FilePickerHelper.FILE_PICKER_TITLE_TEXT, titleText); currentToolSession.setAttribute(FilePickerHelper.FILE_PICKER_INSTRUCTION_TEXT, instructionText); currentToolSession.setAttribute(FilePickerHelper.FILE_PICKER_RESOURCE_FILTER, ComponentManager.get("org.sakaiproject.content.api.ContentResourceFilter.spreadsheetCsvFile")); ExternalContext context = FacesContext.getCurrentInstance().getExternalContext(); context.redirect("sakai.filepicker.helper/tool"); } catch(Exception e) { logger.error(this + ".launchFilePicker - " + e); e.printStackTrace(); } } /** * SAK-14173 - This event will cause the title to be stored in the bean so that it can be stored * in the session when the user goes to choose a file and will not be lost * @param event JSF value change event */ public void changeTitle(ValueChangeEvent event) { this.title = (String) event.getNewValue(); } /** * SAK-14173 - Clears all the session variables stored as part of the spreadsheet upload code * @param event JSF event */ public void cancelAndClearSession(ActionEvent event) { clearImportTitle(); clearPickedFile(); } /** * SAK-14173 - Clears the import title from the session */ private void clearImportTitle() { ToolSession currentToolSession = SessionManager.getCurrentToolSession(); currentToolSession.removeAttribute(IMPORT_TITLE); } /** * Extract and store the reference to the uploaded file. * Only process the first reference - don't do multiple uploads. * */ public void savePickedUploadFile() { ToolSession session = SessionManager.getCurrentToolSession(); if (session.getAttribute(PICKED_FILE_REFERENCE) != null) { pickedFileReference = (String) session.getAttribute(PICKED_FILE_REFERENCE); pickedFileDesc = (String) session.getAttribute(PICKED_FILE_DESC); } } /** * Use the uploaded file reference to get an InputStream. * Must be a reference to a ContentResource * @param resource the ContentResource associated with {@link #pickedFileReference}. * see {@link #getPickedContentResource()} * @return InputStream */ private InputStream getPickedFileStream(ContentResource resource) { InputStream inStream = null; if (resource != null) { try { inStream = resource.streamContent(); } catch(ServerOverloadException soe) { logger.error(soe.getStackTrace()); } } return inStream; } /** * * @return the ContentResource associated with the {@link #pickedFileReference} property. * returns null if the entity is not a ContentResource */ private ContentResource getPickedContentResource() { ContentResource resource = null; if (pickedFileReference != null) { Reference ref = EntityManager.newReference(pickedFileReference); if (ref != null) { Entity ent = ref.getEntity(); if (ent instanceof ContentResource) { // entity came from file picker, so it should be a content resource resource = (ContentResource) ent; } } } return resource; } private void clearPickedFile() { ToolSession session = SessionManager.getCurrentToolSession(); session.removeAttribute(PICKED_FILE_REFERENCE); session.removeAttribute(PICKED_FILE_DESC); pickedFileDesc = null; pickedFileReference = null; } /** * @return the pickedFileDesc */ public String getPickedFileDesc() { // check the session attribute first ToolSession session = SessionManager.getCurrentToolSession(); if (session.getAttribute(PICKED_FILE_DESC) != null) { pickedFileDesc = (String) session.getAttribute(PICKED_FILE_DESC); } return pickedFileDesc; } /** * @param pickedFileDesc the pickedFileDesc to set */ public void setPickedFileDesc(String pickedFileDesc) { this.pickedFileDesc = pickedFileDesc; } /** * * @return the number of users with the student-type role enrolled in the current site */ private int getNumStudentsInSite() { List enrollments = getSectionAwareness().getSiteMembersInRole(getGradebookUid(), Role.STUDENT); int numStudentsInSite = enrollments != null ? enrollments.size() : 0; return numStudentsInSite; } private String getCsvDelimiter() { if (csvDelimiter == null) { csvDelimiter = ServerConfigurationService.getString("csv.separator", ","); } return csvDelimiter; } /** * * @param doubleAsString * @return a locale-aware Double value representation of the given String * @throws ParseException */ private Double convertStringToDouble(String doubleAsString) throws ParseException { Double scoreAsDouble = null; if (doubleAsString != null) { Number numericScore = getNumberFormat().parse(doubleAsString.trim()); scoreAsDouble = numericScore.doubleValue(); } return scoreAsDouble; } private NumberFormat getNumberFormat() { if (numberFormat == null) { numberFormat = NumberFormat.getInstance(new ResourceLoader().getLocale()); } return numberFormat; } public String getDateEntryFormatDescription(){ return this.date_entry_format_description; } }