Java tutorial
/** * * Copyright (c) 2006 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://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.contentreview.impl.urkund; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.validator.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.dao.impl.ContentReviewDao; 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.model.ContentReviewItem; import org.sakaiproject.contentreview.model.ContentReviewItemUrkund; import org.sakaiproject.contentreview.urkund.client.SubmissionsResponse; import org.sakaiproject.contentreview.urkund.client.UrkundClient; import org.sakaiproject.contentreview.urkund.client.UrkundException; import org.sakaiproject.contentreview.urkund.client.UrkundSubmission; import org.sakaiproject.db.api.SqlService; import org.sakaiproject.entity.api.EntityManager; 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.ServerOverloadException; import org.sakaiproject.exception.TypeException; import org.sakaiproject.genericdao.api.search.Restriction; import org.sakaiproject.genericdao.api.search.Search; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.SiteService; import org.sakaiproject.tool.api.Session; 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; public class UrkundReviewServiceImpl extends BaseReviewServiceImpl { private static final Log log = LogFactory.getLog(UrkundReviewServiceImpl.class); public static final String URKUND_DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; private static final String SERVICE_NAME = "Urkund"; // Site property to enable or disable use of Urkund for the site private static final String URKUND_SITE_PROPERTY = "urkund"; // 0 is unique user ID (must include friendly email address characters only) // 1 is unique site ID (must include friendly email address characters only) // 2 is integration context string (must be 2 to 10 characters) private static final String URKUND_SPOOFED_EMAIL_TEMPLATE = "%s_%s.%s@submitters.urkund.com"; final static long LOCK_PERIOD = 12000000; private Long maxRetry = null; private List<String> enabledSiteTypes; private UrkundClient urkundClient; private String spoofEmailContext; /** * Setters */ private ServerConfigurationService serverConfigurationService; public void setServerConfigurationService(ServerConfigurationService serverConfigurationService) { this.serverConfigurationService = serverConfigurationService; } public void setUrkundClient(UrkundClient urkundClient) { this.urkundClient = urkundClient; } private EntityManager entityManager; public void setEntityManager(EntityManager en) { this.entityManager = en; } private ContentHostingService contentHostingService; public void setContentHostingService(ContentHostingService contentHostingService) { this.contentHostingService = contentHostingService; } private SakaiPersonManager sakaiPersonManager; public void setSakaiPersonManager(SakaiPersonManager s) { this.sakaiPersonManager = s; } private ContentReviewDao dao; public void setDao(ContentReviewDao dao) { super.setDao(dao); this.dao = dao; } private UserDirectoryService userDirectoryService; public void setUserDirectoryService(UserDirectoryService userDirectoryService) { super.setUserDirectoryService(userDirectoryService); this.userDirectoryService = userDirectoryService; } private SiteService siteService; public void setSiteService(SiteService siteService) { this.siteService = siteService; } private SqlService sqlService; public void setSqlService(SqlService sql) { sqlService = sql; } private PreferencesService preferencesService; public void setPreferencesService(PreferencesService preferencesService) { this.preferencesService = preferencesService; } private UrkundContentValidator urkundContentValidator; public void setUrkundContentValidator(UrkundContentValidator urkundContentValidator) { this.urkundContentValidator = urkundContentValidator; } private SecurityService securityService; public void setSecurityService(SecurityService securityService) { this.securityService = securityService; } /** * Place any code that should run when this class is initialized by spring here */ public void init() { maxRetry = Long.valueOf(serverConfigurationService.getInt("urkund.maxRetry", 10)); enabledSiteTypes = Arrays .asList(ArrayUtils.nullToEmpty(serverConfigurationService.getStrings("urkund.sitetypes"))); if (enabledSiteTypes != null && !enabledSiteTypes.isEmpty()) { log.info("Urkund is enabled for site types: " + StringUtils.join(enabledSiteTypes, ",")); } spoofEmailContext = serverConfigurationService.getString("urkund.spoofemailcontext", null); } @Override public String getServiceName() { return SERVICE_NAME; } /** * Allow Urkund for this site? */ public boolean isSiteAcceptable(Site s) { if (s == null) { return false; } log.debug("isSiteAcceptable: " + s.getId() + " / " + s.getTitle()); // Check site property ResourceProperties properties = s.getProperties(); String prop = (String) properties.get(URKUND_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; } @Override public String getIconUrlforScore(Long score, Long warnings) { String urlBase = "/sakai-contentreview-tool-urkund/images/"; String suffix = ".gif"; if (warnings > 0) { return urlBase + "w" + suffix; } if (score.equals((long) 0)) { return urlBase + "0" + suffix; } else if (score.compareTo(Long.valueOf(12)) < 0) { return urlBase + "1" + suffix; } else if (score.compareTo(Long.valueOf(25)) < 0) { return urlBase + "2" + suffix; } else if (score.compareTo(Long.valueOf(38)) < 0) { return urlBase + "3" + suffix; } else if (score.compareTo(Long.valueOf(50)) < 0) { return urlBase + "4" + suffix; } else if (score.compareTo(Long.valueOf(62)) < 0) { return urlBase + "5" + suffix; } else if (score.compareTo(Long.valueOf(75)) < 0) { return urlBase + "6" + suffix; } else if (score.compareTo(Long.valueOf(88)) < 0) { return urlBase + "7" + suffix; } else { return urlBase + "8" + suffix; } } /** * This uses the default Instructor information or current user. * * @see org.sakaiproject.contentreview.impl.hbm.BaseReviewServiceImpl#getReviewReportInstructor(java.lang.String) */ public String getReviewReportInstructor(String contentId) throws QueueException, ReportException { Search search = new Search(); search.addRestriction(new Restriction("contentId", contentId)); List<ContentReviewItemUrkund> matchingItems = dao.findBySearch(ContentReviewItemUrkund.class, search); if (matchingItems.isEmpty()) { log.debug("Content " + contentId + " has not been queued previously"); throw new QueueException("Content " + contentId + " has not been queued previously"); } if (matchingItems.size() > 1) { log.warn("More than one matching item found - using first item found"); } // check that the report is available ContentReviewItemUrkund item = (ContentReviewItemUrkund) matchingItems.iterator().next(); if (item.getStatus().compareTo(ContentReviewItem.SUBMITTED_REPORT_AVAILABLE_CODE) != 0) { log.debug("Report not available: " + item.getStatus()); throw new ReportException("Report not available: " + item.getStatus()); } return item.getReportUrl(); } public String getReviewReportStudent(String contentId) throws QueueException, ReportException { Search search = new Search(); search.addRestriction(new Restriction("contentId", contentId)); List<ContentReviewItemUrkund> matchingItems = dao.findBySearch(ContentReviewItemUrkund.class, search); if (matchingItems.size() == 0) { log.debug("Content " + contentId + " has not been queued previously"); throw new QueueException("Content " + contentId + " has not been queued previously"); } if (matchingItems.size() > 1) { log.debug("More than one matching item found - using first item found"); } // check that the report is available ContentReviewItemUrkund item = (ContentReviewItemUrkund) matchingItems.iterator().next(); if (item.getStatus().compareTo(ContentReviewItem.SUBMITTED_REPORT_AVAILABLE_CODE) != 0) { log.debug("Report not available: " + item.getStatus()); throw new ReportException("Report not available: " + item.getStatus()); } return item.getReportUrl(); } @Override public void queueContent(String userId, String siteId, String taskId, String contentId) throws QueueException { log.debug("Method called queueContent(" + userId + "," + siteId + "," + contentId + ")"); 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 + " " + defaultAssignmentName; } log.debug("Adding content: " + contentId + " from site " + siteId + " and user: " + userId + " for task: " + taskId + " to submission queue"); /* * first check that this content has not been submitted before this may * not be the best way to do this - perhaps use contentId as the primary * key for now id is the primary key and so the database won't complain * if we put in repeats necessitating the check */ List<ContentReviewItemUrkund> existingItems = getItemsByContentId(contentId); if (existingItems.size() > 0) { throw new QueueException("Content " + contentId + " is already queued, not re-queued"); } ContentReviewItemUrkund item = new ContentReviewItemUrkund(userId, siteId, taskId, contentId, new Date(), ContentReviewItem.NOT_SUBMITTED_CODE, null, null, null); item.setNextRetryTime(new Date()); dao.save(item); } public int getReviewScore(String contentId) throws QueueException, ReportException, Exception { log.debug("Getting review score for content: " + contentId); List<ContentReviewItemUrkund> matchingItems = getItemsByContentId(contentId); if (matchingItems.size() == 0) { log.debug("Content " + contentId + " has not been queued previously"); throw new QueueException("Content " + contentId + " has not been queued previously"); } if (matchingItems.size() > 1) { log.debug("More than one matching item - using first item found"); } ContentReviewItem item = (ContentReviewItem) matchingItems.iterator().next(); if (item.getStatus().compareTo(ContentReviewItem.SUBMITTED_REPORT_AVAILABLE_CODE) != 0) { log.debug("Report not available: " + item.getStatus()); throw new ReportException("Report not available: " + item.getStatus()); } return item.getReviewScore().intValue(); } public Long getReviewStatus(String contentId) throws QueueException { log.debug("Returning review status for content: " + contentId); List<ContentReviewItemUrkund> matchingItems = getItemsByContentId(contentId); if (matchingItems.size() == 0) { log.debug("Content " + contentId + " has not been queued previously"); throw new QueueException("Content " + contentId + " has not been queued previously"); } if (matchingItems.size() > 1) { log.debug("more than one matching item found - using first item found"); } return ((ContentReviewItem) matchingItems.iterator().next()).getStatus(); } public Date getDateQueued(String contentId) throws QueueException { log.debug("Returning date queued for content: " + contentId); List<ContentReviewItemUrkund> matchingItems = getItemsByContentId(contentId); if (matchingItems.size() == 0) { log.debug("Content " + contentId + " has not been queued previously"); throw new QueueException("Content " + contentId + " has not been queued previously"); } if (matchingItems.size() > 1) { log.debug("more than one matching item found - using first item found"); } return ((ContentReviewItem) matchingItems.iterator().next()).getDateQueued(); } public Date getDateSubmitted(String contentId) throws QueueException, SubmissionException { log.debug("Returning date queued for content: " + contentId); List<ContentReviewItemUrkund> matchingItems = getItemsByContentId(contentId); if (matchingItems.size() == 0) { log.debug("Content " + contentId + " has not been queued previously"); throw new QueueException("Content " + contentId + " has not been queued previously"); } if (matchingItems.size() > 1) { log.debug("more than one matching item found - using first item found"); } ContentReviewItem item = (ContentReviewItem) matchingItems.iterator().next(); if (item.getDateSubmitted() == null) { log.debug("Content not yet submitted: " + item.getStatus()); throw new SubmissionException("Content not yet submitted: " + item.getStatus()); } return item.getDateSubmitted(); } public List<ContentReviewItem> getReportList(String siteId, String taskId) { log.debug("Returning list of reports for site: " + siteId + ", task: " + taskId); Search search = new Search(); //Urkund-99 siteId can be null if (siteId != null) { search.addRestriction(new Restriction("siteId", siteId)); } search.addRestriction(new Restriction("taskId", taskId)); search.addRestriction(new Restriction("status", ContentReviewItem.SUBMITTED_REPORT_AVAILABLE_CODE)); return (List<ContentReviewItem>) (Object) dao.findBySearch(ContentReviewItemUrkund.class, search); } public List<ContentReviewItem> getAllContentReviewItems(String siteId, String taskId) { log.debug("Returning list of reports for site: " + siteId + ", task: " + taskId); Search search = new Search(); //Urkund-99 siteId can be null if (siteId != null) { search.addRestriction(new Restriction("siteId", siteId)); } search.addRestriction(new Restriction("taskId", taskId)); return (List<ContentReviewItem>) (Object) dao.findBySearch(ContentReviewItemUrkund.class, search); } public List<ContentReviewItem> getReportList(String siteId) { log.debug("Returning list of reports for site: " + siteId); Search search = new Search(); search.addRestriction(new Restriction("siteId", siteId)); search.addRestriction(new Restriction("status", ContentReviewItem.SUBMITTED_REPORT_AVAILABLE_CODE)); return (List<ContentReviewItem>) (Object) dao.findBySearch(ContentReviewItemUrkund.class, search); } public void resetUserDetailsLockedItems(String userId) { Search search = new Search(); search.addRestriction(new Restriction("userId", userId)); search.addRestriction(new Restriction("status", ContentReviewItem.SUBMISSION_ERROR_USER_DETAILS_CODE)); List<ContentReviewItemUrkund> lockedItems = dao.findBySearch(ContentReviewItemUrkund.class, search); for (int i = 0; i < lockedItems.size(); i++) { ContentReviewItemUrkund thisItem = (ContentReviewItemUrkund) lockedItems.get(i); thisItem.setStatus(ContentReviewItem.SUBMISSION_ERROR_RETRY_CODE); dao.update(thisItem); } } public void removeFromQueue(String ContentId) { List<ContentReviewItemUrkund> object = getItemsByContentId(ContentId); dao.delete(object); } public String getReviewReport(String contentId) 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); } 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 { int result = -1; try { List<ContentReviewItemUrkund> matchingItems = getItemsByContentId(contentId); if (matchingItems.isEmpty()) { log.debug("Content " + contentId + " has not been queued previously"); } if (matchingItems.size() > 1) { log.debug("More than one matching item - using first item found"); } ContentReviewItemUrkund item = (ContentReviewItemUrkund) matchingItems.iterator().next(); if (item.getStatus().compareTo(ContentReviewItem.SUBMITTED_REPORT_AVAILABLE_CODE) != 0) { log.debug("Report not available: " + item.getStatus()); } result = item.getReviewScore(); } catch (Exception e) { log.error("(getReviewScore)" + e); } return result; } List<ContentReviewItemUrkund> getItemsByContentId(String contentId) { Search search = new Search(); search.addRestriction(new Restriction("contentId", contentId)); List<ContentReviewItemUrkund> existingItems = dao.findBySearch(ContentReviewItemUrkund.class, search); return existingItems; } /** * 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) { try { String 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 (User user : activeUsers) { if (userId.equals(user.getId())) { return true; } } } catch (Exception e) { log.info("(isStudentUser)" + e); } return isStudent; } 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(); } /* * Obtain a lock on the item */ private boolean obtainLock(String itemId) { Boolean lock = dao.obtainLock(itemId, serverConfigurationService.getServerId(), LOCK_PERIOD); return (lock != null) ? lock : false; } /* * Get the next item that needs to be submitted * */ private ContentReviewItemUrkund getNextItemInSubmissionQueue() { Search search = new Search(); search.addRestriction(new Restriction("status", ContentReviewItem.NOT_SUBMITTED_CODE)); List<ContentReviewItemUrkund> notSubmittedItems = dao.findBySearch(ContentReviewItemUrkund.class, search); for (ContentReviewItemUrkund notSubmittedItem : notSubmittedItems) { // can we get a lock? if (obtainLock("item." + notSubmittedItem.getId().toString())) { return notSubmittedItem; } } search = new Search(); search.addRestriction(new Restriction("status", ContentReviewItem.SUBMISSION_ERROR_RETRY_CODE)); notSubmittedItems = dao.findBySearch(ContentReviewItemUrkund.class, search); //we need the next one whose retry time has not been reached for (ContentReviewItemUrkund notSubmittedItem : notSubmittedItems) { if (hasReachedRetryTime(notSubmittedItem) && obtainLock("item." + notSubmittedItem.getId().toString())) { return notSubmittedItem; } } return null; } private boolean hasReachedRetryTime(ContentReviewItemUrkund item) { // has the item reached its next retry time? if (item.getNextRetryTime() == null) { item.setNextRetryTime(new Date()); } if (item.getNextRetryTime().after(new Date())) { //we haven't reached the next retry time log.debug("next retry time not yet reached for item: " + item.getId()); dao.update(item); return false; } return true; } private void releaseLock(ContentReviewItem currentItem) { dao.releaseLock("item." + currentItem.getId().toString(), serverConfigurationService.getServerId()); } public void processQueue() { log.debug("Processing submission queue"); int errors = 0; int success = 0; for (ContentReviewItemUrkund currentItem = getNextItemInSubmissionQueue(); currentItem != null; currentItem = getNextItemInSubmissionQueue()) { if (currentItem.getRetryCount() == null) { currentItem.setRetryCount(Long.valueOf(0)); currentItem.setNextRetryTime(this.getNextRetryTime(0)); dao.update(currentItem); } else if (currentItem.getRetryCount().intValue() > maxRetry) { currentItem.setStatus(ContentReviewItem.SUBMISSION_ERROR_RETRY_EXCEEDED); currentItem.setLastError("Contact Service desk for help"); dao.update(currentItem); errors++; continue; } else { long l = currentItem.getRetryCount(); l++; currentItem.setRetryCount(l); currentItem.setNextRetryTime(this.getNextRetryTime(l)); dao.update(currentItem); } User user; try { user = userDirectoryService.getUser(currentItem.getUserId()); } catch (UserNotDefinedException e1) { log.error("Submission attempt unsuccessful - User not found.", e1); currentItem.setStatus(ContentReviewItem.SUBMISSION_ERROR_NO_RETRY_CODE); currentItem.setLastError("Contact Service desk for help"); dao.update(currentItem); releaseLock(currentItem); errors++; continue; } String uem = null; if (spoofEmailContext != null && spoofEmailContext.length() >= 2 && spoofEmailContext.length() <= 10) { uem = generateSpoofedSubmitterEmail(user, currentItem.getSiteId()); } else { uem = getEmail(user); } if (uem == null) { log.error("User: " + user.getEid() + " has no valid email"); currentItem.setStatus(ContentReviewItem.SUBMISSION_ERROR_USER_DETAILS_CODE); currentItem.setLastError("Contact Service desk for help"); dao.update(currentItem); releaseLock(currentItem); errors++; continue; } ContentResource resource; ResourceProperties resourceProperties; String fileName; try { try { resource = contentHostingService.getResource(currentItem.getContentId()); } catch (IdUnusedException e4) { log.warn("IdUnusedException: no resource with id " + currentItem.getContentId()); dao.delete(currentItem); errors++; continue; } resourceProperties = resource.getProperties(); fileName = resourceProperties.getProperty(resourceProperties.getNamePropDisplayName()); // Truncate long filenames to max 200 chars if (fileName != null && fileName.length() >= 200) { fileName = truncateFileName(fileName, 198); } } catch (PermissionException e2) { log.error("Submission failed due to permission error: " + e2.getMessage()); currentItem.setStatus(ContentReviewItem.SUBMISSION_ERROR_NO_RETRY_CODE); currentItem.setLastError("Contact Service desk for help"); dao.update(currentItem); releaseLock(currentItem); errors++; continue; } catch (TypeException e) { log.error("Submission failed due to content Type error.", e); currentItem.setStatus(ContentReviewItem.SUBMISSION_ERROR_NO_RETRY_CODE); currentItem.setLastError("Contact Service desk for help"); dao.update(currentItem); releaseLock(currentItem); errors++; continue; } // Get last GUID in contentId for external Id String[] tokens = currentItem.getContentId().split("/"); String extId = tokens[tokens.length - 2]; // Only extension in filename if (fileName.lastIndexOf(".") == 0) { fileName = extId + fileName; } UrkundSubmission submission = new UrkundSubmission(); submission.setSubmitterEmail(uem); submission.setSubject(""); // Insert site name? submission.setMessage(""); // Insert assignment title? submission.setFilename(fileName); submission.setExternalId(extId); submission.setAnon(false); // Check if valid content type, only try to submit if valid if (submission.getSupportedContentType() == null) { currentItem.setStatus(ContentReviewItem.SUBMISSION_ERROR_NO_RETRY_CODE); currentItem.setLastError("Invalid content type"); log.error("Submit NOT successful, invalid content type, will NOT be retried, content Id: " + currentItem.getContentId()); dao.update(currentItem); releaseLock(currentItem); errors++; continue; } try { submission.setDocument(resource.streamContent()); } catch (ServerOverloadException ex) { Logger.getLogger(UrkundReviewServiceImpl.class.getName()).log(Level.SEVERE, null, ex); } try { SubmissionsResponse rsp = urkundClient.submitDocument(submission); currentItem.setStatus(ContentReviewItem.SUBMITTED_AWAITING_REPORT_CODE); currentItem.setLastError(null); currentItem.setErrorCode(null); currentItem.setExternalId(extId); currentItem.setDateSubmitted(new Date()); success++; dao.update(currentItem); log.info("Submit successful, content Id:" + currentItem.getContentId()); } catch (UrkundException e) { // If server responds with a 4xx its a bad request, otherwise retry if (e.getHttpCode() >= 400 && e.getHttpCode() < 500) { currentItem.setStatus(ContentReviewItem.SUBMISSION_ERROR_NO_RETRY_CODE); currentItem.setLastError("Contact Service desk for help"); log.error("Submit NOT successful, HTTP code: " + e.getHttpCode() + ", will NOT be retried, content Id: " + currentItem.getContentId() + ", error: " + e.getMessage()); } else { currentItem.setStatus(ContentReviewItem.SUBMISSION_ERROR_RETRY_CODE); currentItem.setLastError("Submission Failure, will be retried"); log.error("Submit NOT successful, HTTP code: " + e.getHttpCode() + ", will be retried, content Id: " + currentItem.getContentId() + ", error: " + e.getMessage()); } dao.update(currentItem); errors++; } catch (Exception e) { // Unknown error, retry currentItem.setStatus(ContentReviewItem.SUBMISSION_ERROR_RETRY_CODE); currentItem.setLastError("Submission Failure, will be retried"); dao.update(currentItem); errors++; log.error("Submit NOT successful, will be retried, content Id: " + currentItem.getContentId() + ", error: " + e.getMessage()); } //release the lock so the reports job can handle it releaseLock(currentItem); getNextItemInSubmissionQueue(); } if (success > 0 || errors > 0) { log.info("Submission queue run completed: " + success + " items submitted, " + errors + " errors."); } } 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() { // get the list of all items that are waiting for reports List<ContentReviewItemUrkund> awaitingReport = dao.findByProperties(ContentReviewItemUrkund.class, new String[] { "status" }, new Object[] { ContentReviewItem.SUBMITTED_AWAITING_REPORT_CODE }); awaitingReport.addAll(dao.findByProperties(ContentReviewItemUrkund.class, new String[] { "status" }, new Object[] { ContentReviewItem.REPORT_ERROR_RETRY_CODE })); Iterator<ContentReviewItemUrkund> listIterator = awaitingReport.iterator(); log.debug("There are " + awaitingReport.size() + " submissions awaiting reports"); while (listIterator.hasNext()) { ContentReviewItemUrkund currentItem = (ContentReviewItemUrkund) 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.debug("next retry time not yet reached for item: " + currentItem.getId()); dao.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(ContentReviewItem.SUBMISSION_ERROR_RETRY_EXCEEDED); dao.update(currentItem); continue; } else { long l = currentItem.getRetryCount(); l++; currentItem.setRetryCount(l); currentItem.setNextRetryTime(this.getNextRetryTime(l)); dao.update(currentItem); } try { List<SubmissionsResponse> rspList = urkundClient.getReports(currentItem.getExternalId()); for (SubmissionsResponse rsp : rspList) { if (rsp.status.state == null) { log.error("status state == null"); } else { String state = rsp.status.state.toLowerCase(); if (currentItem.getExternalId().equals(rsp.externalId)) { log.info("Urkund submission report, state: " + state + ", " + rsp.toString()); if ("analyzed".equals(state)) { currentItem.setReviewScore(Math.round(rsp.report.significance)); currentItem.setReportUrl(rsp.report.reportUrl); currentItem.setStatus(ContentReviewItem.SUBMITTED_REPORT_AVAILABLE_CODE); currentItem.setDateReportReceived(new Date()); currentItem.setWarnings(rsp.report.warnings.size()); currentItem.setOptOutUrl(rsp.document.optOutInfo.url); } else if ("accepted".equals(state)) { currentItem.setOptOutUrl(rsp.document.optOutInfo.url); } else if ("submitted".equals(state)) { continue; } else { currentItem.setStatus(ContentReviewItem.REPORT_ERROR_NO_RETRY_CODE); currentItem.setLastError(rsp.status.message); } dao.update(currentItem); } } } } catch (UrkundException e) { log.error(e.getMessage()); } } } // 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) { SakaiPerson sp = sakaiPersonManager.getSakaiPerson(user.getId(), sakaiPersonManager.getSystemMutableType()); if (sp != null && isValidEmail(sp.getMail())) { profile_email = sp.getMail().trim(); } } if (uem == null && profile_email != null) { uem = profile_email; } if (uem == null && account_email != null) { uem = account_email; } 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) { if (email == null || email.equals("")) { return false; } email = email.trim(); //must contain @ if (!email.contains("@")) { return false; } //an email can't contain spaces if (email.indexOf(" ") > 0) { return false; } //use commons-validator EmailValidator validator = EmailValidator.getInstance(); return validator.isValid(email); } /* (non-Javadoc) * @see org.sakaiproject.contentreview.service.ContentReviewService#isAcceptableContent(org.sakaiproject.content.api.ContentResource) */ public boolean isAcceptableContent(ContentResource resource) { return urkundContentValidator.isAcceptableContent(resource); } /** * find the next time this item should be tried * * @param retryCount * @return */ private Date getNextRetryTime(long retryCount) { int offset = 30; if (retryCount == 1) { offset = 60; } else if (retryCount == 2) { offset = 120; } else if (retryCount == 3) { offset = 240; } else if (retryCount > 3) { offset = 480; } Calendar cal = Calendar.getInstance(); cal.add(Calendar.MINUTE, offset); return cal.getTime(); } public String getLocalizedStatusMessage(String messageCode, String userRef) { String userId = EntityReference.getIdFromRef(userRef); ResourceLoader resourceLoader = new ResourceLoader(userId, "urkund"); 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); List<ContentReviewItemUrkund> matchingItems = getItemsByContentId(contentId); if (matchingItems.isEmpty()) { log.debug("Content " + contentId + " has not been queued previously"); return null; } if (matchingItems.size() > 1) { log.debug("more than one matching item found - using first item found"); } //its possible the error code column is not populated Integer errorCode = ((ContentReviewItemUrkund) matchingItems.iterator().next()).getErrorCode(); if (errorCode == null) { return ((ContentReviewItemUrkund) matchingItems.iterator().next()).getLastError(); } return getLocalizedStatusMessage(errorCode.toString()); } @Override public Map getAssignment(String siteId, String taskId) throws SubmissionException, TransientSubmissionException { log.debug("Dummy getAssignment()"); return null; } @Override public void createAssignment(String siteId, String taskId, Map extraAsnnOpts) throws SubmissionException, TransientSubmissionException { log.debug("Dummy createAssignment()"); } private String generateSpoofedSubmitterEmail(User user, String siteId) { String spoofedEmail = String.format(URKUND_SPOOFED_EMAIL_TEMPLATE, user.getId(), siteId, spoofEmailContext); return spoofedEmail; } @Override public int getReviewWarnings(String contentId) throws QueueException, ReportException, Exception { List<ContentReviewItemUrkund> matchingItems = getItemsByContentId(contentId); if (matchingItems.size() == 0) { log.debug("Content " + contentId + " has not been queued previously"); throw new QueueException("Content " + contentId + " has not been queued previously"); } if (matchingItems.size() > 1) { log.debug("More than one matching item - using first item found"); } ContentReviewItemUrkund item = (ContentReviewItemUrkund) matchingItems.iterator().next(); if (item.getStatus().compareTo(ContentReviewItem.SUBMITTED_REPORT_AVAILABLE_CODE) != 0) { log.debug("Report not available: " + item.getStatus()); throw new ReportException("Report not available: " + item.getStatus()); } return item.getWarnings(); } @Override public String getOptOutUrl(String contentId) throws QueueException, ReportException { List<ContentReviewItemUrkund> matchingItems = getItemsByContentId(contentId); if (matchingItems.size() == 0) { log.debug("Content " + contentId + " has not been queued previously"); throw new QueueException("Content " + contentId + " has not been queued previously"); } if (matchingItems.size() > 1) { log.debug("More than one matching item - using first item found"); } ContentReviewItemUrkund item = (ContentReviewItemUrkund) matchingItems.iterator().next(); if (item.getStatus().compareTo(ContentReviewItem.SUBMITTED_REPORT_AVAILABLE_CODE) != 0) { log.debug("Report not available: " + item.getStatus()); throw new ReportException("Report not available: " + item.getStatus()); } return item.getOptOutUrl(); } }