Java tutorial
/** * Copyright (c) 2003 The Apereo 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://opensource.org/licenses/ecl2 * * 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.contentreview.turnitin; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Random; import java.util.Set; import java.util.SortedSet; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.validator.routines.EmailValidator; import org.sakaiproject.api.common.edu.person.SakaiPerson; import org.sakaiproject.api.common.edu.person.SakaiPersonManager; import org.sakaiproject.authz.api.SecurityAdvisor; import org.sakaiproject.authz.api.SecurityService; import org.sakaiproject.component.api.ServerConfigurationService; import org.sakaiproject.content.api.ContentHostingService; import org.sakaiproject.content.api.ContentResource; import org.sakaiproject.contentreview.advisors.ContentReviewSiteAdvisor; import org.sakaiproject.contentreview.dao.ContentReviewConstants; import org.sakaiproject.contentreview.dao.ContentReviewItem; import org.sakaiproject.contentreview.exception.QueueException; import org.sakaiproject.contentreview.exception.ReportException; import org.sakaiproject.contentreview.exception.SubmissionException; import org.sakaiproject.contentreview.exception.TransientSubmissionException; import org.sakaiproject.contentreview.service.ContentReviewQueueService; import org.sakaiproject.contentreview.service.ContentReviewService; import org.sakaiproject.contentreview.turnitin.util.TurnitinAPIUtil; import org.sakaiproject.entity.api.Entity; import org.sakaiproject.entity.api.EntityManager; import org.sakaiproject.entity.api.EntityProducer; import org.sakaiproject.entity.api.Reference; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.entitybroker.EntityReference; import org.sakaiproject.exception.IdUnusedException; import org.sakaiproject.exception.PermissionException; import org.sakaiproject.exception.TypeException; import org.sakaiproject.service.gradebook.shared.Assignment; import org.sakaiproject.service.gradebook.shared.GradebookExternalAssessmentService; import org.sakaiproject.service.gradebook.shared.GradebookNotFoundException; import org.sakaiproject.service.gradebook.shared.GradebookService; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.SiteService; import org.sakaiproject.tool.api.Session; import org.sakaiproject.tool.api.SessionManager; import org.sakaiproject.tool.api.ToolManager; import org.sakaiproject.user.api.PreferencesService; import org.sakaiproject.user.api.User; import org.sakaiproject.user.api.UserDirectoryService; import org.sakaiproject.user.api.UserNotDefinedException; import org.sakaiproject.util.ResourceLoader; import org.w3c.dom.CharacterData; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import lombok.Setter; @Slf4j public class TurnitinReviewServiceImpl implements ContentReviewService { public static final String TURNITIN_DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; private static final String SERVICE_NAME = "Turnitin"; // Site property to enable or disable use of Turnitin for the site private static final String TURNITIN_SITE_PROPERTY = "turnitin"; final static long LOCK_PERIOD = 12000000; private boolean studentAccountNotified = true; private int sendSubmissionNotification = 0; private Long maxRetry = null; // note that the assignment id actually has to be unique globally so use // this as a prefix // eg. assignid = defaultAssignId + siteId private String defaultAssignId = null; private String defaultClassPassword = null; private List<String> enabledSiteTypes; /** * If set to true in properties, will result in 3 random digits being * appended to the email name. In other words, adrian.r.fish@gmail.com will * become something like adrian.r.fish593@gmail.com */ private boolean spoilEmailAddresses = false; /** Prefer system profile email addresses */ private boolean preferSystemProfileEmail = true; /** Use guest account eids as email addresses */ private boolean preferGuestEidEmail = true; @Setter private TurnitinAccountConnection turnitinConn; @Setter private ServerConfigurationService serverConfigurationService; @Setter private EntityManager entityManager; @Setter private ContentHostingService contentHostingService; @Setter private SakaiPersonManager sakaiPersonManager; @Setter private UserDirectoryService userDirectoryService; @Setter private SiteService siteService; @Setter private PreferencesService preferencesService; @Setter private TurnitinContentValidator turnitinContentValidator; @Setter private GradebookService gradebookService; @Setter private GradebookExternalAssessmentService gradebookExternalAssessmentService; @Setter private SecurityService securityService; @Setter private SessionManager sessionManager; @Setter private ContentReviewSiteAdvisor siteAdvisor; @Setter private ToolManager toolManager; @Setter ContentReviewQueueService crqs; public void init() { studentAccountNotified = turnitinConn.isStudentAccountNotified(); sendSubmissionNotification = turnitinConn.getSendSubmissionNotification(); maxRetry = turnitinConn.getMaxRetry(); defaultAssignId = turnitinConn.getDefaultAssignId(); defaultClassPassword = turnitinConn.getDefaultClassPassword(); spoilEmailAddresses = serverConfigurationService.getBoolean("turnitin.spoilEmailAddresses", false); preferSystemProfileEmail = serverConfigurationService.getBoolean("turnitin.preferSystemProfileEmail", true); preferGuestEidEmail = serverConfigurationService.getBoolean("turnitin.preferGuestEidEmail", true); enabledSiteTypes = Arrays .asList(ArrayUtils.nullToEmpty(serverConfigurationService.getStrings("turnitin.sitetypes"))); log.info("init(): spoilEmailAddresses=" + spoilEmailAddresses + " preferSystemProfileEmail=" + preferSystemProfileEmail + " preferGuestEidEmail=" + preferGuestEidEmail); if (enabledSiteTypes != null && !enabledSiteTypes.isEmpty()) { log.info("Turnitin is enabled for site types: " + StringUtils.join(enabledSiteTypes, ",")); } if (!turnitinConn.isUseSourceParameter()) { if (serverConfigurationService.getBoolean("turnitin.updateAssingments", false)) doAssignments(); } } @Override public String getServiceName() { return SERVICE_NAME; } /** * Allow Turnitin for this site? */ @Override public boolean isSiteAcceptable(Site s) { if (s == null) { return false; } log.debug("isSiteAcceptable: " + s.getId() + " / " + s.getTitle()); // Delegated to another bean if (siteAdvisor != null) { return siteAdvisor.siteCanUseReviewService(s); } // Check site property ResourceProperties properties = s.getProperties(); String prop = (String) properties.get(TURNITIN_SITE_PROPERTY); if (prop != null) { log.debug("Using site property: " + prop); return Boolean.parseBoolean(prop); } // Check list of allowed site types, if defined if (enabledSiteTypes != null && !enabledSiteTypes.isEmpty()) { log.debug("Using site type: " + s.getType()); return enabledSiteTypes.contains(s.getType()); } // No property set, no restriction on site types, so allow return true; } public String getIconUrlforScore(Long score) { String urlBase = "/library/content-review/score_"; String suffix = ".gif"; if (score.equals(Long.valueOf(0))) { return urlBase + "blue" + suffix; } else if (score.compareTo(Long.valueOf(25)) < 0) { return urlBase + "green" + suffix; } else if (score.compareTo(Long.valueOf(50)) < 0) { return urlBase + "yellow" + suffix; } else if (score.compareTo(Long.valueOf(75)) < 0) { return urlBase + "orange" + suffix; } else { return urlBase + "red" + suffix; } } /** * This uses the default Instructor information or current user. * * @see org.sakaiproject.contentreview.impl.BaseReviewServiceImpl#getReviewReportInstructor(java.lang.String) */ public String getReviewReportInstructor(String contentId, String assignmentRef, String userId) throws QueueException, ReportException { Optional<ContentReviewItem> matchingItem = crqs.getQueuedItem(getProviderId(), contentId); if (!matchingItem.isPresent()) { log.debug("Content " + contentId + " has not been queued previously"); throw new QueueException("Content " + contentId + " has not been queued previously"); } // check that the report is available // TODO if the database record does not show report available check with // turnitin (maybe) ContentReviewItem item = matchingItem.get(); if (item.getStatus() .compareTo(ContentReviewConstants.CONTENT_REVIEW_SUBMITTED_REPORT_AVAILABLE_CODE) != 0) { log.debug("Report not available: " + item.getStatus()); throw new ReportException("Report not available: " + item.getStatus()); } // report is available - generate the URL to display String oid = item.getExternalId(); String fid = "6"; String fcmd = "1"; String cid = item.getSiteId(); String assignid = defaultAssignId + item.getSiteId(); String utp = "2"; Map params = TurnitinAPIUtil.packMap(turnitinConn.getBaseTIIOptions(), "fid", fid, "fcmd", fcmd, "assignid", assignid, "cid", cid, "oid", oid, "utp", utp); params.putAll(getInstructorInfo(item.getSiteId())); return turnitinConn.buildTurnitinURL(params); } public String getReviewReportStudent(String contentId, String assignmentRef, String userId) throws QueueException, ReportException { Optional<ContentReviewItem> matchingItem = crqs.getQueuedItem(getProviderId(), contentId); if (!matchingItem.isPresent()) { log.debug("Content " + contentId + " has not been queued previously"); throw new QueueException("Content " + contentId + " has not been queued previously"); } // check that the report is available // TODO if the database record does not show report available check with // turnitin (maybe) ContentReviewItem item = matchingItem.get(); if (item.getStatus() .compareTo(ContentReviewConstants.CONTENT_REVIEW_SUBMITTED_REPORT_AVAILABLE_CODE) != 0) { log.debug("Report not available: " + item.getStatus()); throw new ReportException("Report not available: " + item.getStatus()); } // report is available - generate the URL to display String oid = item.getExternalId(); String fid = "6"; String fcmd = "1"; String cid = item.getSiteId(); String assignid = defaultAssignId + item.getSiteId(); User user = userDirectoryService.getCurrentUser(); // USe the method to get the correct email String uem = getEmail(user); String ufn = getUserFirstName(user); String uln = getUserLastName(user); String uid = item.getUserId(); String utp = "1"; Map params = TurnitinAPIUtil.packMap(turnitinConn.getBaseTIIOptions(), "fid", fid, "fcmd", fcmd, "assignid", assignid, "uid", uid, "cid", cid, "oid", oid, "uem", uem, "ufn", ufn, "uln", uln, "utp", utp); return turnitinConn.buildTurnitinURL(params); } public String getReviewReport(String contentId, String assignmentRef, String userId) throws QueueException, ReportException { // first retrieve the record from the database to get the externalId of // the content log.warn("Deprecated Methog getReviewReport(String contentId) called"); return this.getReviewReportInstructor(contentId, assignmentRef, userId); } private Optional<ContentReviewItem> getItemByContentId(String contentId) { return crqs.getQueuedItem(getProviderId(), contentId); } /** * Get additional data from String if available * * @return array containing site ID, Task ID, Task Title */ private String[] getAssignData(String data) { String[] assignData = null; try { if (data.contains("#")) { assignData = data.split("#"); } } catch (Exception e) { } return assignData; } public String getInlineTextId(String assignmentReference, String userId, long submissionTime) { return ""; } public boolean acceptInlineAndMultipleAttachments() { return false; } public int getReviewScore(String contentId, String assignmentRef, String userId) throws QueueException, ReportException, Exception { ContentReviewItem item = null; try { Optional<ContentReviewItem> matchingItem = getItemByContentId(contentId); if (!matchingItem.isPresent()) { log.debug("Content " + contentId + " has not been queued previously"); } item = matchingItem.get(); if (item.getStatus() .compareTo(ContentReviewConstants.CONTENT_REVIEW_SUBMITTED_REPORT_AVAILABLE_CODE) != 0) { log.debug("Report not available: " + item.getStatus()); } } catch (Exception e) { log.error("(getReviewScore)" + e); } String[] assignData = null; try { assignData = getAssignData(contentId); } catch (Exception e) { log.error("(assignData)" + e); } String siteId = "", taskId = "", taskTitle = ""; Map<String, Object> data = new HashMap<String, Object>(); if (assignData != null) { siteId = assignData[0]; taskId = assignData[1]; taskTitle = assignData[2]; } else { siteId = item.getSiteId(); taskId = item.getTaskId(); taskTitle = getAssignmentTitle(taskId); data.put("assignment1", "assignment1"); } // Sync Grades if (turnitinConn.getUseGradeMark()) { try { data.put("siteId", siteId); data.put("taskId", taskId); data.put("taskTitle", taskTitle); syncGrades(data); } catch (Exception e) { log.error("Error syncing grades. " + e); } } return item.getReviewScore().intValue(); } /** * Check if grade sync has been run already for the specified site * * @param sess * Current Session * @param taskId * @return */ public boolean gradesChecked(Session sess, String taskId) { String sessSync = ""; try { sessSync = sess.getAttribute("sync").toString(); if (sessSync.equals(taskId)) { return true; } } catch (Exception e) { // log.error("(gradesChecked)"+e); } return false; } /** * Check if the specified user has the student role on the specified site. * * @param siteId * Site ID * @param userId * User ID * @return true if user has student role on the site. */ public boolean isUserStudent(String siteId, String userId) { boolean isStudent = false; try { Set<String> studentIds = siteService.getSite(siteId).getUsersIsAllowed("section.role.student"); List<User> activeUsers = userDirectoryService.getUsers(studentIds); for (int i = 0; i < activeUsers.size(); i++) { User user = activeUsers.get(i); if (userId.equals(user.getId())) { return true; } } } catch (Exception e) { log.info("(isStudentUser)" + e); } return isStudent; } /** * Return the Gradebook item associated with an assignment. * * @param data * Map containing Site/Assignment IDs * @return Associated gradebook item */ public Assignment getAssociatedGbItem(Map data) { Assignment assignment = null; String taskId = data.get("taskId").toString(); String siteId = data.get("siteId").toString(); String taskTitle = data.get("taskTitle").toString(); pushAdvisor(); try { List<Assignment> allGbItems = gradebookService.getAssignments(siteId); for (Assignment assign : allGbItems) { // Match based on External ID / Assignment title if (taskId.equals(assign.getExternalId()) || assign.getName().equals(taskTitle)) { assignment = assign; break; } } } catch (Exception e) { log.error("(allGbItems)" + e.toString()); } finally { popAdvisor(); } return assignment; } /** * Check Turnitin for grades and write them to the associated gradebook * * @param data * Map containing relevant IDs (site ID, Assignment ID, Title) */ public void syncGrades(Map<String, Object> data) { // Get session and check if gardes have already been synced Session sess = sessionManager.getCurrentSession(); boolean runOnce = gradesChecked(sess, data.get("taskId").toString()); boolean isStudent = isUserStudent(data.get("siteId").toString(), sess.getUserId()); if (turnitinConn.getUseGradeMark() && runOnce == false && isStudent == false) { log.info("Syncing Grades with Turnitin"); String siteId = data.get("siteId").toString(); String taskId = data.get("taskId").toString(); HashMap<String, Integer> reportTable = new HashMap<String, Integer>(); HashMap<String, String> additionalData = new HashMap<String, String>(); String tiiUserId = ""; String assign = taskId; if (data.containsKey("assignment1")) { // Assignments 1 uses the actual title whereas Assignments 2 // uses the ID assign = getAssignmentTitle(taskId); } // Run once sess.setAttribute("sync", taskId); // Get students enrolled on class in Turnitin Map<String, Object> enrollmentInfo = getAllEnrollmentInfo(siteId); // Get Associated GB item Assignment assignment = getAssociatedGbItem(data); // List submissions call Map params = new HashMap(); params = TurnitinAPIUtil.packMap(turnitinConn.getBaseTIIOptions(), "fid", "10", "fcmd", "2", "tem", getTEM(siteId), "assign", assign, "assignid", taskId, "cid", siteId, "ctl", siteId, "utp", "2"); params.putAll(getInstructorInfo(siteId)); Document document = null; try { document = turnitinConn.callTurnitinReturnDocument(params); } catch (TransientSubmissionException e) { log.error(e.getMessage()); } catch (SubmissionException e) { log.warn("SubmissionException error. " + e.getMessage()); } Element root = document.getDocumentElement(); if (((CharacterData) (root.getElementsByTagName("rcode").item(0).getFirstChild())).getData().trim() .compareTo("72") == 0) { NodeList objects = root.getElementsByTagName("object"); String grade = ""; log.debug(objects.getLength() + " objects in the returned list"); for (int i = 0; i < objects.getLength(); i++) { tiiUserId = ((CharacterData) (((Element) (objects.item(i))).getElementsByTagName("userid") .item(0).getFirstChild())).getData().trim(); additionalData.put("tiiUserId", tiiUserId); // Get GradeMark Grade try { grade = ((CharacterData) (((Element) (objects.item(i))).getElementsByTagName("score") .item(0).getFirstChild())).getData().trim(); reportTable.put("grade" + tiiUserId, Integer.valueOf(grade)); } catch (Exception e) { // No score returned grade = ""; } if (!grade.equals("")) { // Update Grade ---------------- if (gradebookService.isGradebookDefined(siteId)) { writeGrade(assignment, data, reportTable, additionalData, enrollmentInfo); } } } } else { log.debug("Report list request not successful"); log.debug(document.getTextContent()); } } } /** * Check if a grade returned from Turnitin is greater than the max points * for an assignment. If so then set to max points. (Grade is unchanged in * Turnitin) * * @param grade * Grade returned from Turnitin * @param assignment * @return */ public String processGrade(String grade, Assignment assignment) { String processedGrade = ""; try { int gradeVal = Integer.parseInt(grade); if (gradeVal > assignment.getPoints()) { processedGrade = Double.toString(assignment.getPoints()); log.info("Grade exceeds maximum point value for this assignment(" + assignment.getName() + ") Setting to Max Points value"); } else { processedGrade = grade; } } catch (NumberFormatException e) { log.warn("Error parsing grade"); } catch (Exception e) { log.warn("Error processing grade"); } return processedGrade; } /** * Write a grade to the gradebook for the current specified user * * @param assignment * @param data * @param reportTable * @param additionalData * @param enrollmentInfo * @return */ public boolean writeGrade(Assignment assignment, Map<String, Object> data, HashMap reportTable, HashMap additionalData, Map enrollmentInfo) { boolean success = false; String grade = null; String siteId = data.get("siteId").toString(); String currentStudentUserId = additionalData.get("tiiUserId").toString(); String tiiExternalId = ""; if (!enrollmentInfo.isEmpty()) { if (enrollmentInfo.containsKey(currentStudentUserId)) { tiiExternalId = enrollmentInfo.get(currentStudentUserId).toString(); log.info("tiiExternalId: " + tiiExternalId); } } else { return false; } // Check if the returned grade is greater than the maximum possible // grade // If so then set to the maximum grade grade = processGrade(reportTable.get("grade" + currentStudentUserId).toString(), assignment); pushAdvisor(); try { if (grade != null) { try { if (data.containsKey("assignment1")) { gradebookExternalAssessmentService.updateExternalAssessmentScore(siteId, assignment.getExternalId(), tiiExternalId, grade); } else { gradebookService.setAssignmentScoreString(siteId, data.get("taskTitle").toString(), tiiExternalId, grade, "SYNC"); } log.info("UPDATED GRADE (" + grade + ") FOR USER (" + tiiExternalId + ") IN ASSIGNMENT (" + assignment.getName() + ")"); success = true; } catch (GradebookNotFoundException e) { log.error("Error update grade GradebookNotFoundException " + e.toString()); } catch (Exception e) { log.error("Error update grade " + e.toString()); } } } catch (Exception e) { log.error("Error setting grade " + e.toString()); } finally { popAdvisor(); } return success; } /** * Get a list of students enrolled on a class in Turnitin * * @param siteId * Site ID * @return Map containing Students turnitin / Sakai ID */ public Map getAllEnrollmentInfo(String siteId) { Map params = new HashMap(); Map<String, String> enrollmentInfo = new HashMap(); String tiiExternalId = "";// the ID sakai stores String tiiInternalId = "";// Turnitin internal ID User user = null; Map instructorInfo = getInstructorInfo(siteId, true); try { user = userDirectoryService.getUser(instructorInfo.get("uid").toString()); } catch (UserNotDefinedException e) { log.error("(getAllEnrollmentInfo)User not defined. " + e); } params = TurnitinAPIUtil.packMap(turnitinConn.getBaseTIIOptions(), "fid", "19", "fcmd", "5", "tem", getTEM(siteId), "ctl", siteId, "cid", siteId, "utp", "2", "uid", user.getId(), "uem", getEmail(user), "ufn", user.getFirstName(), "uln", user.getLastName()); Document document = null; try { document = turnitinConn.callTurnitinReturnDocument(params); } catch (Exception e) { log.warn("Failed to get enrollment data using user: " + user.getDisplayName(), e); } Element root = document.getDocumentElement(); if (((CharacterData) (root.getElementsByTagName("rcode").item(0).getFirstChild())).getData().trim() .compareTo("93") == 0) { NodeList objects = root.getElementsByTagName("student"); for (int i = 0; i < objects.getLength(); i++) { tiiExternalId = ((CharacterData) (((Element) (objects.item(i))).getElementsByTagName("uid").item(0) .getFirstChild())).getData().trim(); tiiInternalId = ((CharacterData) (((Element) (objects.item(i))).getElementsByTagName("userid") .item(0).getFirstChild())).getData().trim(); enrollmentInfo.put(tiiInternalId, tiiExternalId); } } return enrollmentInfo; } public void pushAdvisor() { securityService.pushAdvisor(new SecurityAdvisor() { public SecurityAdvisor.SecurityAdvice isAllowed(String userId, String function, String reference) { return SecurityAdvisor.SecurityAdvice.ALLOWED; } }); } public void popAdvisor() { securityService.popAdvisor(); } /** * private methods */ private String encodeParam(String name, String value, String boundary) { return "--" + boundary + "\r\nContent-Disposition: form-data; name=\"" + name + "\"\r\n\r\n" + value + "\r\n"; } /** * This method was originally private, but is being made public for the * moment so we can run integration tests. TODO Revisit this decision. * * @param siteId * @throws SubmissionException * @throws TransientSubmissionException */ @SuppressWarnings("unchecked") public void createClass(String siteId) throws SubmissionException, TransientSubmissionException { log.debug("Creating class for site: " + siteId); String cpw = defaultClassPassword; String ctl = siteId; String fcmd = "2"; String fid = "2"; String utp = "2"; // user type 2 = instructor String cid = siteId; Document document = null; Map params = TurnitinAPIUtil.packMap(turnitinConn.getBaseTIIOptions(), "cid", cid, "cpw", cpw, "ctl", ctl, "fcmd", fcmd, "fid", fid, "utp", utp); params.putAll(getInstructorInfo(siteId)); document = turnitinConn.callTurnitinReturnDocument(params); Element root = document.getDocumentElement(); String rcode = ((CharacterData) (root.getElementsByTagName("rcode").item(0).getFirstChild())).getData() .trim(); if (((CharacterData) (root.getElementsByTagName("rcode").item(0).getFirstChild())).getData().trim() .compareTo("20") == 0 || ((CharacterData) (root.getElementsByTagName("rcode").item(0).getFirstChild())).getData().trim() .compareTo("21") == 0) { log.debug("Create Class successful"); } else { if ("218".equals(rcode) || "9999".equals(rcode)) { throw new TransientSubmissionException("Create Class not successful. Message: " + ((CharacterData) (root.getElementsByTagName("rmessage").item(0).getFirstChild())) .getData().trim() + ". Code: " + ((CharacterData) (root.getElementsByTagName("rcode").item(0).getFirstChild())).getData() .trim()); } else { throw new SubmissionException("Create Class not successful. Message: " + ((CharacterData) (root.getElementsByTagName("rmessage").item(0).getFirstChild())) .getData().trim() + ". Code: " + ((CharacterData) (root.getElementsByTagName("rcode").item(0).getFirstChild())).getData() .trim()); } } } /** * This returns the String that will be used as the Assignment Title in Turn * It In. * * The current implementation here has a few interesting caveats so that it * will work with both, the existing Assignments 1 integration, and the new * Assignments 2 integration under development. * * We will check and see if the taskId starts with /assignment/. If it does * we will look up the Assignment Entity on the legacy Entity bus. (not the * entitybroker). This needs some general work to be made generally modular * ( and useful for more than just Assignments 1 and 2 ). We will need to * look at some more concrete use cases and then factor it accordingly in * the future when the next scenerio is required. * * Another oddity is that to get rid of our hard dependency on Assignments 1 * we are invoking the getTitle method by hand. We probably need a mechanism * to register a title handler or something as part of the setup process for * new services that want to be reviewable. * * @param taskId * @return */ private String getAssignmentTitle(String taskId) { String togo = taskId; if (taskId.startsWith("/assignment/")) { try { Reference ref = entityManager.newReference(taskId); log.debug("got ref " + ref + " of type: " + ref.getType()); EntityProducer ep = ref.getEntityProducer(); Entity ent = ep.getEntity(ref); log.debug("got entity " + ent); String title = scrubSpecialCharacters(ent.getClass().getMethod("getTitle").invoke(ent).toString()); log.debug("Got reflected assignemment title from entity " + title); togo = URLDecoder.decode(title, "UTF-8"); } catch (Exception e) { e.printStackTrace(); } } // Turnitin requires Assignment titles to be at least two characters // long if (togo.length() == 1) { togo = togo + "_"; } return togo; } private String scrubSpecialCharacters(String title) { try { if (title.contains("&")) { title = title.replace('&', 'n'); } if (title.contains("%")) { title = title.replace("%", "percent"); } } catch (Exception e) { e.printStackTrace(); } return title; } /** * @param siteId * @param taskId * @throws SubmissionException * @throws TransientSubmissionException */ public void createAssignment(String siteId, String taskId) throws SubmissionException, TransientSubmissionException { createAssignment(siteId, taskId, null); } /** * Works by fetching the Instructor User info based on defaults or current * user. * * @param siteId * @param taskId * @return * @throws SubmissionException * @throws TransientSubmissionException */ @SuppressWarnings("unchecked") public Map getAssignment(String siteId, String taskId) throws SubmissionException, TransientSubmissionException { String taskTitle = getAssignmentTitle(taskId); Map params = TurnitinAPIUtil.packMap(turnitinConn.getBaseTIIOptions(), "assign", taskTitle, "assignid", taskId, "cid", siteId, "ctl", siteId, "fcmd", "7", "fid", "4", "utp", "2"); params.putAll(getInstructorInfo(siteId)); return turnitinConn.callTurnitinReturnMap(params); } public void addTurnitinInstructor(Map userparams) throws SubmissionException, TransientSubmissionException { Map params = new HashMap(); params.putAll(userparams); params.putAll(turnitinConn.getBaseTIIOptions()); params.put("fid", "1"); params.put("fcmd", "2"); params.put("utp", "2"); turnitinConn.callTurnitinReturnMap(params); } /** * Creates or Updates an Assignment * * This method will look at the current user or default instructor for it's * user information. * * * @param siteId * @param taskId * @param extraAsnnOpts * @throws SubmissionException * @throws TransientSubmissionException */ @SuppressWarnings("unchecked") public void createAssignment(String siteId, String taskId, Map extraAsnnOpts) throws SubmissionException, TransientSubmissionException { // get the assignment reference String taskTitle = ""; if (extraAsnnOpts.containsKey("title")) { taskTitle = extraAsnnOpts.get("title").toString(); } else { getAssignmentTitle(taskId); } log.debug("Creating assignment for site: " + siteId + ", task: " + taskId + " tasktitle: " + taskTitle); SimpleDateFormat dform = ((SimpleDateFormat) DateFormat.getDateInstance()); dform.applyPattern(TURNITIN_DATETIME_FORMAT); Calendar cal = Calendar.getInstance(); // set this to yesterday so we avoid timezone problems etc // TII-143 seems this now causes problems may need a finner tweak than 1 // day like midnight +1 min or something cal.set(Calendar.HOUR, 0); cal.set(Calendar.MINUTE, 1); // cal.add(Calendar.DAY_OF_MONTH, -1); String dtstart = dform.format(cal.getTime()); String today = dtstart; // set the due dates for the assignments to be in 5 month's time // turnitin automatically sets each class end date to 6 months after it // is created // the assignment end date must be on or before the class end date String fcmd = "2"; // new assignment boolean asnnExists = false; // If this assignment already exists, we should use fcmd 3 to update it. Map tiiresult = this.getAssignment(siteId, taskId); if (tiiresult.get("rcode") != null && tiiresult.get("rcode").equals("85")) { fcmd = "3"; asnnExists = true; } /* * Some notes about start and due dates. This information is accurate as * of Nov 12, 2009 and was determined by testing and experimentation * with some Sash scripts. * * A turnitin due date, must be after the start date. This makes sense * and follows the logic in both Assignments 1 and 2. * * When *creating* a new Turnitin Assignment, the start date must be * todays date or later. The format for dates only includes the day, and * not any specific times. I believe that, in order to make up for time * zone differences between your location and the turnitin cloud, it can * be basically the current day anywhere currently, with some slack. For * instance I can create an assignment for yesterday, but not for 2 days * ago. Doing so causes an error. * * However! For an existing turnitin assignment, you appear to have the * liberty of changing the start date to sometime in the past. You can * also change an assignment to have a due date in the past as long as * it is still after the start date. * * So, to avoid errors when syncing information, or adding turnitin * support to new or existing assignments we will: * * 1. If the assignment already exists we'll just save it. * * 2. If the assignment does not exist, we will save it once using * todays date for the start and due date, and then save it again with * the proper dates to ensure we're all tidied up and in line. * * Also, with our current class creation, due dates can be 5 years out, * but not further. This seems a bit lower priortity, but we still * should figure out an appropriate way to deal with it if it does * happen. * */ // TODO use the 'secret' function to change this to longer cal.add(Calendar.MONTH, 5); String dtdue = dform.format(cal.getTime()); log.debug("Set date due to: " + dtdue); if (extraAsnnOpts != null && extraAsnnOpts.containsKey("dtdue")) { dtdue = extraAsnnOpts.get("dtdue").toString(); log.debug("Settign date due from external to: " + dtdue); extraAsnnOpts.remove("dtdue"); } String fid = "4"; // function id String utp = "2"; // user type 2 = instructor String s_view_report = "1"; if (extraAsnnOpts != null && extraAsnnOpts.containsKey("s_view_report")) { s_view_report = extraAsnnOpts.get("s_view_report").toString(); extraAsnnOpts.remove("s_view_report"); } // erater String erater = (serverConfigurationService.getBoolean("turnitin.option.erater.default", false)) ? "1" : "0"; String ets_handbook = "1"; String ets_dictionary = "en"; String ets_spelling = "1"; String ets_style = "1"; String ets_grammar = "1"; String ets_mechanics = "1"; String ets_usage = "1"; try { if (extraAsnnOpts != null && extraAsnnOpts.containsKey("erater")) { erater = extraAsnnOpts.get("erater").toString(); extraAsnnOpts.remove("erater"); ets_handbook = extraAsnnOpts.get("ets_handbook").toString(); extraAsnnOpts.remove("ets_handbook"); ets_dictionary = extraAsnnOpts.get("ets_dictionary").toString(); extraAsnnOpts.remove("ets_dictionary"); ets_spelling = extraAsnnOpts.get("ets_spelling").toString(); extraAsnnOpts.remove("ets_spelling"); ets_style = extraAsnnOpts.get("ets_style").toString(); extraAsnnOpts.remove("ets_style"); ets_grammar = extraAsnnOpts.get("ets_grammar").toString(); extraAsnnOpts.remove("ets_grammar"); ets_mechanics = extraAsnnOpts.get("ets_mechanics").toString(); extraAsnnOpts.remove("ets_mechanics"); ets_usage = extraAsnnOpts.get("ets_usage").toString(); extraAsnnOpts.remove("ets_usage"); } } catch (Exception e) { log.info("(createAssignment)erater extraAsnnOpts. " + e); } String cid = siteId; String assignid = taskId; String ctl = siteId; Map params = TurnitinAPIUtil.packMap(turnitinConn.getBaseTIIOptions(), "assign", taskTitle, "assignid", assignid, "cid", cid, "ctl", ctl, "dtdue", dtdue, "dtstart", dtstart, "fcmd", "3", "fid", fid, "s_view_report", s_view_report, "utp", utp, "erater", erater, "ets_handbook", ets_handbook, "ets_dictionary", ets_dictionary, "ets_spelling", ets_spelling, "ets_style", ets_style, "ets_grammar", ets_grammar, "ets_mechanics", ets_mechanics, "ets_usage", ets_usage); // Save instructorInfo up here to reuse for calls in this // method, since theoretically getInstructorInfo could return // different instructors for different invocations and we need // the same one since we're using a session id. Map instructorInfo = getInstructorInfo(siteId); params.putAll(instructorInfo); if (extraAsnnOpts != null) { for (Object key : extraAsnnOpts.keySet()) { if (extraAsnnOpts.get(key) == null) { continue; } params = TurnitinAPIUtil.packMap(params, key.toString(), extraAsnnOpts.get(key).toString()); } } // We only need to use a session id if we are creating this // assignment for the first time. String sessionid = null; Map sessionParams = null; if (!asnnExists) { // Try adding the user in case they don't exist TII-XXX addTurnitinInstructor(instructorInfo); sessionParams = turnitinConn.getBaseTIIOptions(); sessionParams.putAll(instructorInfo); sessionParams.put("utp", utp); sessionid = TurnitinSessionFuncs.getTurnitinSession(turnitinConn, sessionParams); Map firstparams = new HashMap(); firstparams.putAll(params); firstparams.put("session-id", sessionid); firstparams.put("dtstart", today); // Make the due date in the future Calendar caldue = Calendar.getInstance(); caldue.add(Calendar.MONTH, 5); String dtdue_first = dform.format(caldue.getTime()); firstparams.put("dtdue", dtdue_first); log.debug("date due is: " + dtdue); log.debug("Start date: " + today); firstparams.put("fcmd", "2"); Document firstSaveDocument = turnitinConn.callTurnitinReturnDocument(firstparams); Element root = firstSaveDocument.getDocumentElement(); int rcode = new Integer( ((CharacterData) (root.getElementsByTagName("rcode").item(0).getFirstChild())).getData().trim()) .intValue(); if ((rcode > 0 && rcode < 100) || rcode == 419) { log.debug("Create FirstDate Assignment successful"); log.debug("tii returned " + ((CharacterData) (root.getElementsByTagName("rmessage").item(0).getFirstChild())) .getData().trim() + ". Code: " + rcode); } else { log.debug("FirstDate Assignment creation failed with message: " + ((CharacterData) (root.getElementsByTagName("rmessage").item(0).getFirstChild())) .getData().trim() + ". Code: " + rcode); // log.debug(root); throw new TransientSubmissionException("FirstDate Create Assignment not successful. Message: " + ((CharacterData) (root.getElementsByTagName("rmessage").item(0).getFirstChild())) .getData().trim() + ". Code: " + rcode, Integer.valueOf(rcode)); } } log.debug("going to attempt second update"); if (sessionid != null) { params.put("session-id", sessionid); } Document document = turnitinConn.callTurnitinReturnDocument(params); Element root = document.getDocumentElement(); int rcode = new Integer( ((CharacterData) (root.getElementsByTagName("rcode").item(0).getFirstChild())).getData().trim()) .intValue(); if ((rcode > 0 && rcode < 100) || rcode == 419) { log.debug("Create Assignment successful"); log.debug("tii returned " + ((CharacterData) (root.getElementsByTagName("rmessage").item(0).getFirstChild())).getData() .trim() + ". Code: " + rcode); } else { log.debug("Assignment creation failed with message: " + ((CharacterData) (root.getElementsByTagName("rmessage").item(0).getFirstChild())).getData() .trim() + ". Code: " + rcode); // log.debug(root); throw new TransientSubmissionException("Create Assignment not successful. Message: " + ((CharacterData) (root.getElementsByTagName("rmessage").item(0).getFirstChild())).getData() .trim() + ". Code: " + rcode, Integer.valueOf(rcode)); } if (sessionid != null) { TurnitinSessionFuncs.logoutTurnitinSession(turnitinConn, sessionid, sessionParams); } } /** * Currently public for integration tests. TODO Revisit visibility of * method. * * @param userId * @param uem * @param siteId * @throws SubmissionException */ public void enrollInClass(String userId, String uem, String siteId) throws SubmissionException, TransientSubmissionException { String uid = userId; String cid = siteId; String ctl = siteId; String fid = "3"; String fcmd = "2"; String tem = getTEM(cid); User user; try { user = userDirectoryService.getUser(userId); } catch (Exception t) { throw new SubmissionException("Cannot get user information", t); } log.debug("Enrolling user " + user.getEid() + "(" + userId + ") in class " + siteId); String ufn = getUserFirstName(user); if (ufn == null) { throw new SubmissionException("User has no first name"); } String uln = getUserLastName(user); if (uln == null) { throw new SubmissionException("User has no last name"); } String utp = "1"; Map params = new HashMap(); params = TurnitinAPIUtil.packMap(turnitinConn.getBaseTIIOptions(), "fid", fid, "fcmd", fcmd, "cid", cid, "tem", tem, "ctl", ctl, "dis", studentAccountNotified ? "0" : "1", "uem", uem, "ufn", ufn, "uln", uln, "utp", utp, "uid", uid); Document document = turnitinConn.callTurnitinReturnDocument(params); Element root = document.getDocumentElement(); String rMessage = ((CharacterData) (root.getElementsByTagName("rmessage").item(0).getFirstChild())) .getData(); String rCode = ((CharacterData) (root.getElementsByTagName("rcode").item(0).getFirstChild())).getData(); if ("31".equals(rCode)) { log.debug("Results from enrollInClass with user + " + userId + " and class title: " + ctl + ".\n" + "rCode: " + rCode + " rMessage: " + rMessage); } else { // certain return codes need to be logged log.warn("Results from enrollInClass with user + " + userId + " and class title: " + ctl + ". " + "rCode: " + rCode + ", rMessage: " + rMessage); // TODO for certain types we should probably throw an exception here // and stop the proccess } } /* * Get the next item that needs to be submitted * */ private Optional<ContentReviewItem> getNextItemInSubmissionQueue() { return crqs.getNextItemInQueueToSubmit(getProviderId()); } public void processQueue() { log.info("Processing submission queue"); int errors = 0; int success = 0; Optional<ContentReviewItem> nextItem = null; while ((nextItem = getNextItemInSubmissionQueue()).isPresent()) { ContentReviewItem item = nextItem.get(); log.debug("Attempting to submit content: " + item.getContentId() + " for user: " + item.getUserId() + " and site: " + item.getSiteId()); if (item.getRetryCount() == null) { item.setRetryCount(Long.valueOf(0)); item.setNextRetryTime(this.getNextRetryTime(0)); crqs.update(item); } else if (item.getRetryCount().intValue() > maxRetry) { item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_RETRY_EXCEEDED_CODE); crqs.update(item); errors++; continue; } else { long l = item.getRetryCount().longValue(); l++; item.setRetryCount(Long.valueOf(l)); item.setNextRetryTime(this.getNextRetryTime(Long.valueOf(l))); crqs.update(item); } User user; try { user = userDirectoryService.getUser(item.getUserId()); } catch (UserNotDefinedException e1) { log.error("Submission attempt unsuccessful - User not found.", e1); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_NO_RETRY_CODE); crqs.update(item); errors++; continue; } String uem = getEmail(user); if (uem == null) { log.error("User: " + user.getEid() + " has no valid email"); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_USER_DETAILS_CODE); item.setLastError("no valid email"); crqs.update(item); errors++; continue; } String ufn = getUserFirstName(user); if (ufn == null || ufn.equals("")) { log.error("Submission attempt unsuccessful - User has no first name"); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_USER_DETAILS_CODE); item.setLastError("has no first name"); crqs.update(item); errors++; continue; } String uln = getUserLastName(user); if (uln == null || uln.equals("")) { log.error("Submission attempt unsuccessful - User has no last name"); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_USER_DETAILS_CODE); item.setLastError("has no last name"); crqs.update(item); errors++; continue; } if (!turnitinConn.isUseSourceParameter()) { try { createClass(item.getSiteId()); } catch (SubmissionException t) { log.error("Submission attempt unsuccessful: Could not create class", t); item.setLastError("Class creation error: " + t.getMessage()); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_RETRY_CODE); crqs.update(item); errors++; continue; } catch (TransientSubmissionException tse) { item.setLastError("Class creation error: " + tse.getMessage()); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_RETRY_CODE); crqs.update(item); errors++; continue; } } try { enrollInClass(item.getUserId(), uem, item.getSiteId()); } catch (Exception t) { log.error("Submission attempt unsuccessful: Could not enroll user in class", t); if (t.getClass() == IOException.class) { item.setLastError("Enrolment error: " + t.getMessage()); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_RETRY_CODE); } else { item.setLastError("Enrolment error: " + t.getMessage()); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_RETRY_CODE); } crqs.update(item); errors++; continue; } if (!turnitinConn.isUseSourceParameter()) { try { Map tiiresult = this.getAssignment(item.getSiteId(), item.getTaskId()); if (tiiresult.get("rcode") != null && !tiiresult.get("rcode").equals("85")) { createAssignment(item.getSiteId(), item.getTaskId()); } } catch (SubmissionException se) { item.setLastError("Assign creation error: " + se.getMessage()); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_NO_RETRY_CODE); if (se.getErrorCode() != null) { item.setErrorCode(se.getErrorCode()); } crqs.update(item); errors++; continue; } catch (TransientSubmissionException tse) { item.setLastError("Assign creation error: " + tse.getMessage()); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_RETRY_CODE); if (tse.getErrorCode() != null) { item.setErrorCode(tse.getErrorCode()); } crqs.update(item); errors++; continue; } } // get all the info for the api call // we do this before connecting so that if there is a problem we can // jump out - saves time // these errors should probably be caught when a student is enrolled // in a class // but we check again here to be sure String fcmd = "2"; String fid = "5"; // to get the name of the initial submited file we need the title ContentResource resource = null; ResourceProperties resourceProperties = null; String fileName = null; try { try { resource = contentHostingService.getResource(item.getContentId()); } catch (IdUnusedException e4) { // ToDo we should probably remove these from the Queue log.warn("IdUnusedException: no resource with id " + item.getContentId()); crqs.delete(item); errors++; continue; } resourceProperties = resource.getProperties(); fileName = resourceProperties.getProperty(resourceProperties.getNamePropDisplayName()); fileName = escapeFileName(fileName, resource.getId()); } catch (PermissionException e2) { log.error("Submission failed due to permission error.", e2); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_NO_RETRY_CODE); item.setLastError("Permission exception: " + e2.getMessage()); crqs.update(item); errors++; continue; } catch (TypeException e) { log.error("Submission failed due to content Type error.", e); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_NO_RETRY_CODE); item.setLastError("Type Exception: " + e.getMessage()); crqs.update(item); errors++; continue; } // TII-97 filenames can't be longer than 200 chars if (fileName != null && fileName.length() >= 200) { fileName = truncateFileName(fileName, 198); } String userEid = item.getUserId(); try { userEid = userDirectoryService.getUserEid(item.getUserId()); } catch (UserNotDefinedException unde) { // nothing realy to do? } String ptl = userEid + ":" + fileName; String ptype = "2"; String uid = item.getUserId(); String cid = item.getSiteId(); String assignid = item.getTaskId(); // TODO ONC-1292 How to get this, and is it still required with // src=9? String tem = getTEM(cid); String utp = "1"; log.debug("Using Emails: tem: " + tem + " uem: " + uem); String assign = getAssignmentTitle(item.getTaskId()); String ctl = item.getSiteId(); Map params = TurnitinAPIUtil.packMap(turnitinConn.getBaseTIIOptions(), "assignid", assignid, "uid", uid, "cid", cid, "assign", assign, "ctl", ctl, "dis", Integer.valueOf(sendSubmissionNotification).toString(), "fcmd", fcmd, "fid", fid, "ptype", ptype, "ptl", ptl, "tem", tem, "uem", uem, "ufn", ufn, "uln", uln, "utp", utp, "resource_obj", resource); Document document = null; try { document = turnitinConn.callTurnitinReturnDocument(params, true); } catch (TransientSubmissionException e) { item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_RETRY_CODE); item.setLastError( "Error Submitting Assignment for Submission: " + e.getMessage() + ". Assume unsuccessful"); crqs.update(item); errors++; continue; } catch (SubmissionException e) { item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_RETRY_CODE); item.setLastError( "Error Submitting Assignment for Submission: " + e.getMessage() + ". Assume unsuccessful"); crqs.update(item); errors++; continue; } Element root = document.getDocumentElement(); String rMessage = ((CharacterData) (root.getElementsByTagName("rmessage").item(0).getFirstChild())) .getData(); String rCode = ((CharacterData) (root.getElementsByTagName("rcode").item(0).getFirstChild())).getData(); if (rCode == null) rCode = ""; else rCode = rCode.trim(); if (rMessage == null) rMessage = rCode; else rMessage = rMessage.trim(); if (rCode.compareTo("51") == 0) { String externalId = ((CharacterData) (root.getElementsByTagName("objectID").item(0) .getFirstChild())).getData().trim(); if (externalId != null && externalId.length() > 0) { log.debug("Submission successful"); item.setExternalId(externalId); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMITTED_AWAITING_REPORT_CODE); item.setRetryCount(Long.valueOf(0)); item.setLastError(null); item.setErrorCode(null); item.setDateSubmitted(new Date()); success++; crqs.update(item); } else { log.warn("invalid external id"); item.setLastError("Submission error: no external id received"); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_RETRY_CODE); errors++; crqs.update(item); } } else { log.debug("Submission not successful: " + ((CharacterData) (root.getElementsByTagName("rmessage").item(0).getFirstChild())) .getData().trim()); if (rMessage.equals("User password does not match user email") || "1001".equals(rCode) || "".equals(rMessage) || "413".equals(rCode) || "1025".equals(rCode) || "250".equals(rCode)) { item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_RETRY_CODE); log.warn("Submission not successful. It will be retried."); errors++; } else if (rCode.equals("423")) { item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_USER_DETAILS_CODE); errors++; } else if (rCode.equals("301")) { // this took a long time log.warn("Submission not successful due to timeout. It will be retried."); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_RETRY_CODE); Calendar cal = Calendar.getInstance(); cal.set(Calendar.HOUR_OF_DAY, 22); item.setNextRetryTime(cal.getTime()); errors++; } else { log.error("Submission not successful. It will NOT be retried."); item.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_NO_RETRY_CODE); errors++; } item.setLastError("Submission Error: " + rMessage + "(" + rCode + ")"); item.setErrorCode(Integer.valueOf(rCode)); crqs.update(item); } // release the lock so the reports job can handle it getNextItemInSubmissionQueue(); } log.info("Submission queue run completed: " + success + " items submitted, " + errors + " errors."); } public String escapeFileName(String fileName, String contentId) { log.debug("origional filename is: " + fileName); if (fileName == null) { // use the id fileName = contentId; } else if (fileName.length() > 199) { fileName = fileName.substring(0, 199); } log.debug("fileName is :" + fileName); try { fileName = URLDecoder.decode(fileName, "UTF-8"); // in rare cases it seems filenames can be double encoded while (fileName.indexOf("%20") > 0 || fileName.contains("%2520")) { try { fileName = URLDecoder.decode(fileName, "UTF-8"); } catch (IllegalArgumentException eae) { log.warn("Unable to decode fileName: " + fileName + ", using contentId: " + contentId); // as the result is likely to cause a MD5 exception use the // ID return contentId; /* * currentItem.setStatus(ContentReviewItem. * SUBMISSION_ERROR_NO_RETRY_CODE); * currentItem.setLastError("FileName decode exception: " + * fileName); dao.update(currentItem); * releaseLock(currentItem); errors++; throw new * SubmissionException("Can't decode fileName!"); */ } } } catch (IllegalArgumentException eae) { log.warn("Unable to decode fileName: " + fileName + ", using contentId: " + contentId); return contentId; } catch (UnsupportedEncodingException e) { log.warn(e.getMessage(), e); } fileName = fileName.replace(' ', '_'); // its possible we have double _ as a result of this lets do some // cleanup fileName = StringUtils.replace(fileName, "__", "_"); log.debug("fileName is :" + fileName); return fileName; } private String truncateFileName(String fileName, int i) { // get the extension for later re-use String extension = ""; if (fileName.contains(".")) { extension = fileName.substring(fileName.lastIndexOf(".")); } fileName = fileName.substring(0, i - extension.length()); fileName = fileName + extension; return fileName; } public void checkForReports() { checkForReportsBulk(); } /* * Fetch reports on a class by class basis */ @SuppressWarnings({ "deprecation", "unchecked" }) public void checkForReportsBulk() { SimpleDateFormat dform = ((SimpleDateFormat) DateFormat.getDateInstance()); dform.applyPattern(TURNITIN_DATETIME_FORMAT); log.info("Fetching reports from Turnitin"); // get the list of all items that are waiting for reports List<ContentReviewItem> awaitingReport = crqs.getAwaitingReports(getProviderId()); Iterator<ContentReviewItem> listIterator = awaitingReport.iterator(); HashMap<String, Integer> reportTable = new HashMap<String, Integer>(); log.debug("There are " + awaitingReport.size() + " submissions awaiting reports"); ContentReviewItem currentItem; while (listIterator.hasNext()) { currentItem = (ContentReviewItem) listIterator.next(); // has the item reached its next retry time? if (currentItem.getNextRetryTime() == null) currentItem.setNextRetryTime(new Date()); if (currentItem.getNextRetryTime().after(new Date())) { // we haven't reached the next retry time log.info("next retry time not yet reached for item: " + currentItem.getId()); crqs.update(currentItem); continue; } if (currentItem.getRetryCount() == null) { currentItem.setRetryCount(Long.valueOf(0)); currentItem.setNextRetryTime(this.getNextRetryTime(0)); } else if (currentItem.getRetryCount().intValue() > maxRetry) { currentItem.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_RETRY_EXCEEDED_CODE); crqs.update(currentItem); continue; } else { log.debug("Still have retries left, continuing. ItemID: " + currentItem.getId()); // Moving down to check for report generate speed. // long l = currentItem.getRetryCount().longValue(); // l++; // currentItem.setRetryCount(Long.valueOf(l)); // currentItem.setNextRetryTime(this.getNextRetryTime(Long.valueOf(l))); // dao.update(currentItem); } if (currentItem.getExternalId() == null || currentItem.getExternalId().equals("")) { currentItem.setStatus(Long.valueOf(4)); crqs.update(currentItem); continue; } if (!reportTable.containsKey(currentItem.getExternalId())) { // get the list from turnitin and see if the review is available log.debug("Attempting to update hashtable with reports for site " + currentItem.getSiteId()); String fcmd = "2"; String fid = "10"; try { User user = userDirectoryService.getUser(currentItem.getUserId()); } catch (Exception e) { log.error("Unable to look up user: " + currentItem.getUserId() + " for contentItem: " + currentItem.getId(), e); } String cid = currentItem.getSiteId(); String tem = getTEM(cid); String utp = "2"; String assignid = currentItem.getTaskId(); String assign = currentItem.getTaskId(); String ctl = currentItem.getSiteId(); // TODO FIXME Current sgithens // Move the update setRetryAttempts to here, and first call and // check the assignment from TII to see if the generate until // due is enabled. In that case we don't want to waste retry // attempts and should just continue. try { // TODO FIXME This is broken at the moment because we need // to have a userid, but this is assuming it's coming from // the thread, but we're in a quartz job. // Map curasnn = getAssignment(currentItem.getSiteId(), // currentItem.getTaskId()); // TODO FIXME Parameterize getAssignment method to take user // information Map getAsnnParams = TurnitinAPIUtil.packMap(turnitinConn.getBaseTIIOptions(), "assign", getAssignmentTitle(currentItem.getTaskId()), "assignid", currentItem.getTaskId(), "cid", currentItem.getSiteId(), "ctl", currentItem.getSiteId(), "fcmd", "7", "fid", "4", "utp", "2"); getAsnnParams.putAll(getInstructorInfo(currentItem.getSiteId())); Map curasnn = turnitinConn.callTurnitinReturnMap(getAsnnParams); if (curasnn.containsKey("object")) { Map curasnnobj = (Map) curasnn.get("object"); String reportGenSpeed = (String) curasnnobj.get("generate"); String duedate = (String) curasnnobj.get("dtdue"); SimpleDateFormat retform = ((SimpleDateFormat) DateFormat.getDateInstance()); retform.applyPattern(TURNITIN_DATETIME_FORMAT); Date duedateObj = null; try { if (duedate != null) { duedateObj = retform.parse(duedate); } } catch (ParseException pe) { log.warn("Unable to parse turnitin dtdue: " + duedate, pe); } if (reportGenSpeed != null && duedateObj != null && reportGenSpeed.equals("2") && duedateObj.after(new Date())) { log.info( "Report generate speed is 2, skipping for now. ItemID: " + currentItem.getId()); // If there was previously a transient error for // this item, reset the status if (ContentReviewConstants.CONTENT_REVIEW_REPORT_ERROR_RETRY_CODE .equals(currentItem.getStatus())) { currentItem.setStatus( ContentReviewConstants.CONTENT_REVIEW_SUBMITTED_AWAITING_REPORT_CODE); currentItem.setLastError(null); currentItem.setErrorCode(null); crqs.update(currentItem); } continue; } else { log.debug("Incrementing retry count for currentItem: " + currentItem.getId()); long l = currentItem.getRetryCount().longValue(); l++; currentItem.setRetryCount(Long.valueOf(l)); currentItem.setNextRetryTime(this.getNextRetryTime(Long.valueOf(l))); crqs.update(currentItem); } } } catch (SubmissionException e) { log.error("Unable to check the report gen speed of the asnn for item: " + currentItem.getId(), e); } catch (TransientSubmissionException e) { log.error("Unable to check the report gen speed of the asnn for item: " + currentItem.getId(), e); } Map params = new HashMap(); // try { params = TurnitinAPIUtil.packMap(turnitinConn.getBaseTIIOptions(), "fid", fid, "fcmd", fcmd, "tem", tem, "assign", assign, "assignid", assignid, "cid", cid, "ctl", ctl, "utp", utp); params.putAll(getInstructorInfo(currentItem.getSiteId())); Document document = null; try { document = turnitinConn.callTurnitinReturnDocument(params); } catch (TransientSubmissionException e) { log.warn("Update failed due to TransientSubmissionException error: " + e.toString(), e); currentItem.setStatus(ContentReviewConstants.CONTENT_REVIEW_REPORT_ERROR_RETRY_CODE); currentItem.setLastError(e.getMessage()); crqs.update(currentItem); break; } catch (SubmissionException e) { log.warn("Update failed due to SubmissionException error: " + e.toString(), e); currentItem.setStatus(ContentReviewConstants.CONTENT_REVIEW_REPORT_ERROR_RETRY_CODE); currentItem.setLastError(e.getMessage()); crqs.update(currentItem); break; } Element root = document.getDocumentElement(); if (((CharacterData) (root.getElementsByTagName("rcode").item(0).getFirstChild())).getData().trim() .compareTo("72") == 0) { log.debug("Report list returned successfully"); NodeList objects = root.getElementsByTagName("object"); String objectId; String similarityScore; String overlap = ""; log.debug(objects.getLength() + " objects in the returned list"); for (int i = 0; i < objects.getLength(); i++) { similarityScore = ((CharacterData) (((Element) (objects.item(i))) .getElementsByTagName("similarityScore").item(0).getFirstChild())).getData().trim(); objectId = ((CharacterData) (((Element) (objects.item(i))).getElementsByTagName("objectID") .item(0).getFirstChild())).getData().trim(); if (similarityScore.compareTo("-1") != 0) { overlap = ((CharacterData) (((Element) (objects.item(i))) .getElementsByTagName("overlap").item(0).getFirstChild())).getData().trim(); reportTable.put(objectId, Integer.valueOf(overlap)); } else { reportTable.put(objectId, Integer.valueOf(-1)); } log.debug("objectId: " + objectId + " similarity: " + similarityScore + " overlap: " + overlap); } } else { log.debug("Report list request not successful"); log.debug(document.getTextContent()); } } int reportVal; // check if the report value is now there (there may have been a // failure to get the list above) if (reportTable.containsKey(currentItem.getExternalId())) { reportVal = ((Integer) (reportTable.get(currentItem.getExternalId()))).intValue(); log.debug("reportVal for " + currentItem.getExternalId() + ": " + reportVal); if (reportVal != -1) { currentItem.setReviewScore(reportVal); currentItem.setStatus(ContentReviewConstants.CONTENT_REVIEW_SUBMITTED_REPORT_AVAILABLE_CODE); currentItem.setDateReportReceived(new Date()); crqs.update(currentItem); log.debug("new report received: " + currentItem.getExternalId() + " -> " + currentItem.getReviewScore()); } } } log.info("Finished fetching reports from Turnitin"); } // returns null if no valid email exists public String getEmail(User user) { String uem = null; // Check account email address String account_email = null; if (isValidEmail(user.getEmail())) { account_email = user.getEmail().trim(); } // Lookup system profile email address if necessary String profile_email = null; if (account_email == null || preferSystemProfileEmail) { SakaiPerson sp = sakaiPersonManager.getSakaiPerson(user.getId(), sakaiPersonManager.getSystemMutableType()); if (sp != null && isValidEmail(sp.getMail())) { profile_email = sp.getMail().trim(); } } // Check guest accounts and use eid as the email if preferred if (this.preferGuestEidEmail && isValidEmail(user.getEid())) { uem = user.getEid(); } if (uem == null && preferSystemProfileEmail && profile_email != null) { uem = profile_email; } if (uem == null && account_email != null) { uem = account_email; } // Randomize the email address if preferred if (spoilEmailAddresses && uem != null) { // Scramble it String[] parts = uem.split("@"); String emailName = parts[0]; Random random = new Random(); int int1 = random.nextInt(); int int2 = random.nextInt(); int int3 = random.nextInt(); emailName += (int1 + int2 + int3); uem = emailName + "@" + parts[1]; if (log.isDebugEnabled()) log.debug("SCRAMBLED EMAIL:" + uem); } log.debug("Using email " + uem + " for user eid " + user.getEid() + " id " + user.getId()); return uem; } /** * Is this a valid email the service will recognize * * @param email * @return */ private boolean isValidEmail(String email) { // TODO: Use a generic Sakai utility class (when a suitable one exists) if (email == null || email.equals("")) return false; email = email.trim(); // must contain @ if (email.indexOf("@") == -1) return false; // an email can't contain spaces if (email.indexOf(" ") > 0) return false; // use commons-validator EmailValidator validator = EmailValidator.getInstance(); if (validator.isValid(email)) return true; return false; } // Methods for updating all assignments that exist public void doAssignments() { log.info("About to update all turnitin assignments"); List<ContentReviewItem> items = crqs.getAllContentReviewItemsGroupedBySiteAndTask(getProviderId()); for (ContentReviewItem item : items) { try { updateAssignment(item.getSiteId(), item.getTaskId()); } catch (SubmissionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * Update Assignment. This method is not currently called by Assignments 1. */ public void updateAssignment(String siteId, String taskId) throws SubmissionException { log.info("updateAssignment(" + siteId + " , " + taskId + ")"); // get the assignment reference String taskTitle = getAssignmentTitle(taskId); log.debug("Creating assignment for site: " + siteId + ", task: " + taskId + " tasktitle: " + taskTitle); SimpleDateFormat dform = ((SimpleDateFormat) DateFormat.getDateInstance()); dform.applyPattern(TURNITIN_DATETIME_FORMAT); Calendar cal = Calendar.getInstance(); // set this to yesterday so we avoid timezpne problems etc cal.add(Calendar.DAY_OF_MONTH, -1); String dtstart = dform.format(cal.getTime()); // set the due dates for the assignments to be in 5 month's time // turnitin automatically sets each class end date to 6 months after it // is created // the assignment end date must be on or before the class end date // TODO use the 'secret' function to change this to longer cal.add(Calendar.MONTH, 5); String dtdue = dform.format(cal.getTime()); String fcmd = "3"; // new assignment String fid = "4"; // function id String utp = "2"; // user type 2 = instructor String s_view_report = "1"; // erater String erater = "0"; String ets_handbook = "1"; String ets_dictionary = "en"; String ets_spelling = "1"; String ets_style = "1"; String ets_grammar = "1"; String ets_mechanics = "1"; String ets_usage = "1"; String cid = siteId; String assignid = taskId; String assign = taskTitle; String ctl = siteId; String assignEnc = assign; try { if (assign.contains("&")) { // log.debug("replacing & in assingment title"); assign = assign.replace('&', 'n'); } assignEnc = assign; log.debug("Assign title is " + assignEnc); } catch (Exception e) { e.printStackTrace(); } Map params = TurnitinAPIUtil.packMap(turnitinConn.getBaseTIIOptions(), "assign", assignEnc, "assignid", assignid, "cid", cid, "ctl", ctl, "dtdue", dtdue, "dtstart", dtstart, "fcmd", fcmd, "fid", fid, "s_view_report", s_view_report, "utp", utp, "erater", erater, "ets_handbook", ets_handbook, "ets_dictionary", ets_dictionary, "ets_spelling", ets_spelling, "ets_style", ets_style, "ets_grammar", ets_grammar, "ets_mechanics", ets_mechanics, "ets_usage", ets_usage); params.putAll(getInstructorInfo(siteId)); Document document = null; try { document = turnitinConn.callTurnitinReturnDocument(params); } catch (TransientSubmissionException tse) { log.error("Error on API call in updateAssignment siteid: " + siteId + " taskid: " + taskId, tse); return; } catch (SubmissionException se) { log.error("Error on API call in updateAssignment siteid: " + siteId + " taskid: " + taskId, se); return; } Element root = document.getDocumentElement(); int rcode = new Integer( ((CharacterData) (root.getElementsByTagName("rcode").item(0).getFirstChild())).getData().trim()) .intValue(); if ((rcode > 0 && rcode < 100) || rcode == 419) { log.debug("Create Assignment successful"); } else { log.debug("Assignment creation failed with message: " + ((CharacterData) (root.getElementsByTagName("rmessage").item(0).getFirstChild())).getData() .trim() + ". Code: " + rcode); throw new SubmissionException("Create Assignment not successful. Message: " + ((CharacterData) (root.getElementsByTagName("rmessage").item(0).getFirstChild())).getData() .trim() + ". Code: " + rcode); } } /* * (non-Javadoc) * * @see org.sakaiproject.contentreview.service.ContentReviewService# * isAcceptableContent(org.sakaiproject.content.api.ContentResource) */ public boolean isAcceptableContent(ContentResource resource) { return turnitinContentValidator.isAcceptableContent(resource); } /** * find the next time this item should be tried * * @param retryCount * @return */ private Date getNextRetryTime(long retryCount) { int offset = 5; if (retryCount > 9 && retryCount < 20) { offset = 10; } else if (retryCount > 19 && retryCount < 30) { offset = 20; } else if (retryCount > 29 && retryCount < 40) { offset = 40; } else if (retryCount > 39 && retryCount < 50) { offset = 80; } else if (retryCount > 49 && retryCount < 60) { offset = 160; } else if (retryCount > 59) { offset = 220; } Calendar cal = Calendar.getInstance(); cal.add(Calendar.MINUTE, offset); return cal.getTime(); } /** * Gets a first name for a user or generates an initial from the eid * * @param user * a sakai user * @return the first name or at least an initial if possible, "X" if no fn * can be made */ private String getUserFirstName(User user) { String ufn = user.getFirstName().trim(); if (ufn == null || ufn.equals("")) { boolean genFN = (boolean) serverConfigurationService.getBoolean("turnitin.generate.first.name", true); if (genFN) { String eid = user.getEid(); if (eid != null && eid.length() > 0) { ufn = eid.substring(0, 1); } else { ufn = "X"; } } } return ufn; } /** * Get user last Name. If turnitin.generate.last.name is set to true last * name is anonamised * * @param user * @return */ private String getUserLastName(User user) { String uln = user.getLastName().trim(); if (uln == null || uln.equals("")) { boolean genLN = serverConfigurationService.getBoolean("turnitin.generate.last.name", false); if (genLN) { String eid = user.getEid(); if (eid != null && eid.length() > 0) { uln = eid.substring(0, 1); } else { uln = "X"; } } } return uln; } public String getLocalizedStatusMessage(String messageCode, String userRef) { String userId = EntityReference.getIdFromRef(userRef); ResourceLoader resourceLoader = new ResourceLoader(userId, "turnitin"); return resourceLoader.getString(messageCode); } public String getReviewError(String contentId) { return getLocalizedReviewErrorMessage(contentId); } public String getLocalizedStatusMessage(String messageCode) { return getLocalizedStatusMessage(messageCode, userDirectoryService.getCurrentUser().getReference()); } public String getLocalizedStatusMessage(String messageCode, Locale locale) { // TODO not sure how to do this with the sakai resource loader return null; } public String getLocalizedReviewErrorMessage(String contentId) { log.debug("Returning review error for content: " + contentId); Optional<ContentReviewItem> item = crqs.getQueuedItem(getProviderId(), contentId); if (item.isPresent()) { // its possible the error code column is not populated Integer errorCode = item.get().getErrorCode(); if (errorCode != null) { return getLocalizedStatusMessage(errorCode.toString()); } return item.get().getLastError(); } log.debug("Content " + contentId + " has not been queued previously"); return null; } private String getTEM(String cid) { if (turnitinConn.isUseSourceParameter()) { return getInstructorInfo(cid).get("uem").toString(); } else { return turnitinConn.getDefaultInstructorEmail(); } } /** * This will return a map of the information for the instructor such as uem, * username, ufn, etc. If the system is configured to use src9 provisioning, * this will draw information from the current thread based user. Otherwise * it will use the default Instructor information that has been configured * for the system. * * @return */ @SuppressWarnings("unchecked") public Map getInstructorInfo(String siteId) { log.debug("Getting instructor info for site " + siteId); Map togo = new HashMap(); if (!turnitinConn.isUseSourceParameter()) { togo.put("uem", turnitinConn.getDefaultInstructorEmail()); togo.put("ufn", turnitinConn.getDefaultInstructorFName()); togo.put("uln", turnitinConn.getDefaultInstructorLName()); togo.put("uid", turnitinConn.getDefaultInstructorId()); } else { String INST_ROLE = "section.role.instructor"; User inst = null; try { Site site = siteService.getSite(siteId); User user = userDirectoryService.getCurrentUser(); log.debug("Current user: " + user.getId()); if (site.isAllowed(user.getId(), INST_ROLE)) { inst = user; } else { Set<String> instIds = getActiveInstructorIds(INST_ROLE, site); if (instIds.size() > 0) { inst = userDirectoryService.getUser((String) instIds.toArray()[0]); } } } catch (IdUnusedException e) { log.error("Unable to fetch site in getAbsoluteInstructorInfo: " + siteId, e); } catch (UserNotDefinedException e) { log.error("Unable to fetch user in getAbsoluteInstructorInfo", e); } if (inst == null) { log.error("Instructor is null in getAbsoluteInstructorInfo"); } else { togo.put("uem", getEmail(inst)); togo.put("ufn", inst.getFirstName()); togo.put("uln", inst.getLastName()); togo.put("uid", inst.getId()); togo.put("username", inst.getDisplayName()); } } return togo; } @SuppressWarnings("unchecked") public Map getInstructorInfo(String siteId, boolean ignoreUseSource) { Map togo = new HashMap(); if (!turnitinConn.isUseSourceParameter() && ignoreUseSource == false) { togo.put("uem", turnitinConn.getDefaultInstructorEmail()); togo.put("ufn", turnitinConn.getDefaultInstructorFName()); togo.put("uln", turnitinConn.getDefaultInstructorLName()); togo.put("uid", turnitinConn.getDefaultInstructorId()); } else { String INST_ROLE = "section.role.instructor"; User inst = null; try { Site site = siteService.getSite(siteId); User user = userDirectoryService.getCurrentUser(); if (site.isAllowed(user.getId(), INST_ROLE)) { inst = user; } else { Set<String> instIds = getActiveInstructorIds(INST_ROLE, site); if (instIds.size() > 0) { inst = userDirectoryService.getUser((String) instIds.toArray()[0]); } } } catch (IdUnusedException e) { log.error("Unable to fetch site in getAbsoluteInstructorInfo: " + siteId, e); } catch (UserNotDefinedException e) { log.error("Unable to fetch user in getAbsoluteInstructorInfo", e); } if (inst == null) { log.error("Instructor is null in getAbsoluteInstructorInfo"); } else { togo.put("uem", getEmail(inst)); togo.put("ufn", inst.getFirstName()); togo.put("uln", inst.getLastName()); togo.put("uid", inst.getId()); togo.put("username", inst.getDisplayName()); } } return togo; } private Set<String> getActiveInstructorIds(String INST_ROLE, Site site) { log.debug("Getting active instructor IDs for permission " + INST_ROLE + " in site " + site.getId()); Set<String> instIds = site.getUsersIsAllowed(INST_ROLE); // the site could contain references to deleted users List<User> activeUsers = userDirectoryService.getUsers(instIds); Set<String> ret = new HashSet<String>(); for (int i = 0; i < activeUsers.size(); i++) { User user = activeUsers.get(i); // Ignore users who do not have a first and/or last name set or do // not have // a valid email address, as this will cause a TII API call to fail if (user.getFirstName() != null && !user.getFirstName().trim().isEmpty() && user.getLastName() != null && !user.getLastName().trim().isEmpty() && getEmail(user) != null) { ret.add(user.getId()); } } return ret; } @Override public boolean allowAllContent() { return false; } @Override public Map<String, SortedSet<String>> getAcceptableExtensionsToMimeTypes() { return new HashMap<String, SortedSet<String>>(); } @Override public Map<String, SortedSet<String>> getAcceptableFileTypesToExtensions() { return new HashMap<String, SortedSet<String>>(); } @Override public void queueContent(String userId, String siteId, String taskId, List<ContentResource> content) throws QueueException { log.debug("Method called queueContent()"); if (content == null || content.isEmpty()) { return; } if (userId == null) { log.debug("Using current user"); userId = userDirectoryService.getCurrentUser().getId(); } if (siteId == null) { log.debug("Using current site"); siteId = toolManager.getCurrentPlacement().getContext(); } if (taskId == null) { log.debug("Generating default taskId"); taskId = siteId + " " + "defaultAssignment"; } log.debug("Adding content from site " + siteId + " and user: " + userId + " for task: " + taskId + " to submission queue"); crqs.queueContent(getProviderId(), userId, siteId, taskId, content); } @Override public Long getReviewStatus(String contentId) throws QueueException { return crqs.getReviewStatus(getProviderId(), contentId); } @Override public Date getDateQueued(String contextId) throws QueueException { return crqs.getDateQueued(getProviderId(), contextId); } @Override public Date getDateSubmitted(String contextId) throws QueueException, SubmissionException { return crqs.getDateSubmitted(getProviderId(), contextId); } @Override public List<ContentReviewItem> getReportList(String siteId, String taskId) throws QueueException, SubmissionException, ReportException { return crqs.getContentReviewItems(getProviderId(), siteId, taskId); } @Override public List<ContentReviewItem> getReportList(String siteId) throws QueueException, SubmissionException, ReportException { return getReportList(siteId, null); } @Override public List<ContentReviewItem> getAllContentReviewItems(String siteId, String taskId) throws QueueException, SubmissionException, ReportException { return crqs.getContentReviewItems(getProviderId(), siteId, taskId); } @Override public void resetUserDetailsLockedItems(String userId) { crqs.resetUserDetailsLockedItems(getProviderId(), userId); } @Override public boolean allowResubmission() { return true; } @Override public void removeFromQueue(String contentId) { crqs.removeFromQueue(getProviderId(), contentId); } }