All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sakaiproject.tool.assessment.TestsAndQuizzesUserNotificationHandler Maven / Gradle / Ivy

package org.sakaiproject.tool.assessment;

import java.time.Instant;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.SessionFactory;
import org.sakaiproject.authz.api.AuthzGroupService;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.event.api.Event;
import org.sakaiproject.event.api.EventTrackingService;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.messaging.api.UserNotificationData;
import org.sakaiproject.messaging.api.AbstractUserNotificationHandler;
import org.sakaiproject.samigo.util.SamigoConstants;
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.assessment.data.dao.assessment.ExtendedTime;
import org.sakaiproject.tool.assessment.data.ifc.assessment.AssessmentAccessControlIfc;
import org.sakaiproject.tool.assessment.facade.ExtendedTimeFacade;
import org.sakaiproject.tool.assessment.facade.PublishedAssessmentFacade;
import org.sakaiproject.tool.assessment.services.PersistenceService;
import org.sakaiproject.tool.assessment.services.assessment.PublishedAssessmentService;
import org.sakaiproject.user.api.User;
import org.sakaiproject.user.api.UserDirectoryService;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;

import static org.sakaiproject.samigo.util.SamigoConstants.*;



/*
 *   Note: Restoring of soft deleted Assessments does not recreate bullhorn alerts!
 */

@Slf4j
@Component
public class TestsAndQuizzesUserNotificationHandler extends AbstractUserNotificationHandler {

    public static Pattern idPattern = Pattern.compile("siteId=(\\S*),\\s*\\S*\\s*publishedAssessmentId=(\\S*)", Pattern.CASE_INSENSITIVE);
    private PublishedAssessmentService publishedAssessmentService = new PublishedAssessmentService();

    @Resource
    private SiteService siteService;

    @Resource
    private AuthzGroupService authzGroupService;

    @Resource
    private SessionManager sessionManager;

    @Resource
    private UserDirectoryService userDirectoryService;

    @Resource(name = "org.sakaiproject.springframework.orm.hibernate.GlobalTransactionManager")
    private PlatformTransactionManager transactionManager;
    @Resource(name = "org.sakaiproject.springframework.orm.hibernate.GlobalSessionFactory")
    private SessionFactory sessionFactory;
    private EventTrackingService eventTrackingService;


    public TestsAndQuizzesUserNotificationHandler(){
        super();
        eventTrackingService = ComponentManager.get(EventTrackingService.class);
    }

    @Override
    public List getHandledEvents() {
        return Arrays.asList(EVENT_ASSESSMENT_AVAILABLE, EVENT_PUBLISHED_ASSESSMENT_RETRACTED, EVENT_ASSESSMENT_UPDATE_AVAILABLE,EVENT_ASSESSMENT_DELETE, EVENT_PUBLISHED_ASSESSMENT_REMOVE);
    }


    @Override
    public Optional> handleEvent(Event e) {

        String from = e.getUserId();
        String ref = e.getResource();


        List refParts = regexHelper(ref);
        String siteId = refParts.get(0);
        String publishedAssessmentId = refParts.get(1);

        PublishedAssessmentFacade pub = null;
        try {
            pub = publishedAssessmentService.getPublishedAssessment(publishedAssessmentId, true);
        }catch(Exception e1){
            log.error(e1.getMessage());
        }
        String releaseTo = pub.getAssessmentAccessControl().getReleaseTo();

        ExtendedTimeFacade extendedTimeFacade = PersistenceService.getInstance().getExtendedTimeFacade();
        List extendedTimes = extendedTimeFacade.getEntriesForPub(pub.getData());

        Map selectedGroups = pub.getReleaseToGroups();


        if (!releaseTo.equals("Anonymous Users")) {
            try {
                switch (e.getEvent()) {
                    case EVENT_ASSESSMENT_AVAILABLE:
                        checkForDelays(pub, extendedTimes,siteId, e.getUserId());
                        return Optional.of(handleAdd(from,ref, siteId, pub, extendedTimes, selectedGroups, releaseTo));
                    case EVENT_ASSESSMENT_UPDATE_AVAILABLE:
                        checkForDelays(pub, extendedTimes,siteId,e.getUserId());
                        return Optional.of(handleUpdate(from, ref, siteId,pub, extendedTimes, selectedGroups, releaseTo));
                    case EVENT_PUBLISHED_ASSESSMENT_RETRACTED:
                    case EVENT_ASSESSMENT_DELETE:
                    case EVENT_PUBLISHED_ASSESSMENT_REMOVE:
                        return Optional.of(deleteAlerts(siteId, pub));
                    default:
                        return Optional.empty();
                }
            } catch (Exception ex) {
                log.error("Failed to handleEvent for Test&Quizzes userNotification alert", ex);
            }
        }
        return Optional.empty();
    }


