/*==========================================================================*\ | $Id: EditFileCommentsPage.java,v 1.7 2011/10/25 15:32:06 stedwar2 Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2006-2010 Virginia Tech | | This file is part of Web-CAT. | | Web-CAT is free software; you can redistribute it and/or modify | it under the terms of the GNU Affero General Public License as published | by the Free Software Foundation; either version 3 of the License, or | (at your option) any later version. | | Web-CAT is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | You should have received a copy of the GNU Affero General Public License | along with Web-CAT; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package org.webcat.grader; import com.webobjects.appserver.*; import com.webobjects.eocontrol.*; import com.webobjects.foundation.*; import java.io.*; import java.util.*; import org.apache.log4j.Logger; import org.jdom.*; import org.jdom.input.SAXBuilder; import org.jdom.output.XMLOutputter; import org.webcat.core.*; import org.webcat.grader.messaging.GraderMarkupParseError; import org.webcat.woextensions.WCResourceManager; import com.webobjects.appserver.WODisplayGroup; // ------------------------------------------------------------------------- /** * This class presents the details for one file in a submission, * including all markup comments and a color-highlighted version * of the source code. * * @author Stephen Edwards, Hussein Vastani * @author Last changed by $Author: stedwar2 $ * @version $Revision: 1.7 $, $Date: 2011/10/25 15:32:06 $ */ public class EditFileCommentsPage extends GraderComponent { //~ Constructors .......................................................... // ---------------------------------------------------------- /** * This is the default constructor * * @param context The page's context */ public EditFileCommentsPage(WOContext context) { super(context); } //~ KVC attributes (must be public) ....................................... public SubmissionFileStats fileStats; public WODisplayGroup filesDisplayGroup; public SubmissionFileStats selectedFile; public SubmissionFileStats file; public NSArray<Byte> formats = SubmissionResult.formats; public byte aFormat; //~ Methods ............................................................... // ---------------------------------------------------------- /** * Adds to the response of the page * * @param response The response being built * @param context The context of the request */ protected void beforeAppendToResponse( WOResponse response, WOContext context) { if (fileStats == null) { fileStats = prefs().submissionFileStats(); } if (log.isDebugEnabled()) { log.debug("beginning appendToResponse()"); SubmissionResult result = fileStats.submissionResult(); log.debug("result = " + result); if (result != null) { log.debug("result = " + result.hashCode()); log.debug("result EC = " + result.editingContext().hashCode()); } } initializeCodeWithComments(); selectedFile = null; filesDisplayGroup.setObjectArray( fileStats.submissionResult().submissionFileStats()); priorOverallComments = fileStats.submissionResult().comments(); super.beforeAppendToResponse(response, context); } // ---------------------------------------------------------- protected void afterAppendToResponse( WOResponse response, WOContext context) { super.afterAppendToResponse(response, context); codeWithComments = null; log.debug("ending appendToResponse()"); } // ---------------------------------------------------------- public WOComponent saveChanges() { try { updateTAScore(storeComments()); fileStats.submissionResult().addCommentByLineFor( user(), priorOverallComments); if (fileStats.submissionResult().changedProperties().size() > 0) { fileStats.submissionResult().setLastUpdated(new NSTimestamp()); } applyLocalChanges(); } catch (Exception e) { // This is thrown by an XML parse error in storeComments(), // so use it to avoid updating the TA Score error("An error occurred while reading your comments. " + "They could not be saved successfully. The situation has " + "been reported to the administrator."); } return hasMessages() ? null : goToSelectedDestination(); } // ---------------------------------------------------------- public WOComponent goToSelectedDestination() { if (selectedFile == null) { return super.next(); } else { prefs().setSubmissionFileStatsRelationship(selectedFile); WCComponent statsPage = pageWithName(EditFileCommentsPage.class); statsPage.nextPage = this.nextPage; return statsPage; } } // ---------------------------------------------------------- public void updateTAScore(double ptsTakenOff) { if (ptsTakenOff == 0.0d) { // do nothing return; } double score = 0.0; SubmissionResult result = fileStats.submissionResult(); Number taScore = result.taScoreRaw(); Number possible = result.submission().assignmentOffering().assignment() .submissionProfile().taPointsRaw(); log.debug( "updateTAScore()"); if ( taScore != null) { //calculate the TA score score = taScore.doubleValue(); log.debug(" TA score = " + score); // now subtract the new score, which takes care of the // difference in the scores score += ptsTakenOff; log.debug(" After subtracting new deductions, score now is = " + score); result.setTaScore(score); } else { if (possible != null) // there is a possible value { score = possible.doubleValue(); score += ptsTakenOff; // adding the deductions result.setTaScore(score); } else { result.setTaScore(result.taScore() + ptsTakenOff); } } double oldDeductions = fileStats.deductions(); log.debug("old deductions = " + oldDeductions); fileStats.setDeductions(oldDeductions + ptsTakenOff); log.debug("new deductions = " + fileStats.deductions()); if (result.status() == Status.TO_DO) { result.setStatus(Status.UNFINISHED); } } // ---------------------------------------------------------- public boolean isGrading() { return true; } /** * This function is called when we save the file after adding comments. It * will extract all the comments that were added and insert them in the * database. * * @return the cumulative score adjustment of all the comments in the file * @throws Exception if an error occurred */ public double storeComments() throws Exception { boolean commentsModified = false; double taPts = 0.0; EOEditingContext ec = localContext(); if (codeWithCommentsToStore == null) { return taPts; } if (codeWithCommentsToStore.length() < 50 && codeWithCommentsToStore.trim().equals("<br />")) { // An error occurred, but it should have already been // trapped elsewhere throw new Exception( "storeComments(): null (<br/>) code returned from client"); } try { // remove the link statement if (log.isDebugEnabled()) { log.debug("before subst:\n---------------------------------"); log.debug(codeWithCommentsToStore.substring(0, 200)); log.debug("----------------------------------"); } codeWithCommentsToStore = codeWithCommentsToStore .replaceFirst("^\\s*<![^>]*>\\s*", "") .replaceFirst("^\\s*<link [^>]*>\\s*", ""); if (log.isDebugEnabled()) { log.debug("after subst:\n----------------------------------"); log.debug(codeWithCommentsToStore.substring(0, 200)); log.debug("----------------------------------"); } SAXBuilder parser = new SAXBuilder(); // Try parsing the file/string with the parser Document doc = parser.build(new StringReader(codeWithCommentsToStore)); Element root = doc.getRootElement(); @SuppressWarnings("unchecked") List<Element> children = root.getChild("tbody").getChildren(); // Delete existing comments by the current user from the // database for (SubmissionFileComment thisComment : fileStats.comments()) { // if its the current users comments if (user() == thisComment.author()) { log.debug("Deleting comment, line " + thisComment.lineNo()); taPts -= thisComment.deduction(); ec.deleteObject(thisComment); commentsModified = true; } } applyLocalChanges(); // check all children for comment box (id should have I) and // then extract values for (Element child : children) { // get the id attribute from the row String id = child.getAttributeValue("id"); if (id == null) { log.error("html tag is missing id attribute:" + fileStats.markupFile().getPath()); continue; } if ( (id.charAt(0) == '\"') && (id.charAt(id.length() - 1) == '\"')) { // if there are quotes around the attribute value id = id.substring(1, id.length() - 1); } String [] idarr = id.split(":"); if (idarr[0].charAt(0) == 'I') { // This is a table tag int boxnum = Integer.parseInt( idarr[0].substring(1, idarr[0].length())); int rownum = Integer.parseInt(idarr[1]); int refnum = Integer.parseInt(idarr[2]); String idpart = "I" + boxnum + ":" + rownum + ":" + refnum; log.debug("Tagged comment block found: " + idpart); Element msg = getElementById(child, idpart + ":M"); if (msg != null) { SubmissionFileComment comment = new SubmissionFileComment(); ec.insertObject(comment); applyLocalChanges(); comment.setAuthorRelationship(user()); comment.setSubmissionFileStatsRelationship(fileStats); Element target = getElementById(child, idpart + ":T"); if (target != null) { comment.setTo( target.getAttribute("value").getValue()); log.debug(" to = " + comment.toNo()); } Element category = getElementById(child, idpart + ":C"); if (category != null) { comment.setCategory(category.getText()); log.debug(" category = " + comment.category()); } Element pts = getElementById(child, idpart + ":P"); if (pts != null) { String ptVal = pts.getText(); int colonIndex = ptVal.indexOf(":"); if (colonIndex != -1) { // colon exists, so remove it ptVal = ptVal.substring( colonIndex + 1, ptVal.length()); } ptVal = ptVal.trim(); if (!ptVal.equals("")) { try { double deduction = Double.parseDouble(ptVal); taPts += deduction; comment.setDeduction(deduction); } catch (NumberFormatException e) { log.error("Double conversion failure on " + "ptVal argument '" + ptVal + "'", e); } } else // if no points given , then 0 is by default { comment.setDeduction(0.0); } log.debug(" deduction = " + comment.deductionRaw()); } XMLOutputter outputter = new XMLOutputter(); outputter.setOmitDeclaration(true); String newmes = outputter.outputString(msg); newmes = newmes.replaceAll(idpart,"&&&&"); comment.setMessage(newmes); log.debug(" message = '" + comment.message() + "'"); comment.setLineNo(rownum); log.debug(" line = " + comment.lineNo()); applyLocalChanges(); log.debug("result = " + comment); commentsModified = true; } else { log.debug("Skipping blank separator TR"); } } } if (commentsModified) { fileStats.submissionResult().setLastUpdated(new NSTimestamp()); if (fileStats.submissionResult().status() == Status.TO_DO) { fileStats.submissionResult().setStatus(Status.UNFINISHED); } } applyLocalChanges(); } catch (Exception e) { log.error("exception reading comments for " + fileStats.markupFile().getPath(), e); new GraderMarkupParseError( fileStats.submissionResult().submission(), GraderMarkupParseError.LOCATION_EDIT_FILE_COMMENTS, e, context(), fileStats.markupFile(), "Raw XML (value of codeWithComments):\n" + codeWithCommentsToStore).send(); throw e; } // Let the raw string be garbage collected, now that we're // finished with it codeWithCommentsToStore = null; log.debug(" Store comments is returning = " + taPts); return taPts; } // ---------------------------------------------------------- public String codeWithComments() { return codeWithComments; } // ---------------------------------------------------------- public void initializeCodeWithComments() { try { codeWithComments = fileStats.codeWithComments( user(), isGrading(), context().request()); if (log.isDebugEnabled()) { log.debug("codeWithComments = " + codeWithComments.substring(0, 200)); } } catch (Exception e) { error("An error occurred while trying to prepare the source code " + "view for this file. The error has been reported to the " + "administrator. Please do not try to edit the comments " + "in this file until the situation has been resolved."); codeWithComments = null; } } // ---------------------------------------------------------- public void setCodeWithComments(String code) { codeWithCommentsToStore = code; } // ---------------------------------------------------------- public String javascriptText() { StringBuffer buffer = new StringBuffer(200); buffer.append("<script type=\"text/javascript\" src=\""); buffer.append(WCResourceManager.resourceURLFor( "htmlarea/codearea.js", "Grader", null, context().request())); buffer.append("\"></script>\n"); buffer.append("<script type=\"text/javascript\" src=\""); buffer.append(WCResourceManager.resourceURLFor( "htmlarea/htmlarea-lang-en.js", "Grader", null, context().request())); buffer.append("\"></script>\n"); buffer.append("<script type=\"text/javascript\" src=\""); buffer.append(WCResourceManager.resourceURLFor( "htmlarea/dialog.js", "Grader", null, context().request())); buffer.append("\"></script>\n"); buffer.append("<script type=\"text/javascript\">\n"); buffer.append("var editor = null;\nfunction initEditor() {\n"); buffer.append("editor = new HTMLArea(\"source\");\n"); { String url = WCResourceManager.versionlessResourceURLFor( "htmlarea/htmlarea.js", "Grader", null, context().request()); if (url != null) { buffer.append("editor.config.editorURL = \""); buffer.append(url.substring(0, url.length() - "htmlarea.js".length())); buffer.append("\";\n"); } url = WCResourceManager.versionlessResourceURLFor( "images/blank.gif", "Core", null, context().request()); if (url != null) { buffer.append("editor.config.coreResourceURL = \""); buffer.append(url.substring(0, url.length() - "images/blank.gif".length())); buffer.append("\";\n"); } } buffer.append("editor.generate();\n"); if (wcSession() != null && user() != null) { buffer.append("editor.config.userName = \""); buffer.append(user().name()); buffer.append("\";\n"); } buffer.append("editor.config.numComments = "); try { buffer.append(fileStats.comments().count()); } catch (Exception e) { buffer.append(0); log.debug("Exception caught in javascriptText():", e); } buffer.append(";\n"); buffer.append("editor.config.viewPoints = "); // Check to see if the current user is authorized to view or modify // the deductions buffer.append(isGrading()); buffer.append(";\n}\n"); buffer.append("dojo.addOnLoad(function() { initEditor() });\n"); buffer.append("</script>\n"); return buffer.toString(); } // ---------------------------------------------------------- public void setJavascriptText(String text) { // What? This is just to satisfy WO, since we never need // to set this field meaningfully } // ---------------------------------------------------------- private static Element getElementById(Element current, String id) { Element result = null; if (current != null) { String thisId = current.getAttributeValue("id"); if (thisId != null && thisId.equals(id)) { result = current; } else { @SuppressWarnings("unchecked") List<Element> children = current.getChildren(); for (Element child : children) { Element e = getElementById(child, id); if (e != null) { result = e; break; } } } } return result; } // ---------------------------------------------------------- public Byte commentFormat() { Byte format = SubmissionResult.formats.get(0); if (fileStats.submissionResult() != null) { format = SubmissionResult.formats.get( fileStats.submissionResult().commentFormat()); } return format; } // ---------------------------------------------------------- public void setCommentFormat(Byte format) { if (format != null && fileStats.submissionResult() != null) { fileStats.submissionResult().setCommentFormat(format.byteValue()); } } // ---------------------------------------------------------- public String formatLabel() { return SubmissionResult.formatStrings.objectAtIndex(aFormat); } //~ Instance/static variables ............................................. private String codeWithComments = null; private String codeWithCommentsToStore = null; private String priorOverallComments = null; static Logger log = Logger.getLogger(EditFileCommentsPage.class); }