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

org.sakaiproject.samigo.impl.SamigoETSProviderImpl Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2015, The Apereo 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/ecl2
 *
 * 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.samigo.impl;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.mail.internet.InternetAddress;

import lombok.extern.slf4j.Slf4j;
import lombok.Setter;

import org.apache.commons.lang3.StringUtils;

import org.sakaiproject.authz.api.AuthzGroup;
import org.sakaiproject.authz.api.AuthzGroupService;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.email.api.EmailService;
import org.sakaiproject.email.api.DigestService;
import org.sakaiproject.emailtemplateservice.api.EmailTemplateService;
import org.sakaiproject.emailtemplateservice.api.RenderedTemplate;
import org.sakaiproject.entity.api.EntityPropertyNotDefinedException;
import org.sakaiproject.entity.api.EntityPropertyTypeException;
import org.sakaiproject.entity.api.ResourceProperties;
import org.sakaiproject.event.api.Event;
import org.sakaiproject.event.api.NotificationService;
import org.sakaiproject.samigo.api.SamigoETSProvider;
import org.sakaiproject.samigo.util.SamigoConstants;
import org.sakaiproject.site.api.Group;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.tool.assessment.facade.PublishedAssessmentFacade;
import org.sakaiproject.tool.assessment.services.assessment.PublishedAssessmentService;
import org.sakaiproject.user.api.Preferences;
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;

@Slf4j
public class SamigoETSProviderImpl implements SamigoETSProvider {
    private         final   Map  constantValues                      = new HashMap<>();
    private         final   String              MULTIPART_BOUNDARY                  = "======sakai-multi-part-boundary======";
    private         final   String              BOUNDARY_LINE                       = "\n\n--"+MULTIPART_BOUNDARY+"\n";
    private         final   String              TERMINATION_LINE                    = "\n\n--"+MULTIPART_BOUNDARY+"--\n\n";
    private         final   String              MIME_ADVISORY                       = "This message is for MIME-compliant mail readers.";
    private static  final   String              ADMIN                               = "admin";
    private                 String              fromAddress                         = "";
    private static  final   ResourceLoader      RB                                  = new ResourceLoader("EmailNotificationMessages");
    private static  final   String              CHANGE_SETTINGS_HOW_TO_INSTRUCTOR   = RB.getString("changeSetting_instructor");
    private static  final   String              CHANGE_SETTINGS_HOW_TO_STUDENT      = RB.getString("changeSetting_student");

    public      void                init                                () {
        log.info("init()");

        fromAddress = serverConfigurationService.getSmtpFrom();

        constantValues.put("localSakaiName" , serverConfigurationService.getString("ui.service", "Sakai"));
        constantValues.put("localSakaiUrl"  , serverConfigurationService.getPortalUrl());

        // Register XML file email templates with the service
        ClassLoader cl = SamigoETSProviderImpl.class.getClassLoader();
        emailTemplateService.importTemplateFromXmlFile(cl.getResourceAsStream(SamigoConstants.EMAIL_TEMPLATE_ASSESSMENT_SUBMITTED_FILE_NAME), SamigoConstants.EMAIL_TEMPLATE_ASSESSMENT_SUBMITTED);
        emailTemplateService.importTemplateFromXmlFile(cl.getResourceAsStream(SamigoConstants.EMAIL_TEMPLATE_ASSESSMENT_AUTO_SUBMITTED_FILE_NAME), SamigoConstants.EMAIL_TEMPLATE_ASSESSMENT_AUTO_SUBMITTED);
        emailTemplateService.importTemplateFromXmlFile(cl.getResourceAsStream(SamigoConstants.EMAIL_TEMPLATE_ASSESSMENT_TIMED_SUBMITTED_FILE_NAME), SamigoConstants.EMAIL_TEMPLATE_ASSESSMENT_TIMED_SUBMITTED);
        emailTemplateService.importTemplateFromXmlFile(cl.getResourceAsStream(SamigoConstants.EMAIL_TEMPLATE_AUTO_SUBMIT_ERRORS_FILE_NAME), SamigoConstants.EMAIL_TEMPLATE_AUTO_SUBMIT_ERRORS);
        emailTemplateService.importTemplateFromXmlFile(cl.getResourceAsStream(SamigoConstants.EMAIL_TEMPLATE_ASSESSMENT_AVAILABLE_FILE_NAME), SamigoConstants.EMAIL_TEMPLATE_ASSESSMENT_AVAILABLE_REMINDER);
    }