    private List handleAdd(String from, String ref ,String siteId, PublishedAssessmentFacade assignment, List extendedTimes,  Map selectedGroups, String releaseTo)
            throws Exception {
        List bhEvents = new ArrayList<>();

        Instant startDateInstant = assignment.getStartDate().toInstant();
        Site site = siteService.getSite(siteId);
        String title = assignment.getTitle();

        Collection groupsUsers = null;

        if (releaseTo.equals(AssessmentAccessControlIfc.RELEASE_TO_SELECTED_GROUPS)) {
            groupsUsers =  getUsersInSelectedGroups(siteId,  selectedGroups);
        }

        Set userUids = site.getUsersIsAllowed(AUTHZ_TAKE_ASSESSMENT);
        for (User u : userDirectoryService.getUsers(userUids)) {
            String to = u.getId();
            //  If this is a grouped assignment, is 'to' in one of the groups?
            if ((releaseTo.equals(site.getTitle()) || (groupsUsers != null && groupsUsers.contains(to))) && (!from.equals(to) && !securityService.isSuperUser(to)) && checkTime(startDateInstant,extendedTimes, to, siteId) && !bhAlreadyExistsForUser(ref, to)) {
                //link to tool
                String url = site.getUrl() + "/tool/" + site.getToolForCommonId("sakai.samigo").getId();
                bhEvents.add(new UserNotificationData(from, to, siteId, title, url));
            }
        }
        return bhEvents;
    }

    private List handleUpdate(String from, String ref, String siteId, PublishedAssessmentFacade assignment, List extendedTimes, Map selectedGroups, String releaseTo)
            throws Exception {
        Site site = siteService.getSite(siteId);
        Set siteUsers = site.getUsersIsAllowed(AUTHZ_TAKE_ASSESSMENT);

        Collection groupsUsers = null;

        if (releaseTo.equals(AssessmentAccessControlIfc.RELEASE_TO_SELECTED_GROUPS)) {
            groupsUsers =  getUsersInSelectedGroups(siteId,  selectedGroups);
        }

        if(!siteUsers.isEmpty()){
            try{
                TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
                transactionTemplate.execute(status -> {
                    sessionFactory.getCurrentSession().createQuery("delete UserNotification where EVENT in :events and REF = :ref and TO_USER in :toUsers")
                            .setParameterList("events", new String[]{EVENT_ASSESSMENT_AVAILABLE, EVENT_ASSESSMENT_UPDATE_AVAILABLE})
                            .setString("ref", ref)
                            .setParameterList("toUsers", siteUsers).executeUpdate();
                    return null;
                });
            }catch (Exception e2){
                log.error("failed to delete TestAndQuizzes userNotification alerts: " + e2.getMessage());
            }
        }

        Instant startDateInstant = assignment.getStartDate().toInstant();
        String title = assignment.getTitle();

        List bhEvents = new ArrayList<>();
        if (!releaseTo.equals("Anonymous Users")) {
            for (String to : siteUsers) {
                //  If this is a grouped assignment, is 'to' in one of the groups?
                if ((releaseTo.equals(siteId) || (groupsUsers != null && groupsUsers.contains(to))) && (!from.equals(to) && !securityService.isSuperUser(to)) && checkTime(startDateInstant, extendedTimes, to, siteId) && !bhAlreadyExistsForUser(ref, to)) {
                    //link to tool
                    String url = site.getUrl() + "/tool/" + site.getToolForCommonId("sakai.samigo").getId();
                    bhEvents.add(new UserNotificationData(from, to, siteId, title, url));
                }
            }
            return bhEvents;
        }
        return bhEvents;
    }

    private List deleteAlerts(String siteId, PublishedAssessmentFacade assignment)
            throws IdUnusedException {
        Site site = siteService.getSite(siteId);
        Set users = site.getUsersIsAllowed(AUTHZ_TAKE_ASSESSMENT);
        List bhEvents = new ArrayList<>();
        // Clean out all the alerts for the site  users.

        String ref = "siteId="+siteId+", assessmentId=" +assignment.getAssessmentId()+", publishedAssessmentId="+ assignment.getPublishedAssessmentId();
        if(!users.isEmpty()){
            try{
                TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
                transactionTemplate.execute(status -> {
                    sessionFactory.getCurrentSession().createQuery("delete UserNotification where EVENT in :events and REF = :ref and TO_USER in :toUsers")
                            .setParameterList("events", new String[]{EVENT_ASSESSMENT_AVAILABLE, EVENT_ASSESSMENT_UPDATE_AVAILABLE})
                            .setString("ref", ref)
                            .setParameterList("toUsers", users).executeUpdate();
                    return null;
                });
            }catch (Exception e3){
                log.error("failed to delete TestAndQuizzes UserNotificationData data " + e3.getMessage());
            }
        }


        eventTrackingService.cancelDelays(ref, EVENT_ASSESSMENT_AVAILABLE);
        return bhEvents;
    }

    /*
     *   user overrides group exception
     *   select first extended time of list for user exceptions; for multiple group exception the last is picked --> related to issues SAK-45277, SAK-44729
     */

