/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/assignment/trunk/assignment-impl/impl/src/java/org/sakaiproject/assignment/impl/DbAssignmentService.java $ * $Id: DbAssignmentService.java 110665 2012-07-24 14:18:50Z azeckoski@unicon.net $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009 The Sakai Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **********************************************************************************/ package org.sakaiproject.assignment.impl; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.assignment.api.Assignment; import org.sakaiproject.assignment.api.AssignmentContent; import org.sakaiproject.assignment.api.AssignmentContentEdit; import org.sakaiproject.assignment.api.AssignmentEdit; import org.sakaiproject.assignment.api.AssignmentSubmission; import org.sakaiproject.assignment.api.AssignmentSubmissionEdit; import org.sakaiproject.assignment.cover.AssignmentService; import org.sakaiproject.authz.api.Member; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.Group; import org.sakaiproject.site.cover.SiteService; import org.sakaiproject.db.api.SqlReader; import org.sakaiproject.db.api.SqlService; import org.sakaiproject.entity.api.Entity; import org.sakaiproject.util.BaseDbSingleStorage; import org.sakaiproject.util.Xml; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * <p> * DbAssignmentService is the database-storing service class for Assignments. * </p> */ public class DbAssignmentService extends BaseAssignmentService { /** Our logger. */ private static Log M_log = LogFactory.getLog(DbAssignmentService.class); /** The name of the db table holding assignment objects. */ protected String m_assignmentsTableName = "ASSIGNMENT_ASSIGNMENT"; /** The name of the db table holding assignment content objects. */ protected String m_contentsTableName = "ASSIGNMENT_CONTENT"; /** The name of the db table holding assignment submission objects. */ protected String m_submissionsTableName = "ASSIGNMENT_SUBMISSION"; /** If true, we do our locks in the remote database, otherwise we do them here. */ protected boolean m_locksInDb = true; /** Extra fields to store in the db with the XML. */ protected static final String[] FIELDS = { "CONTEXT"}; /** Extra fields to store in the db with the XML in ASSIGNMENT_SUBMISSION table */ protected static final String[] SUBMISSION_FIELDS = { "CONTEXT", "SUBMITTER_ID", "SUBMIT_TIME", "SUBMITTED", "GRADED"}; /********************************************************************************************************************************************************************************************************************************************************** * Constructors, Dependencies and their setter methods *********************************************************************************************************************************************************************************************************************************************************/ /** Dependency: SqlService */ protected SqlService m_sqlService = null; /** * Dependency: SqlService. * * @param service * The SqlService. */ public void setSqlService(SqlService service) { m_sqlService = service; } /** * Configuration: set the table name for assignments. * * @param path * The table name for assignments. */ public void setAssignmentTableName(String name) { m_assignmentsTableName = name; } /** * Configuration: set the table name for contents. * * @param path * The table name for contents. */ public void setContentTableName(String name) { m_contentsTableName = name; } /** * Configuration: set the table name for submissions. * * @param path * The table name for submissions. */ public void setSubmissionTableName(String name) { m_submissionsTableName = name; } /** * Configuration: set the locks-in-db * * @param value * The locks-in-db value. */ public void setLocksInDb(String value) { m_locksInDb = Boolean.valueOf(value).booleanValue(); } /** Set if we are to run the to-context conversion. */ protected boolean m_convertToContext = false; /** * Configuration: run the to-context conversion * * @param value * The locks-in-db value. */ public void setConvertToContext(String value) { m_convertToContext = Boolean.valueOf(value).booleanValue(); } /** Configuration: to run the ddl on init or not. */ protected boolean m_autoDdl = false; /** * Configuration: to run the ddl on init or not. * * @param value * the auto ddl value. */ public void setAutoDdl(String value) { m_autoDdl = Boolean.valueOf(value).booleanValue(); } /********************************************************************************************************************************************************************************************************************************************************** * Init and Destroy *********************************************************************************************************************************************************************************************************************************************************/ /** * Final initialization, once all dependencies are set. */ public void init() { try { // if we are auto-creating our schema, check and create if (m_autoDdl) { m_sqlService.ddl(this.getClass().getClassLoader(), "sakai_assignment"); } super.init(); M_log.info("init: assignments table: " + m_assignmentsTableName + " contents table: " + m_contentsTableName + " submissions table: " + m_submissionsTableName + " locks-in-db" + m_locksInDb); // convert? if (m_convertToContext) { m_convertToContext = false; convertToContext(); } } catch (Throwable t) { M_log.warn(this + ".init(): ", t); } } /********************************************************************************************************************************************************************************************************************************************************** * BaseAssignmentService extensions *********************************************************************************************************************************************************************************************************************************************************/ /** * Construct a Storage object for Assignments. * * @return The new storage object for Assignments. */ public AssignmentStorage newAssignmentStorage() { return new DbCachedAssignmentStorage(new AssignmentStorageUser()); } // newAssignmentStorage /** * Construct a Storage object for AssignmentsContents. * * @return The new storage object for AssignmentContents. */ public AssignmentContentStorage newContentStorage() { return new DbCachedAssignmentContentStorage(new AssignmentContentStorageUser()); } // newContentStorage /** * Construct a Storage object for AssignmentSubmissions. * * @return The new storage object for AssignmentSubmissions. */ protected AssignmentSubmissionStorage newSubmissionStorage() { return new DbCachedAssignmentSubmissionStorage(new AssignmentSubmissionStorageUser()); } // newSubmissionStorage /********************************************************************************************************************************************************************************************************************************************************** * Storage implementations *********************************************************************************************************************************************************************************************************************************************************/ /********************************************************************************************************************************************************************************************************************************************************** * AssignmentStorage implementation *********************************************************************************************************************************************************************************************************************************************************/ /** * Covers for the BaseDbSingleStorage, providing Assignment and AssignmentEdit parameters */ protected class DbCachedAssignmentStorage extends BaseDbSingleStorage implements AssignmentStorage { /** * Construct. * * @param assignment * The StorageUser class to call back for creation of Resource and Edit objects. */ public DbCachedAssignmentStorage(AssignmentStorageUser assignment) { super(m_assignmentsTableName, "ASSIGNMENT_ID", FIELDS, m_locksInDb, "assignment", assignment, m_sqlService); } // DbCachedAssignmentStorage public boolean check(String id) { return super.checkResource(id); } public Assignment get(String id) { return (Assignment) super.getResource(id); } public List getAll(String context) { return super.getAllResourcesWhere(FIELDS[0], context); } public AssignmentEdit put(String id, String context) { // pack the context in an array Object[] others = new Object[1]; others[0] = context; return (AssignmentEdit) super.putResource(id, others); } public AssignmentEdit edit(String id) { return (AssignmentEdit) super.editResource(id); } public void commit(AssignmentEdit edit) { super.commitResource(edit); } public void cancel(AssignmentEdit edit) { super.cancelResource(edit); } public void remove(AssignmentEdit edit) { super.removeResource(edit); } } // DbCachedAssignmentStorage /********************************************************************************************************************************************************************************************************************************************************** * AssignmentContentStorage implementation *********************************************************************************************************************************************************************************************************************************************************/ /** * Covers for the BaseDbSingleStorage, providing AssignmentContent and AssignmentContentEdit parameters */ protected class DbCachedAssignmentContentStorage extends BaseDbSingleStorage implements AssignmentContentStorage { /** * Construct. * * @param content * The StorageUser class to call back for creation of Resource and Edit objects. */ public DbCachedAssignmentContentStorage(AssignmentContentStorageUser content) { super(m_contentsTableName, "CONTENT_ID", FIELDS, m_locksInDb, "content", content, m_sqlService); } // DbCachedAssignmentContentStorage public boolean check(String id) { return super.checkResource(id); } public AssignmentContent get(String id) { return (AssignmentContent) super.getResource(id); } public List getAll(String context) { return super.getAllResourcesWhere(FIELDS[0], context); } public AssignmentContentEdit put(String id, String context) { // pack the context in an array Object[] others = new Object[1]; others[0] = context; return (AssignmentContentEdit) super.putResource(id, others); } public AssignmentContentEdit edit(String id) { return (AssignmentContentEdit) super.editResource(id); } public void commit(AssignmentContentEdit edit) { super.commitResource(edit); } public void cancel(AssignmentContentEdit edit) { super.cancelResource(edit); } public void remove(AssignmentContentEdit edit) { super.removeResource(edit); } } // DbCachedAssignmentContentStorage /********************************************************************************************************************************************************************************************************************************************************** * AssignmentSubmissionStorage implementation *********************************************************************************************************************************************************************************************************************************************************/ /** * Covers for the BaseDbSingleStorage, providing AssignmentSubmission and AssignmentSubmissionEdit parameters */ protected class DbCachedAssignmentSubmissionStorage extends BaseDbSingleStorage implements AssignmentSubmissionStorage { /*FIELDS: "CONTEXT", "SUBMITTER_ID", "SUBMIT_TIME", "SUBMITTED", "GRADED"*/ /** * Construct. * * @param submission * The StorageUser class to call back for creation of Resource and Edit objects. */ public DbCachedAssignmentSubmissionStorage(AssignmentSubmissionStorageUser submission) { super(m_submissionsTableName, "SUBMISSION_ID", SUBMISSION_FIELDS, m_locksInDb, "submission", submission, m_sqlService); } // DbCachedAssignmentSubmissionStorage public boolean check(String id) { return super.checkResource(id); } public AssignmentSubmission get(String id) { return (AssignmentSubmission) super.getResource(id); } /** * {@inheritDoc} */ public AssignmentSubmission get (String assignmentId, String userId) { Entity entry = null; // get the user from the db // need to construct the query here instead of relying on the SingleStorage client String sql = "select XML from " + m_submissionsTableName + " where (" + SUBMISSION_FIELDS[0] + " = ? AND "+ SUBMISSION_FIELDS[1] + " = ?)"; Object fields[] = new Object[2]; fields[0] = caseId(assignmentId); fields[1] = caseId(userId); List xml = m_sql.dbRead(sql, fields, null); if (!xml.isEmpty()) { // create the Resource from the db xml entry = readResource((String) xml.get(0)); return (AssignmentSubmission) entry; } else { return null; } } /** * Helper method to exclude inactive site members from submissions count * @param sqlWhere where clause from sql query * @param assignmentId assignment id relating to query * @return */ private int getSubmissionsCountWhere(String queryType, String assignmentRef) { int count = 0; Site site = null; Collection asgGroups = null; String sqlWhere = null; try { Assignment a = AssignmentService.getAssignment(assignmentRef); // is this a non-electronice submission type assignment boolean isNonElectronicSubmission = a.NON_ELECTRONIC_ASSIGNMENT_SUBMISSION == a.getContent().getTypeOfSubmission(); if ("submitted".equals(queryType)) { if (isNonElectronicSubmission) { sqlWhere ="where context='" + assignmentId(assignmentRef) + "' AND " + SUBMISSION_FIELDS[3] + "='" + Boolean.TRUE.toString() + "'"; } else { sqlWhere ="where context='" + assignmentId(assignmentRef) + "' AND " + SUBMISSION_FIELDS[2] + " IS NOT NULL AND " + SUBMISSION_FIELDS[3] + "='" + Boolean.TRUE.toString() + "'"; } } else if ("ungraded".equals(queryType)) { if (isNonElectronicSubmission) { sqlWhere = "where context='" + assignmentId(assignmentRef) + "' AND " + SUBMISSION_FIELDS[3] + "='" + Boolean.TRUE.toString() + "' AND " + SUBMISSION_FIELDS[4] + "='" + Boolean.FALSE.toString() + "'"; } else { sqlWhere ="where context='" + assignmentId(assignmentRef) + "' AND " + SUBMISSION_FIELDS[2] + " IS NOT NULL AND " + SUBMISSION_FIELDS[3] + "='" + Boolean.TRUE.toString() + "' AND " + SUBMISSION_FIELDS[4] + "='" + Boolean.FALSE.toString() + "'"; } } if (a.getAccess().equals(Assignment.AssignmentAccess.GROUPED)) { asgGroups = a.getGroups(); } site = SiteService.getSite(a.getContext()); List l = super.getSelectedResourcesWhere(sqlWhere); if (a.isGroup()) { for (Object o : l) { AssignmentSubmission assignmentSubmission = (AssignmentSubmission)o; Group _gg = site.getGroup(assignmentSubmission.getSubmitterId()); if (_gg != null) count++; } } else { // check whether the submitter is an active member of the site for (Object o : l) { AssignmentSubmission assignmentSubmission = (AssignmentSubmission)o; String userId = assignmentSubmission.getSubmitterIdString(); Member member = site != null ? site.getMember(userId) : null; if(member != null && member.isActive()) { if (asgGroups != null) { // for group based assignment: check whether member is in any group if the assignment is for groups boolean inGroup = false; for (Iterator iAsgGroups=asgGroups.iterator(); site!=null && !inGroup && iAsgGroups.hasNext();) { String groupId = (String) iAsgGroups.next(); try { Group group = site.getGroup(groupId); if ( group != null && group.getUserRole(userId) != null) { // in one of the group, hence increase the count inGroup = true; count++; } } catch (Exception ee) { M_log.warn(this + " getSubmissionsCountWhere " + ee.getMessage() + " sqlWhere = " + sqlWhere + " assignmentRef=" + assignmentRef); } } } else { // for site based assignment count++; } } } } } catch (Throwable t) { M_log.warn(this + ".getSubmissionsCountWhere(): ", t); throw new IllegalArgumentException(t); } return count; } /** * {@inheritDoc} */ public int getSubmittedSubmissionsCount(String assignmentRef) { return getSubmissionsCountWhere("submitted", assignmentRef); } /** * {@inheritDoc} */ public int getUngradedSubmissionsCount(String assignmentRef) { return getSubmissionsCountWhere("ungraded", assignmentRef ); } public List getAll(String context) { return super.getAllResourcesWhere(SUBMISSION_FIELDS[0], context); } public AssignmentSubmissionEdit put(String id, String assignmentId, String submitterId, String submitTime, String submitted, String graded) { // pack the context in an array Object[] others = new Object[5]; others[0] = assignmentId; others[1] = submitterId; others[2] = submitTime; others[3] = submitted; others[4] = graded; return (AssignmentSubmissionEdit) super.putResource(id, others); } public AssignmentSubmissionEdit edit(String id) { return (AssignmentSubmissionEdit) super.editResource(id); } public void commit(AssignmentSubmissionEdit edit) { super.commitResource(edit); } public void cancel(AssignmentSubmissionEdit edit) { super.cancelResource(edit); } public void remove(AssignmentSubmissionEdit edit) { super.removeResource(edit); } } // DbCachedAssignmentSubmissionStorage /** * fill in the context field for any record missing it */ protected void convertToContext() { M_log.info(this + " convertToContext"); try { // get a connection final Connection connection = m_sqlService.borrowConnection(); boolean wasCommit = connection.getAutoCommit(); connection.setAutoCommit(false); // read all assignment records String sql = "select XML from ASSIGNMENT_ASSIGNMENT where CONTEXT is null"; m_sqlService.dbRead(connection, sql, null, new SqlReader() { public Object readSqlResultRecord(ResultSet result) { try { // create the Resource from the db xml String xml = result.getString(1); // read the xml Document doc = Xml.readDocumentFromString(xml); // verify the root element Element root = doc.getDocumentElement(); if (!root.getTagName().equals("assignment")) { M_log.warn(this + " convertToContext(): XML root element not assignment: " + root.getTagName()); return null; } Assignment a = new BaseAssignment(root); // context is context String context = a.getContext(); String id = a.getId(); // update String update = "update ASSIGNMENT_ASSIGNMENT set CONTEXT = ? where ASSIGNMENT_ID = ?"; Object fields[] = new Object[2]; fields[0] = context; fields[1] = id; boolean ok = m_sqlService.dbWrite(connection, update, fields); M_log.info(this + " convertToContext: assignment id: " + id + " context: " + context + " ok: " + ok); return null; } catch (SQLException ignore) { M_log.warn(this + ":convertToContext " + ignore.getMessage()); return null; } } }); // read all content records sql = "select XML from ASSIGNMENT_CONTENT where CONTEXT is null"; m_sqlService.dbRead(connection, sql, null, new SqlReader() { public Object readSqlResultRecord(ResultSet result) { try { // create the Resource from the db xml String xml = result.getString(1); // read the xml Document doc = Xml.readDocumentFromString(xml); // verify the root element Element root = doc.getDocumentElement(); if (!root.getTagName().equals("content")) { M_log.warn(this + " convertToContext(): XML root element not content: " + root.getTagName()); return null; } AssignmentContent c = new BaseAssignmentContent(root); // context is creator String context = c.getCreator(); String id = c.getId(); // update String update = "update ASSIGNMENT_CONTENT set CONTEXT = ? where CONTENT_ID = ?"; Object fields[] = new Object[2]; fields[0] = context; fields[1] = id; boolean ok = m_sqlService.dbWrite(connection, update, fields); M_log.info(this + " convertToContext: content id: " + id + " context: " + context + " ok: " + ok); return null; } catch (SQLException ignore) { M_log.warn(this + ":convertToContext SqlReader " + ignore.getMessage()); return null; } } }); // read all submission records sql = "select XML from ASSIGNMENT_SUBMISSION where CONTEXT is null"; m_sqlService.dbRead(connection, sql, null, new SqlReader() { public Object readSqlResultRecord(ResultSet result) { try { // create the Resource from the db xml String xml = result.getString(1); // read the xml Document doc = Xml.readDocumentFromString(xml); // verify the root element Element root = doc.getDocumentElement(); if (!root.getTagName().equals("submission")) { M_log.warn(this + " convertToContext(): XML root element not submission: " + root.getTagName()); return null; } AssignmentSubmission s = new BaseAssignmentSubmission(root); // context is assignment id String context = s.getAssignmentId(); String id = s.getId(); // update String update = "update ASSIGNMENT_SUBMISSION set CONTEXT = ? where SUBMISSION_ID = ?"; Object fields[] = new Object[2]; fields[0] = context; fields[1] = id; boolean ok = m_sqlService.dbWrite(connection, update, fields); M_log.info(this + " convertToContext: submission id: " + id + " context: " + context + " ok: " + ok); return null; } catch (SQLException ignore) { M_log.warn(this + ":convertToContext:SqlReader " + ignore.getMessage()); return null; } } }); connection.commit(); connection.setAutoCommit(wasCommit); m_sqlService.returnConnection(connection); } catch (Throwable t) { M_log.warn(this + " convertToContext: failed: " + t); } // TODO: M_log.info(this + " convertToContext: done"); } }