    public      void                notify                              (String eventKey, Map notificationValues, Event event) {
        log.debug("Notify, templateKey: " + eventKey + " event: " + event.toString());
        switch(eventKey){
            case SamigoConstants.EVENT_ASSESSMENT_SUBMITTED:
                handleAssessmentSubmitted(notificationValues, event);
                break;
            case SamigoConstants.EVENT_ASSESSMENT_SUBMITTED_AUTO:
                handleAssessmentAutoSubmitted(notificationValues, event);
                break;
            case SamigoConstants.EVENT_ASSESSMENT_SUBMITTED_TIMER_THREAD:
                handleAssessmentTimedSubmitted(notificationValues, event);
                break;
        }
    }

    public      void                notifyAutoSubmitFailures            (int count) {
        boolean notifyOn = serverConfigurationService.getBoolean(SamigoConstants.SAK_PROP_AUTO_SUBMIT_ERROR_NOTIFICATION_ENABLED, false);
        if (!notifyOn) {
            return;
        }
        String supportAddress = serverConfigurationService.getString(SamigoConstants.SAK_PROP_SUPPORT_EMAIL_ADDRESS, fromAddress);
        String toAddress = serverConfigurationService.getString(SamigoConstants.SAK_PROP_AUTO_SUBMIT_ERROR_NOTIFICATION_TO_ADDRESS, supportAddress);

        Map replacementValues = new HashMap<>();
        replacementValues.put("failureCount", Integer.toString(count));

        try {
            RenderedTemplate template = emailTemplateService.getRenderedTemplate(SamigoConstants.EMAIL_TEMPLATE_AUTO_SUBMIT_ERRORS, Locale.getDefault(), replacementValues);
            if (template == null) {
                throw new IllegalStateException("Template is null");
            }
            InternetAddress[] to = { new InternetAddress(toAddress) };
            emailService.sendMail(new InternetAddress(fromAddress), to, template.getRenderedSubject(), template.getRenderedMessage(), to, null, null);
        }
        catch (Exception e) {
            log.error("Unable to send email notification for AutoSubmit failures.", e);
        }
    }

    private     void                handleAssessmentSubmitted           (Map notificationValues, Event event) {
        log.debug("Assessment Submitted");
        assessmentSubmittedHelper(notificationValues, event, 1);

    }

    private     void                handleAssessmentAutoSubmitted       (Map notificationValues, Event event){
        log.debug("Assessment Auto Submitted");
        assessmentSubmittedHelper(notificationValues, event, 2);
    }

    private     void                handleAssessmentTimedSubmitted      (Map notificationValues, Event event){
        log.debug("Assessment Timed Submitted");
        assessmentSubmittedHelper(notificationValues, event, 3);
    }

    /*
     * assessmentSubmittedType is an int.
     * 1 = Normal Submission
     * 2 = Auto Submission
     * 3 = Timer expired Submission
     */
    private     void                assessmentSubmittedHelper           (Map notificationValues, Event event, int assessmentSubmittedType){
        log.debug("assessment Submitted helper, assessmentSubmittedType: " + assessmentSubmittedType);
        String              priStr                  = Integer.toString(event.getPriority());
        Map replacementValues       = new HashMap<>(constantValues);
        try {
            User            user                    = userDirectoryService.getUser(notificationValues.get("userID").toString());

            PublishedAssessmentService pubAssServ   = new PublishedAssessmentService();
            PublishedAssessmentFacade  pubAssFac    = pubAssServ.getSettingsOfPublishedAssessment(notificationValues.get("publishedAssessmentID").toString());

            // Format dates
            DateFormat dfIn                         = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
            DateFormat dfIn2                        = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
            DateFormat dfOut                        = new SimpleDateFormat("yyyy-MMM-dd hh:mm a", preferencesService.getLocale(user.getId()));
            String formattedDueDate                 = (pubAssFac.getDueDate() == null) ? "" : dfOut.format(pubAssFac.getDueDate());
            Date submissionDate                     = null;
            String inSubmissionDateStr              = (notificationValues.get("submissionDate") == null) ? "" : notificationValues.get("submissionDate").toString();
            try {
               submissionDate = dfIn.parse(inSubmissionDateStr);
            } catch (java.text.ParseException e) {
               log.debug("Submission date parse exception (first format): " + e.getMessage());
            }
            if (submissionDate == null) {
               try {
                  submissionDate = dfIn2.parse(inSubmissionDateStr);
               } catch (java.text.ParseException e) {
                  log.debug("Submission date parse exception (second format): " + e.getMessage());
               }
            }
            String formattedSubmissionDate          = (submissionDate == null) ? inSubmissionDateStr : dfOut.format(submissionDate);

            String          siteID                  = pubAssFac.getOwnerSiteId();
            /*
             * siteName
             * siteID
             * userName
             * userDisplayID
             * assessmentTitle
             * assessmentDueDate
             * assessmentGradingID
             * submissionDate
             * confirmationNumber
             * releaseToGroups
             */
            replacementValues.put("siteName"            , pubAssFac.getOwnerSite());
            replacementValues.put("siteID"              , siteID);
            replacementValues.put("userName"            , user.getDisplayName());
            replacementValues.put("userDisplayID"       , user.getDisplayId());
            replacementValues.put("assessmentTitle"     , pubAssFac.getTitle());
            replacementValues.put("assessmentDueDate"   , formattedDueDate);
            replacementValues.put("assessmentGradingID" , notificationValues.get("assessmentGradingID").toString());
            replacementValues.put("submissionDate"      , formattedSubmissionDate);
            replacementValues.put("confirmationNumber"  , notificationValues.get("confirmationNumber").toString());
            
            if (notificationValues.get("releaseToGroups") != null){
            	replacementValues.put("releaseToGroups", notificationValues.get("releaseToGroups").toString());
            }            

            notifyStudent(user, priStr, assessmentSubmittedType, replacementValues);
            notifyInstructor(siteID, pubAssFac.getInstructorNotification(), assessmentSubmittedType, user, replacementValues);
        } catch(UserNotDefinedException e){
            log.warn("UserNotDefined: " + notificationValues.get("userID").toString() + " in sending samigo notification.");
        }
    }