    private boolean checkTime(Instant startDateInstant , List  extendedTimes, String to, String siteId){
        ListIterator times = extendedTimes.listIterator();
        boolean first = true;
        boolean result = true;
        boolean exTimeIsSet =false;

        while(times.hasNext()) {
            ExtendedTime exTime = (ExtendedTime) times.next();
            String user = exTime.getUser();

            Set set = new HashSet();
            set.add(getGroupRef(siteId, exTime.getGroup()));
            Collection groupUsers = authzGroupService.getAuthzUsersInGroups(set);


            Instant startInstant = exTime.getStartDate().toInstant();

            if (startInstant.isBefore(Instant.now()) && (( user != null && user.equals(to) && first) || (first && groupUsers != null && groupUsers.contains(to)))) {
                result = true;
                exTimeIsSet = true;
                if(StringUtils.isNotEmpty(user)){
                    first = false;
                }
            } else if (startInstant.isAfter(Instant.now()) && ((user != null && user.equals(to) && first) || (first && groupUsers != null && groupUsers.contains(to)))) {
                result = false;
                exTimeIsSet = true;
                if(StringUtils.isNotEmpty(user)){
                    first = false;
                }
            }
        }
        if(!exTimeIsSet && startDateInstant.isAfter(Instant.now())){
            result = false;
        }
        return result;
    }

    /*
     *  sakai allows only one active delay for a certain reference
     *  after first event is fired after publishing --> check if a new delay should be created
     */

    private void checkForDelays(PublishedAssessmentFacade  assignment,List extendedTimes,String siteId, String userId){
        Instant earliestDelayInstant = null;
        if(assignment.getStartDate().toInstant().isAfter(Instant.now())){
            earliestDelayInstant =  assignment.getStartDate().toInstant();
        }
        if (!extendedTimes.isEmpty()){
            for (ExtendedTime exTime : extendedTimes) {
                Instant exStartInstant = exTime.getStartDate().toInstant();
                if (exStartInstant.isAfter(Instant.now()) && (earliestDelayInstant != null && exStartInstant.isBefore(earliestDelayInstant))) {
                    earliestDelayInstant = exStartInstant;

                } else if (earliestDelayInstant != null && exStartInstant.isAfter(earliestDelayInstant)) {
                    //leave empty
                } else if (exStartInstant.isAfter(Instant.now())) {
                    earliestDelayInstant = exStartInstant;
                }
            }
        }
        if(earliestDelayInstant != null){
            // creating a new delay after an already delayed event, the sessionUserId is different from the original author of the assessment
            Session session = sessionManager.getCurrentSession();
            boolean flag = false;
            String tmpSessionUserId = null;
            if(!Objects.equals(session.getUserId(), userId)){
                tmpSessionUserId = session.getUserId();
                session.setUserId(userId);
                flag = true;
            }
            Event event = eventTrackingService.newEvent(EVENT_ASSESSMENT_AVAILABLE, "siteId=" + siteId + ", assessmentId=" + assignment.getAssessmentId() + ", publishedAssessmentId=" + assignment.getPublishedAssessmentId(), true);
            eventTrackingService.delay(event, earliestDelayInstant);
            if(flag){
                session.setUserId(tmpSessionUserId);
            }
        }
    }

    private boolean bhAlreadyExistsForUser(String ref, String toUser){
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        return transactionTemplate.execute(status -> {
            Long bhWithRef = (Long) sessionFactory.getCurrentSession()
                    .createQuery("select count(*) from UserNotification where ref = :ref and event = :event and toUser = :toUser")
                    .setString("ref", ref).setString("event", EVENT_ASSESSMENT_AVAILABLE).setString("toUser", toUser).uniqueResult();
            return bhWithRef > 0;
        });
    }

    private  Collection removeDuplicates(Collection list){
        ArrayList newList = new ArrayList();
        for (T element : list) {
            if (!newList.contains(element)) {
                newList.add(element);
            }
        }
        return newList;
    }

    private String getGroupRef(String siteId, String groupId){
        return  "/site/" + siteId + "/group/" +  groupId;
    }

    private Collection getUsersInSelectedGroups(String siteId, Map selectedGroups){
        String[] groups = null;
        Set groupIds = new HashSet();
        Collection groupsUsers = null;

        for(Map.Entry entry: selectedGroups.entrySet()){
            String id = getGroupRef(siteId, entry.getKey());
            groupIds.add(id);
        }
        groupsUsers = authzGroupService.getAuthzUsersInGroups(groupIds);
        groupsUsers = removeDuplicates(groupsUsers);
        return groupsUsers;

    }

    public static List regexHelper(String ref){
        Matcher matcher = idPattern.matcher(ref);
        String  siteId = null;
        String publishedAssessmentId = null;
        List response = new ArrayList();

        while (matcher.find()) {
            if (matcher.group(1) != null) {
                siteId = matcher.group(1);
                response.add(siteId);
            }
            if (matcher.group(2) != null) {
                publishedAssessmentId = matcher.group(2);
                response.add(publishedAssessmentId);
            }
        }
        return response;

    }



}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy