/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.course.assessment.manager;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.olat.basesecurity.model.IdentityRefImpl;
import org.olat.core.commons.persistence.DB;
import org.olat.core.gui.control.Event;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.core.util.event.GenericEventListener;
import org.olat.course.CourseFactory;
import org.olat.course.assessment.AssessmentMode;
import org.olat.course.assessment.AssessmentMode.Status;
import org.olat.course.assessment.AssessmentModeCoordinationService;
import org.olat.course.assessment.AssessmentModeNotificationEvent;
import org.olat.course.assessment.AssessmentModule;
import org.olat.course.assessment.model.AssessmentModeImpl;
import org.olat.course.assessment.model.CoordinatedAssessmentMode;
import org.olat.course.assessment.model.TransientAssessmentMode;
import org.olat.group.ui.edit.BusinessGroupModifiedEvent;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
*
* Initial date: 06.01.2015<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
@Service
public class AssessmentModeCoordinationServiceImpl implements AssessmentModeCoordinationService, GenericEventListener {
private static final OLog log = Tracing.createLoggerFor(AssessmentModeCoordinationServiceImpl.class);
@Autowired
private DB dbInstance;
@Autowired
private AssessmentModule assessmentModule;
@Autowired
private RepositoryService repositoryService;
@Autowired
private CoordinatorManager coordinatorManager;
@Autowired
private AssessmentModeManagerImpl assessmentModeManager;
private Map<Long,CoordinatedAssessmentMode> coordinatedModes = new ConcurrentHashMap<>();
protected synchronized void beat() {
if(assessmentModule.isAssessmentModeEnabled()) {
Date now = now();
List<Long> currentModeKeys = new ArrayList<>();
List<AssessmentMode> currentModes = assessmentModeManager.getAssessmentModes(now);
for(AssessmentMode currentMode:currentModes) {
try {
sendEvent(currentMode, now, false);
currentModeKeys.add(currentMode.getKey());
} catch (Exception e) {
log.error("", e);
}
}
//remove coordinated mode
List<Long> coordinatedModeKeys = new ArrayList<>(coordinatedModes.keySet());
for(Long coordinatedModeKey:coordinatedModeKeys) {
if(!currentModeKeys.contains(coordinatedModeKey)) {
CoordinatedAssessmentMode decoordinatedMode = coordinatedModes.remove(coordinatedModeKey);
if(decoordinatedMode != null) {
coordinatorManager.getCoordinator().getEventBus()
.deregisterFor(this, decoordinatedMode.getListenerRes());
}
}
}
if(coordinatedModes.size() > 250) {
log.error("Seem to be a leak of coordinated modes");
}
}
}
private Date now() {
Calendar cal = Calendar.getInstance();
//round to minute
cal.set(Calendar.MILLISECOND, 0);
cal.set(Calendar.SECOND, 0);
return cal.getTime();
}
protected AssessmentMode syncManuallySetStatus(AssessmentMode mode, boolean forceStatus) {
return sendEvent(mode, now(), forceStatus);
}
protected AssessmentMode syncAutomicallySetStatus(AssessmentMode mode) {
return sendEvent(mode, now(), true);
}
private void manageListenersOfCoordinatedMode(AssessmentMode mode) {
try {
Status status = mode.getStatus();
if(status == Status.leadtime || status == Status.assessment || status == Status.followup) {
//add listeners
CoordinatedAssessmentMode coordinateMode = coordinatedModes.get(mode.getKey());
if(coordinateMode == null) {
coordinateMode = new CoordinatedAssessmentMode(mode);
coordinatedModes.put(mode.getKey(), coordinateMode);
}
coordinatorManager.getCoordinator().getEventBus()
.registerFor(this, null, coordinateMode.getListenerRes());
} else if(coordinatedModes.containsKey(mode.getKey())) {
CoordinatedAssessmentMode decoordinateMode = coordinatedModes.remove(mode.getKey());
if(decoordinateMode != null) {
coordinatorManager.getCoordinator().getEventBus()
.deregisterFor(this, decoordinateMode.getListenerRes());
}
}
} catch (Exception e) {
log.error("", e);
}
}
@Override
public void event(Event event) {
if(event instanceof BusinessGroupModifiedEvent) {
try {
BusinessGroupModifiedEvent mod = (BusinessGroupModifiedEvent)event;
if(BusinessGroupModifiedEvent.IDENTITY_ADDED_EVENT.equals(mod.getCommand())) {
Long identityKey = mod.getAffectedIdentityKey();
sendEventAfterMembershipChange(identityKey);
}
} catch (Exception e) {
log.error("", e);
}
}
}
private void sendEventAfterMembershipChange(final Long identityKey) {
List<AssessmentMode> modes = assessmentModeManager.getAssessmentModeFor(new IdentityRefImpl(identityKey));
for(AssessmentMode mode:modes) {
Status status = mode.getStatus();
if(status == Status.leadtime ) {
sendEvent(AssessmentModeNotificationEvent.LEADTIME, mode,
assessmentModeManager.getAssessedIdentityKeys(mode));
} else if(status == Status.assessment) {
sendEvent(AssessmentModeNotificationEvent.START_ASSESSMENT, mode,
assessmentModeManager.getAssessedIdentityKeys(mode));
} else if(status == Status.followup) {
sendEvent(AssessmentModeNotificationEvent.END, mode,
assessmentModeManager.getAssessedIdentityKeys(mode));
}
}
}
@Override
public Status evaluateStatus(Date begin, int leadtime, Date end, int followup) {
Status status;
Date now = now();
Date beginWithLeadTime = assessmentModeManager.evaluateLeadTime(begin, leadtime);
Date endWithFollowupTime = assessmentModeManager.evaluateFollowupTime(end, followup);
if(beginWithLeadTime.compareTo(now) > 0) {
status = Status.none;
} else if(beginWithLeadTime.compareTo(now) <= 0 && begin.compareTo(now) > 0 && !beginWithLeadTime.equals(begin)) {
status = Status.leadtime;
} else if(begin.compareTo(now) <= 0 && end.compareTo(now) > 0) {
status = Status.assessment;
} else if(end.compareTo(now) <= 0 && endWithFollowupTime.compareTo(now) > 0) {
if(followup > 0) {
status = Status.followup;
} else {
status = Status.end;
}
} else if(endWithFollowupTime.compareTo(now) <= 0) {
status = Status.end;
} else {
status = null;
}
return status;
}
private AssessmentMode sendEvent(AssessmentMode mode, Date now, boolean forceStatus) {
if(mode.getBeginWithLeadTime().compareTo(now) > 0) {
//none
Status status = mode.getStatus();
if(status != Status.leadtime && status != Status.assessment && status != Status.followup && status != Status.end) {
mode = ensureStatusOfMode(mode, Status.none);
sendEvent(AssessmentModeNotificationEvent.BEFORE, mode,
assessmentModeManager.getAssessedIdentityKeys(mode));
}
} else if(mode.getBeginWithLeadTime().compareTo(now) <= 0 && mode.getBegin().compareTo(now) > 0
&& mode.getBeginWithLeadTime().compareTo(mode.getBegin()) != 0) {
//leading time
Status status = mode.getStatus();
if(status != Status.assessment && status != Status.followup && status != Status.end) {
mode = ensureStatusOfMode(mode, Status.leadtime);
sendEvent(AssessmentModeNotificationEvent.LEADTIME, mode,
assessmentModeManager.getAssessedIdentityKeys(mode));
}
} else if(mode.isManualBeginEnd() && !forceStatus) {
//what to do in manual mode
if(mode.getStatus() == Status.followup) {
if(mode.getEndWithFollowupTime().compareTo(now) < 0) {
mode = ensureStatusOfMode(mode, Status.end);
sendEvent(AssessmentModeNotificationEvent.END, mode,
assessmentModeManager.getAssessedIdentityKeys(mode));
}
}
} else {
if(mode.getBegin().compareTo(now) <= 0 && mode.getEnd().compareTo(now) > 0) {
mode = ensureStatusOfMode(mode, Status.assessment);
sendEvent(AssessmentModeNotificationEvent.START_ASSESSMENT, mode,
assessmentModeManager.getAssessedIdentityKeys(mode));
//message 5 minutes before end
Calendar cal = Calendar.getInstance();
cal.setTime(mode.getEnd());
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
cal.add(Calendar.MINUTE, -6);
if(now.after(cal.getTime())) {
sendEvent(AssessmentModeNotificationEvent.STOP_WARNING, mode, null);
}
} else if(mode.getEnd().compareTo(now) <= 0 && mode.getEndWithFollowupTime().compareTo(now) > 0) {
if(mode.getFollowupTime() > 0) {
mode = ensureStatusOfMode(mode, Status.followup);
sendEvent(AssessmentModeNotificationEvent.STOP_ASSESSMENT, mode,
assessmentModeManager.getAssessedIdentityKeys(mode));
} else {
mode = ensureStatusOfMode(mode, Status.end);
sendEvent(AssessmentModeNotificationEvent.END, mode,
assessmentModeManager.getAssessedIdentityKeys(mode));
}
} else if(mode.getEndWithFollowupTime().compareTo(now) <= 0) {
mode = ensureStatusOfMode(mode, Status.end);
sendEvent(AssessmentModeNotificationEvent.END, mode,
assessmentModeManager.getAssessedIdentityKeys(mode));
}
}
manageListenersOfCoordinatedMode(mode);
return mode;
}
private AssessmentMode ensureStatusOfMode(AssessmentMode mode, Status status) {
Status currentStatus = mode.getStatus();
if(currentStatus == null || currentStatus != status) {
mode.setStatus(status);
mode = dbInstance.getCurrentEntityManager().merge(mode);
if(status == Status.leadtime || status == Status.assessment) {
warmUpAssessment(mode);
}
dbInstance.commit();
}
return mode;
}
private void sendEvent(String cmd, AssessmentMode mode, Set<Long> assessedIdentityKeys) {
TransientAssessmentMode transientMode = new TransientAssessmentMode(mode);
AssessmentModeNotificationEvent event = new AssessmentModeNotificationEvent(cmd, transientMode, assessedIdentityKeys);
coordinatorManager.getCoordinator().getEventBus()
.fireEventToListenersOf(event, AssessmentModeNotificationEvent.ASSESSMENT_MODE_NOTIFICATION);
}
@Override
public boolean canStart(AssessmentMode assessmentMode) {
boolean canStart;
Status status = assessmentMode.getStatus();
if(status == Status.assessment || status == Status.followup || status == Status.end) {
canStart = false;
} else {
canStart = true;
}
return canStart;
}
@Override
public boolean canStop(AssessmentMode assessmentMode) {
boolean canStop;
Status status = assessmentMode.getStatus();
if(status == Status.leadtime || status == Status.assessment) {
canStop = true;
} else {
canStop = false;
}
return canStop;
}
@Override
public AssessmentMode startAssessment(AssessmentMode mode) {
mode = assessmentModeManager.getAssessmentModeById(mode.getKey());
mode = ensureStatusOfMode(mode, Status.assessment);
Set<Long> assessedIdentityKeys = assessmentModeManager.getAssessedIdentityKeys(mode);
sendEvent(AssessmentModeNotificationEvent.START_ASSESSMENT, mode, assessedIdentityKeys);
return mode;
}
@Override
public AssessmentMode stopAssessment(AssessmentMode mode) {
mode = assessmentModeManager.getAssessmentModeById(mode.getKey());
Set<Long> assessedIdentityKeys = assessmentModeManager.getAssessedIdentityKeys(mode);
if(mode.getFollowupTime() > 0) {
Date now = new Date();
Date followupTime = assessmentModeManager.evaluateFollowupTime(now, mode.getFollowupTime());
((AssessmentModeImpl)mode).setEnd(now);
((AssessmentModeImpl)mode).setEndWithFollowupTime(followupTime);
mode.setStatus(Status.followup);
mode = dbInstance.getCurrentEntityManager().merge(mode);
dbInstance.commit();
sendEvent(AssessmentModeNotificationEvent.STOP_ASSESSMENT, mode, assessedIdentityKeys);
} else {
mode = ensureStatusOfMode(mode, Status.end);
sendEvent(AssessmentModeNotificationEvent.END, mode, assessedIdentityKeys);
}
return mode;
}
private void warmUpAssessment(AssessmentMode mode) {
RepositoryEntry entry = repositoryService.loadByKey(mode.getRepositoryEntry().getKey());
CourseFactory.loadCourse(entry);
}
}