    private     void                notifyInstructor                    (String siteID, Integer instructNoti, int assessmentSubmittedType,
                                                                            User submittingUser, Map replacementValues){
        log.debug("notifyInstructor");
        replacementValues.put("changeSettingInstructions" , CHANGE_SETTINGS_HOW_TO_INSTRUCTOR);

        List  validUsers                      = new ArrayList<>();
        RenderedTemplate    rt                      = getRenderedTemplateBySubmissionType(assessmentSubmittedType, submittingUser, replacementValues);
        String              message                 = getBody(rt);

        try{
            Site            site                    = siteService.getSite(siteID);
            Set     siteUsersHasRole        = new HashSet<>();
            
            if (replacementValues.get("releaseToGroups") != null){
            	siteUsersHasRole = extractInstructorsFromGroups(site, (String) replacementValues.get("releaseToGroups") );
            }

            if (siteUsersHasRole.isEmpty()) {
            	AuthzGroup azGroup = authzGroupService.getAuthzGroup("/site/" + siteID);
            	siteUsersHasRole = site.getUsersHasRole(azGroup.getMaintainRole());
            }

            for(String userString : siteUsersHasRole){
                try{
                    if(!userString.equals(ADMIN)) {
                        User user = userDirectoryService.getUser(userString);
                        validUsers.add(user);
                    }
                } catch(UserNotDefinedException e){
                    log.warn("Instructor '" + userString +"' not found in samigo notification.");
                }
            }
        } catch(org.sakaiproject.exception.IdUnusedException e){
            //Site not found
            log.warn("Site '{}' not found while sending instructor notifications for samigo submission.", siteID);
            log.debug(e.getMessage(), e);
        } catch(org.sakaiproject.authz.api.GroupNotDefinedException e){
            // Realm not found
            log.warn("AuthzGroup '/site/{}' not found while sending instructor notifications for samigo submission", siteID);
            log.debug(e.getMessage(), e);
        }

        List          immediateUsers          = new ArrayList<>();

        if(validUsers.size() > 0){
            List        headers         = getHeaders(rt, validUsers, constantValues.get("localSakaiName"), fromAddress);

            for(User user : validUsers){
                if(instructNoti == NotificationService.PREF_IMMEDIATE){
                    immediateUsers.add(user);
                } else if(instructNoti == NotificationService.PREF_DIGEST){
                    log.debug("notifyInstructor + sendDigest + User: " + user.getDisplayName() + " rt: " + rt.getKey());
                    digestService.digest(user.getId(), rt.getRenderedSubject(), rt.getRenderedMessage());
                }
            }

            if(instructNoti == NotificationService.PREF_IMMEDIATE && !immediateUsers.isEmpty()) {
                log.debug("notifyInstructor + send one email to Users: " + immediateUsers.toString() +" rt: " + rt.getKey());
                emailService.sendToUsers(immediateUsers, headers, message);
            }
        }
    }
    
    private Set extractInstructorsFromGroups(Site site,String allGroups){

        Set usersWithRole = new HashSet<>();

        List groups = Stream.of(allGroups)
                .map(s -> s.split(";")).flatMap(Arrays::stream)
                .collect(Collectors.toList());

        for (String groupId : groups){
            Group group = site.getGroup(groupId);
            Set  groupUsersWithRole = group.getUsersHasRole(group.getMaintainRole());
            usersWithRole.addAll(groupUsersWithRole);
        }

        return usersWithRole;
    }

    private     void                notifyStudent                       (User user, String priStr, int assessmentSubmittedType,
                                                                            Map replacementValues){
        log.debug("notifyStudent");
        replacementValues.put( "changeSettingInstructions" , CHANGE_SETTINGS_HOW_TO_STUDENT );

        List          users                   = new ArrayList<>();
        RenderedTemplate    rt                      = getRenderedTemplateBySubmissionType( assessmentSubmittedType, user, replacementValues );
        String              message                 = getBody( rt );
        users.add(user);

        List        headers                 = getHeaders(rt, users, constantValues.get("localSakaiName"), fromAddress);
        int                 uSamEmailPref           = getUserPreferences(user, priStr);

        if(uSamEmailPref == NotificationService.PREF_IMMEDIATE){
            log.debug("notifyStudent + send one email + rt: " + rt.getKey());
            emailService.sendToUsers(users, headers, message);
        } else if (uSamEmailPref == NotificationService.PREF_DIGEST){
            log.debug("notifyStudent + sendDigest + rt: " + rt.getKey());
            digestService.digest(user.getId(), rt.getRenderedSubject(), rt.getRenderedMessage());
        }
    }

    private     int                 getUserPreferences                  (User user, String priStr){
        log.debug("getUserPreferences User: " + user.getDisplayName());
        int                 uSamEmailPref           = SamigoConstants.NOTI_PREF_DEFAULT;

        Preferences         userPrefs               = preferencesService.getPreferences(user.getId());
        ResourceProperties  props                   = userPrefs.getProperties(NotificationService.PREFS_TYPE + SamigoConstants.NOTI_PREFS_TYPE_SAMIGO);

        try{
            uSamEmailPref                           = (int) props.getLongProperty(priStr);
        } catch (EntityPropertyNotDefinedException | EntityPropertyTypeException e){
            //User hasn't changed preference
        }
        log.debug("getUserPreferences: pref=" + uSamEmailPref);
        return uSamEmailPref;
    }

