001: /**********************************************************************************
002: *
003: * $Id: GradebookExternalAssessmentServiceImpl.java 20819 2007-01-30 22:29:31Z ray@media.berkeley.edu $
004: *
005: ***********************************************************************************
006: *
007: * Copyright (c) 2007 The Regents of the University of California
008: *
009: * Licensed under the Educational Community License, Version 1.0 (the "License");
010: * you may not use this file except in compliance with the License.
011: * You may obtain a copy of the License at
012: *
013: * http://www.opensource.org/licenses/ecl1.php
014: *
015: * Unless required by applicable law or agreed to in writing, software
016: * distributed under the License is distributed on an "AS IS" BASIS,
017: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018: * See the License for the specific language governing permissions and
019: * limitations under the License.
020: *
021: **********************************************************************************/package org.sakaiproject.component.gradebook;
022:
023: import java.util.Date;
024: import java.util.HashSet;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.Map;
028: import java.util.Set;
029:
030: import org.apache.commons.lang.StringUtils;
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033: import org.hibernate.HibernateException;
034: import org.hibernate.Query;
035: import org.hibernate.Session;
036: import org.sakaiproject.service.gradebook.shared.AssessmentNotFoundException;
037: import org.sakaiproject.service.gradebook.shared.AssignmentHasIllegalPointsException;
038: import org.sakaiproject.service.gradebook.shared.ConflictingAssignmentNameException;
039: import org.sakaiproject.service.gradebook.shared.ConflictingExternalIdException;
040: import org.sakaiproject.service.gradebook.shared.GradebookExternalAssessmentService;
041: import org.sakaiproject.service.gradebook.shared.GradebookNotFoundException;
042: import org.sakaiproject.tool.gradebook.Assignment;
043: import org.sakaiproject.tool.gradebook.AssignmentGradeRecord;
044: import org.sakaiproject.tool.gradebook.Gradebook;
045: import org.springframework.orm.hibernate3.HibernateCallback;
046: import org.springframework.orm.hibernate3.HibernateTemplate;
047:
048: public class GradebookExternalAssessmentServiceImpl extends
049: BaseHibernateManager implements
050: GradebookExternalAssessmentService {
051: private static final Log log = LogFactory
052: .getLog(GradebookExternalAssessmentServiceImpl.class);
053: // Special logger for data contention analysis.
054: private static final Log logData = LogFactory
055: .getLog(GradebookExternalAssessmentServiceImpl.class
056: .getName()
057: + ".GB_DATA");
058:
059: public void addExternalAssessment(final String gradebookUid,
060: final String externalId, final String externalUrl,
061: final String title, final double points,
062: final Date dueDate, final String externalServiceDescription)
063: throws ConflictingAssignmentNameException,
064: ConflictingExternalIdException, GradebookNotFoundException {
065:
066: // Ensure that the required strings are not empty
067: if (StringUtils.trimToNull(externalServiceDescription) == null
068: || StringUtils.trimToNull(externalId) == null
069: || StringUtils.trimToNull(title) == null) {
070: throw new RuntimeException(
071: "External service description, externalId, and title must not be empty");
072: }
073:
074: // Ensure that points is > zero
075: if (points <= 0) {
076: throw new AssignmentHasIllegalPointsException(
077: "Points must be > 0");
078: }
079:
080: // Ensure that the assessment name is unique within this gradebook
081: if (isAssignmentDefined(gradebookUid, title)) {
082: throw new ConflictingAssignmentNameException(
083: "An assignment with that name already exists in gradebook uid="
084: + gradebookUid);
085: }
086:
087: getHibernateTemplate().execute(new HibernateCallback() {
088: public Object doInHibernate(Session session)
089: throws HibernateException {
090: // Ensure that the externalId is unique within this gradebook
091: Integer externalIdConflicts = (Integer) session
092: .createQuery(
093: "select count(asn) from Assignment as asn where asn.externalId=? and asn.gradebook.uid=?")
094: .setString(0, externalId).setString(1,
095: gradebookUid).uniqueResult();
096: if (externalIdConflicts.intValue() > 0) {
097: throw new ConflictingExternalIdException(
098: "An external assessment with that ID already exists in gradebook uid="
099: + gradebookUid);
100: }
101:
102: // Get the gradebook
103: Gradebook gradebook = getGradebook(gradebookUid);
104:
105: // Create the external assignment
106: Assignment asn = new Assignment(gradebook, title,
107: new Double(points), dueDate);
108: asn.setExternallyMaintained(true);
109: asn.setExternalId(externalId);
110: asn.setExternalInstructorLink(externalUrl);
111: asn.setExternalStudentLink(externalUrl);
112: asn.setExternalAppName(externalServiceDescription);
113: //set released to be true to support selective release
114: asn.setReleased(true);
115:
116: session.save(asn);
117: return null;
118: }
119: });
120: if (log.isInfoEnabled())
121: log
122: .info("External assessment added to gradebookUid="
123: + gradebookUid + ", externalId="
124: + externalId + " by userUid="
125: + getUserUid() + " from externalApp="
126: + externalServiceDescription);
127: }
128:
129: /**
130: * @see org.sakaiproject.service.gradebook.shared.GradebookService#updateExternalAssessment(java.lang.String, java.lang.String, java.lang.String, java.lang.String, long, java.util.Date)
131: */
132: public void updateExternalAssessment(final String gradebookUid,
133: final String externalId, final String externalUrl,
134: final String title, final double points, final Date dueDate)
135: throws GradebookNotFoundException,
136: AssessmentNotFoundException,
137: AssignmentHasIllegalPointsException {
138: final Assignment asn = getExternalAssignment(gradebookUid,
139: externalId);
140:
141: if (asn == null) {
142: throw new AssessmentNotFoundException(
143: "There is no assessment id=" + externalId
144: + " in gradebook uid=" + gradebookUid);
145: }
146:
147: // Ensure that points is > zero
148: if (points <= 0) {
149: throw new AssignmentHasIllegalPointsException(
150: "Points must be > 0");
151: }
152:
153: // Ensure that the required strings are not empty
154: if (StringUtils.trimToNull(externalId) == null
155: || StringUtils.trimToNull(title) == null) {
156: throw new RuntimeException(
157: "ExternalId, and title must not be empty");
158: }
159:
160: HibernateCallback hc = new HibernateCallback() {
161: public Object doInHibernate(Session session)
162: throws HibernateException {
163: asn.setExternalInstructorLink(externalUrl);
164: asn.setExternalStudentLink(externalUrl);
165: asn.setName(title);
166: asn.setDueDate(dueDate);
167: //support selective release
168: asn.setReleased(true);
169: asn.setPointsPossible(new Double(points));
170: session.update(asn);
171: if (log.isInfoEnabled())
172: log
173: .info("External assessment updated in gradebookUid="
174: + gradebookUid
175: + ", externalId="
176: + externalId
177: + " by userUid="
178: + getUserUid());
179: return null;
180:
181: }
182: };
183: getHibernateTemplate().execute(hc);
184: }
185:
186: /**
187: * @see org.sakaiproject.service.gradebook.shared.GradebookService#removeExternalAssessment(java.lang.String, java.lang.String)
188: */
189: public void removeExternalAssessment(final String gradebookUid,
190: final String externalId) throws GradebookNotFoundException,
191: AssessmentNotFoundException {
192: // Get the external assignment
193: final Assignment asn = getExternalAssignment(gradebookUid,
194: externalId);
195: if (asn == null) {
196: throw new AssessmentNotFoundException(
197: "There is no external assessment id=" + externalId
198: + " in gradebook uid=" + gradebookUid);
199: }
200:
201: // We need to go through Spring's HibernateTemplate to do
202: // any deletions at present. See the comments to deleteGradebook
203: // for the details.
204: HibernateTemplate hibTempl = getHibernateTemplate();
205:
206: List toBeDeleted = hibTempl
207: .find(
208: "from AssignmentGradeRecord as agr where agr.gradableObject=?",
209: asn);
210: int numberDeleted = toBeDeleted.size();
211: hibTempl.deleteAll(toBeDeleted);
212: if (log.isInfoEnabled())
213: log.info("Deleted " + numberDeleted
214: + " externally defined scores");
215:
216: // Delete the assessment.
217: hibTempl.flush();
218: hibTempl.clear();
219: hibTempl.delete(asn);
220:
221: if (log.isInfoEnabled())
222: log.info("External assessment removed from gradebookUid="
223: + gradebookUid + ", externalId=" + externalId
224: + " by userUid=" + getUserUid());
225: }
226:
227: private Assignment getExternalAssignment(final String gradebookUid,
228: final String externalId) throws GradebookNotFoundException {
229: final Gradebook gradebook = getGradebook(gradebookUid);
230:
231: HibernateCallback hc = new HibernateCallback() {
232: public Object doInHibernate(Session session)
233: throws HibernateException {
234: return session
235: .createQuery(
236: "from Assignment as asn where asn.gradebook=? and asn.externalId=?")
237: .setEntity(0, gradebook).setString(1,
238: externalId).uniqueResult();
239: }
240: };
241: return (Assignment) getHibernateTemplate().execute(hc);
242: }
243:
244: /**
245: * @see org.sakaiproject.service.gradebook.shared.GradebookService#updateExternalAssessmentScore(java.lang.String, java.lang.String, java.lang.String, Double)
246: */
247: public void updateExternalAssessmentScore(
248: final String gradebookUid, final String externalId,
249: final String studentUid, final Double points)
250: throws GradebookNotFoundException,
251: AssessmentNotFoundException {
252:
253: final Assignment asn = getExternalAssignment(gradebookUid,
254: externalId);
255:
256: if (asn == null) {
257: throw new AssessmentNotFoundException(
258: "There is no assessment id=" + externalId
259: + " in gradebook uid=" + gradebookUid);
260: }
261:
262: if (logData.isDebugEnabled())
263: logData.debug("BEGIN: Update 1 score for gradebookUid="
264: + gradebookUid + ", external assessment="
265: + externalId + " from " + asn.getExternalAppName());
266:
267: HibernateCallback hc = new HibernateCallback() {
268: public Object doInHibernate(Session session)
269: throws HibernateException {
270: Date now = new Date();
271:
272: AssignmentGradeRecord agr = getAssignmentGradeRecord(
273: asn, studentUid, session);
274:
275: // Try to reduce data contention by only updating when the
276: // score has actually changed.
277: Double oldPointsEarned = (agr == null) ? null : agr
278: .getPointsEarned();
279: if (((points != null) && (!points
280: .equals(oldPointsEarned)))
281: || ((points == null) && (oldPointsEarned != null))) {
282: if (agr == null) {
283: agr = new AssignmentGradeRecord(asn,
284: studentUid, points);
285: } else {
286: agr.setPointsEarned(points);
287: }
288:
289: agr.setDateRecorded(now);
290: agr.setGraderId(getUserUid());
291: if (log.isDebugEnabled())
292: log
293: .debug("About to save AssignmentGradeRecord id="
294: + agr.getId()
295: + ", version="
296: + agr.getVersion()
297: + ", studenttId="
298: + agr.getStudentId()
299: + ", pointsEarned="
300: + agr.getPointsEarned());
301: session.saveOrUpdate(agr);
302:
303: // Sync database.
304: session.flush();
305: session.clear();
306: } else {
307: if (log.isDebugEnabled())
308: log
309: .debug("Ignoring updateExternalAssessmentScore, since the new points value is the same as the old");
310: }
311: return null;
312: }
313: };
314: getHibernateTemplate().execute(hc);
315: if (logData.isDebugEnabled())
316: logData.debug("END: Update 1 score for gradebookUid="
317: + gradebookUid + ", external assessment="
318: + externalId + " from " + asn.getExternalAppName());
319: if (log.isDebugEnabled())
320: log
321: .debug("External assessment score updated in gradebookUid="
322: + gradebookUid
323: + ", externalId="
324: + externalId
325: + " by userUid="
326: + getUserUid() + ", new score=" + points);
327: }
328:
329: public void updateExternalAssessmentScores(
330: final String gradebookUid, final String externalId,
331: final Map studentUidsToScores)
332: throws GradebookNotFoundException,
333: AssessmentNotFoundException {
334:
335: final Assignment assignment = getExternalAssignment(
336: gradebookUid, externalId);
337: if (assignment == null) {
338: throw new AssessmentNotFoundException(
339: "There is no assessment id=" + externalId
340: + " in gradebook uid=" + gradebookUid);
341: }
342: final Set studentIds = studentUidsToScores.keySet();
343: if (studentIds.isEmpty()) {
344: return;
345: }
346: final Date now = new Date();
347: final String graderId = getUserUid();
348:
349: getHibernateTemplate().execute(new HibernateCallback() {
350: public Object doInHibernate(Session session)
351: throws HibernateException {
352: List existingScores;
353: if (studentIds.size() <= MAX_NUMBER_OF_SQL_PARAMETERS_IN_LIST) {
354: Query q = session
355: .createQuery("from AssignmentGradeRecord as gr where gr.gradableObject=:go and gr.studentId in (:studentIds)");
356: q.setParameter("go", assignment);
357: q.setParameterList("studentIds", studentIds);
358: existingScores = q.list();
359: } else {
360: Query q = session
361: .createQuery("from AssignmentGradeRecord as gr where gr.gradableObject=:go");
362: q.setParameter("go", assignment);
363: existingScores = filterGradeRecordsByStudents(q
364: .list(), studentIds);
365: }
366:
367: Set previouslyUnscoredStudents = new HashSet(studentIds);
368: Set changedStudents = new HashSet();
369: for (Iterator iter = existingScores.iterator(); iter
370: .hasNext();) {
371: AssignmentGradeRecord agr = (AssignmentGradeRecord) iter
372: .next();
373: String studentUid = agr.getStudentId();
374: previouslyUnscoredStudents.remove(studentUid);
375:
376: // Try to reduce data contention by only updating when a score
377: // has changed.
378: Double oldPointsEarned = agr.getPointsEarned();
379: Double newPointsEarned = (Double) studentUidsToScores
380: .get(studentUid);
381: if (((newPointsEarned != null) && (!newPointsEarned
382: .equals(oldPointsEarned)))
383: || ((newPointsEarned == null) && (oldPointsEarned != null))) {
384: agr.setDateRecorded(now);
385: agr.setGraderId(graderId);
386: agr.setPointsEarned(newPointsEarned);
387: session.update(agr);
388: changedStudents.add(studentUid);
389: }
390: }
391: for (Iterator iter = previouslyUnscoredStudents
392: .iterator(); iter.hasNext();) {
393: String studentUid = (String) iter.next();
394:
395: // Don't save unnecessary null scores.
396: Double newPointsEarned = (Double) studentUidsToScores
397: .get(studentUid);
398: if (newPointsEarned != null) {
399: AssignmentGradeRecord agr = new AssignmentGradeRecord(
400: assignment, studentUid, newPointsEarned);
401: agr.setDateRecorded(now);
402: agr.setGraderId(graderId);
403: session.save(agr);
404: changedStudents.add(studentUid);
405: }
406: }
407:
408: if (log.isDebugEnabled())
409: log.debug("updateExternalAssessmentScores sent "
410: + studentIds.size()
411: + " records, actually changed "
412: + changedStudents.size());
413:
414: // Sync database.
415: session.flush();
416: session.clear();
417: return null;
418: }
419: });
420: }
421:
422: public boolean isAssignmentDefined(final String gradebookUid,
423: final String assignmentName)
424: throws GradebookNotFoundException {
425: Assignment assignment = (Assignment) getHibernateTemplate()
426: .execute(new HibernateCallback() {
427: public Object doInHibernate(Session session)
428: throws HibernateException {
429: return getAssignmentWithoutStats(gradebookUid,
430: assignmentName, session);
431: }
432: });
433: return (assignment != null);
434: }
435:
436: public boolean isExternalAssignmentDefined(String gradebookUid,
437: String externalId) throws GradebookNotFoundException {
438: Assignment assignment = getExternalAssignment(gradebookUid,
439: externalId);
440: return (assignment != null);
441: }
442:
443: public void setExternalAssessmentToGradebookAssignment(
444: final String gradebookUid, final String externalId) {
445: final Assignment assignment = getExternalAssignment(
446: gradebookUid, externalId);
447: if (assignment == null) {
448: throw new AssessmentNotFoundException(
449: "There is no assessment id=" + externalId
450: + " in gradebook uid=" + gradebookUid);
451: }
452: assignment.setExternalAppName(null);
453: assignment.setExternalId(null);
454: assignment.setExternalInstructorLink(null);
455: assignment.setExternalStudentLink(null);
456: assignment.setExternallyMaintained(false);
457: getHibernateTemplate().execute(new HibernateCallback() {
458: public Object doInHibernate(Session session)
459: throws HibernateException {
460: session.update(assignment);
461: if (log.isInfoEnabled())
462: log
463: .info("Externally-managed assignment "
464: + externalId
465: + " moved to Gradebook management in gradebookUid="
466: + gradebookUid + " by userUid="
467: + getUserUid());
468: return null;
469: }
470: });
471: }
472:
473: }
|