    private     String              getBody                             (RenderedTemplate rt){
        log.debug("getBody");
        StringBuilder       body                = new StringBuilder();
        body.append(MIME_ADVISORY);

        if (rt.getRenderedMessage() != null) {
            body.append(BOUNDARY_LINE);
            body.append("Content-Type: text/plain; charset=UTF-8\n");
            body.append(rt.getRenderedMessage());
        }
        if (rt.getRenderedHtmlMessage() != null) {
            //append the HMTL part
            body.append(BOUNDARY_LINE);
            body.append("Content-Type: text/html; charset=UTF-8\n");
            body.append(rt.getRenderedHtmlMessage());
        }

        body.append(TERMINATION_LINE);

        return body.toString();
    }

    private     RenderedTemplate    getRenderedTemplateBySubmissionType (int assessmentSubmittedType, User user, Map replacementValues){
        RenderedTemplate template;
        switch(assessmentSubmittedType){
            case 2:
                template = getRenderedTemplate(SamigoConstants.EMAIL_TEMPLATE_ASSESSMENT_AUTO_SUBMITTED, user, replacementValues);
                break;
            case 3:
                template = getRenderedTemplate(SamigoConstants.EMAIL_TEMPLATE_ASSESSMENT_TIMED_SUBMITTED, user, replacementValues);
                break;
            default:
                template = getRenderedTemplate(SamigoConstants.EMAIL_TEMPLATE_ASSESSMENT_SUBMITTED, user, replacementValues);
                break;
        }

        return template;
    }

    private     RenderedTemplate    getRenderedTemplate                 (String templateName, User user, Map replacementValues){
        log.debug("getting template: " + templateName);
        RenderedTemplate    template            = null;

        try {
            template = emailTemplateService.getRenderedTemplateForUser(templateName, user!=null?user.getReference():"", replacementValues);
        }catch (Exception e) {
            log.warn("Samigo Notification email template error. " + this + e.getMessage());
        }

        return template;
    }

    // Based on EmailTemplateService.sendRenderedMessages()
    private     List        getHeaders                          (RenderedTemplate rt, List toAddress, String fromName, String fromEmail){
        log.debug("getHeaders");
        List        headers             = new ArrayList<>();
        //the template may specify a from address
        if (StringUtils.isNotBlank(rt.getFrom())) {
            headers.add("From: \"" + rt.getFrom() );
        } else {
            headers.add("From: \"" + fromName + "\" <" + fromEmail + ">" );
        }
        // Add a To: header of either the recipient (if only 1), or the sender (if multiple)
        String              toName                  = fromName;
        String              toEmail                 = fromEmail;

        if (toAddress.size() == 1) {
            User u = toAddress.get(0);
            toName = u.getDisplayName();
            toEmail = u.getEmail();
        }

        headers.add("To: \"" + toName + "\" <" + toEmail + ">" );

        //SAK-21742 we need the rendered subject
        headers.add("Subject: " + rt.getRenderedSubject());
        headers.add("Content-Type: multipart/alternative; boundary=\"" + MULTIPART_BOUNDARY + "\"");
        headers.add("Mime-Version: 1.0");
        headers.add("Return-Path: <>");
        headers.add("Auto-Submitted: auto-generated");

        return headers;
    }

    @Setter
    private                         ServerConfigurationService  serverConfigurationService;

    @Setter
    private                         UserDirectoryService        userDirectoryService;

    @Setter
    private                         EmailTemplateService        emailTemplateService;

    @Setter
    private                         PreferencesService          preferencesService;

    @Setter
    private                         EmailService                emailService;

    @Setter
    private                         DigestService               digestService;

    @Setter
    private                         SiteService                 siteService;

    @Setter
    private                         AuthzGroupService           authzGroupService;
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy