/** * Licensed to Apereo under one or more contributor license * agreements. See the NOTICE file distributed with this work * for additional information regarding copyright ownership. * Apereo licenses this file to you under the Apache 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 the following location: * * http://www.apache.org/licenses/LICENSE-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.jasig.portlet.blackboardvcportlet.service.impl; import net.fortuna.ical4j.data.CalendarOutputter; import net.fortuna.ical4j.model.Calendar; import net.fortuna.ical4j.model.DateTime; import net.fortuna.ical4j.model.component.VEvent; import net.fortuna.ical4j.model.property.Description; import net.fortuna.ical4j.model.property.Location; import net.fortuna.ical4j.model.property.Status; import net.fortuna.ical4j.model.property.Uid; import org.apache.velocity.app.VelocityEngine; import org.jasig.portlet.blackboardvcportlet.data.ConferenceUser; import org.jasig.portlet.blackboardvcportlet.data.Session; import org.jasig.portlet.blackboardvcportlet.data.ConferenceUser.Roles; import org.jasig.portlet.blackboardvcportlet.service.MailTask; import org.jasig.portlet.blackboardvcportlet.service.MailTemplateService; import org.jasig.portlet.blackboardvcportlet.service.SessionService; import org.jasig.portlet.blackboardvcportlet.service.util.MailMessages; import org.jasig.portlet.blackboardvcportlet.service.util.MailSubstitutions; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ByteArrayResource; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.mail.javamail.MimeMessagePreparator; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.ui.velocity.VelocityEngineUtils; import javax.mail.internet.MimeMessage; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @Service("jasigMailTemplateService") public class MailTemplateServiceImpl implements BeanFactoryAware, MailTemplateService { protected Logger logger = LoggerFactory.getLogger(getClass()); final private Queue<MailTask> theQueue = new ConcurrentLinkedQueue<MailTask>(); private JavaMailSender mailSender; @SuppressWarnings("unused") private BeanFactory beanFactory; private VelocityEngine velocityEngine; private SessionService sessionService; private static final String DATE_FORMAT = "MM/dd/yyyy HH:mm z"; private static final DateTimeFormatter dateFormatter = DateTimeFormat.forPattern(DATE_FORMAT); private static final DateTimeZone timezone = DateTimeZone.forID("America/Chicago"); private String defaultFromAddress; @Value("${mail.sendMail}") private String sendMail; @Value("${mail.extParticipantMailMessage.subject}") private String externalParticipantSubject; @Value("${mail.intParticipantMailMessage.subject}") private String internalParticipantSubject; @Value("${mail.moderatorMailMessage.subject}") private String moderatorSubject; @Value("${mail.sessionDeletionMessage.subject}") private String sessionDeletionSubject; /** * Default Constructor */ public MailTemplateServiceImpl() { super(); } @Value("${mail.from}") public void setFrom(String from) { this.defaultFromAddress = from; } @Autowired public void setSessionService(SessionService service) { this.sessionService = service; } @Autowired public void setBeanFactory(BeanFactory bf) throws BeansException { beanFactory = bf; } @Autowired public void setJavaMailSender(JavaMailSender ms) { this.mailSender = ms; } @Autowired public void setVelocityEngine(VelocityEngine velocityEngine) { this.velocityEngine = velocityEngine; } /** * Clear the queue every 1 second after last completion */ @Scheduled(fixedDelay = 1000) private void clearQueue() { MailTask cur = theQueue.poll(); while (cur != null) { sendMail(cur); cur = theQueue.poll(); } } public void sendMail(final MailTask mt) { try { MimeMessagePreparator messagePreparator = new MimeMessagePreparator() { public void prepare(MimeMessage mimeMessage) throws Exception { MimeMessageHelper message = new MimeMessageHelper(mimeMessage,true); if(mt.getMeetingInvite() != null) { CalendarOutputter outputter = new CalendarOutputter(); ByteArrayOutputStream os = new ByteArrayOutputStream(); outputter.setValidating(false); outputter.output(mt.getMeetingInvite(), os); message.addAttachment("invite.ics", new ByteArrayResource(os.toByteArray())); } message.setFrom(mt.getFrom() != null ? mt.getFrom() : defaultFromAddress); if (mt.getTo() != null) { String[] toArray = mt.getTo().toArray(new String[mt.getTo().size()]); message.setTo(toArray); } if (mt.getSubject() != null) { message.setSubject(mt.getSubject()); } else { switch (mt.getTemplate()) { case MODERATOR: message.setSubject(moderatorSubject); break; case INTERNAL_PARTICIPANT: message.setSubject(internalParticipantSubject); break; case EXTERNAL_PARTICIPANT: message.setSubject(externalParticipantSubject); break; case SESSION_DELETION: message.setSubject(sessionDeletionSubject); break; default: message.setSubject(""); } } message.setText(buildEmailMessage(mt), false); } }; mailSender.send(messagePreparator); } catch (Exception e) { logger.error("Issue with sending email", e); } } @SuppressWarnings("unchecked") public String buildEmailMessage(final MailTask mailTask) { return VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, mailTask.getTemplate().getClassPathToTemplate(), "UTF-8", mailTask.getSubstitutions()); } @Override public void sendEmail(MailTask mailTask) { if("true".equalsIgnoreCase(sendMail)) theQueue.add(mailTask); } @Override public void buildAndSendSessionEmails(Session session, boolean isUpdate, boolean isFirstTime) { for(ConferenceUser moderator : sessionService.getSessionChairs(session)) { this.sendEmail(buildModeratorMailTask(moderator, session, isUpdate && !isFirstTime)); } for(ConferenceUser user : sessionService.getSessionNonChairs(session)) { this.sendEmail(buildParticipantMailTask(user, session, isUpdate && !isFirstTime)); } } @Override public MailTask buildModeratorMailTask(ConferenceUser moderator, Session session, boolean isUpdate) { List<String> emailList = new ArrayList<String>(); emailList.add(moderator.getEmail()); String userSessionUrl = sessionService.getOrCreateSessionUrl(moderator, session); //substitutions Map<String, String> substitutions = createBaseSubstitutionMap(session); substitutions.put(MailSubstitutions.DISPLAY_NAME.toString(), moderator.getDisplayName()); substitutions.put(MailSubstitutions.SESSION_USER_URL.toString(), userSessionUrl); substitutions.put(MailSubstitutions.SESSION_GUEST_URL.toString(), session.getGuestUrl()); substitutions.put(MailSubstitutions.SESSION_UPDATE_TEXT.toString(), isUpdate ? "**Time update for existing session.**" : ""); MailTask mt = new MailTask(emailList,substitutions,MailMessages.MODERATOR); mt.setMeetingInvite(buildIcsFile(session, moderator)); mt.setSubject(moderatorSubject + ": " + session.getSessionName()); return mt; } @Override public MailTask buildSwitchRolesEmail(ConferenceUser user, Session session, Roles newRole) { List<String> emailList = new ArrayList<String>(); emailList.add(user.getEmail()); String userSessionUrl = sessionService.getOrCreateSessionUrl(user, session); //substitutions Map<String, String> substitutions = createBaseSubstitutionMap(session); substitutions.put(MailSubstitutions.DISPLAY_NAME.toString(), user.getDisplayName()); substitutions.put(MailSubstitutions.SESSION_USER_URL.toString(), userSessionUrl); substitutions.put(MailSubstitutions.SESSION_GUEST_URL.toString(), session.getGuestUrl()); substitutions.put(MailSubstitutions.SESSION_CREATOR_EMAIL.toString(), session.getCreator().getEmail()); substitutions.put(MailSubstitutions.SESSION_CREATOR_NAME.toString(), session.getCreator().getDisplayName()); substitutions.put(MailSubstitutions.SESSION_UPDATE_TEXT.toString(), "*** Your role for this session has changed and the URL for the session has been updated ***"); MailTask mt; if(Roles.CHAIR.equals(newRole)) { mt = new MailTask(emailList,substitutions,MailMessages.MODERATOR); mt.setSubject(moderatorSubject + ": " + session.getSessionName()); } else { mt = new MailTask(emailList,substitutions,MailMessages.INTERNAL_PARTICIPANT); mt.setSubject(internalParticipantSubject + ": " + session.getSessionName()); } mt.setMeetingInvite(buildIcsFile(session, user)); return mt; } @Override public MailTask buildParticipantMailTask(ConferenceUser participant, Session session, boolean isUpdate) { List<String> emailList = new ArrayList<String>(); emailList.add(participant.getEmail()); //substitutions Map<String, String> substitutions = createBaseSubstitutionMap(session); substitutions.put(MailSubstitutions.DISPLAY_NAME.toString(), participant.getDisplayName()); substitutions.put(MailSubstitutions.SESSION_USER_URL.toString(), sessionService.getOrCreateSessionUrl(participant, session)); substitutions.put(MailSubstitutions.SESSION_CREATOR_EMAIL.toString(), session.getCreator().getEmail()); substitutions.put(MailSubstitutions.SESSION_CREATOR_NAME.toString(), session.getCreator().getDisplayName()); substitutions.put(MailSubstitutions.SESSION_UPDATE_TEXT.toString(), isUpdate ? "**Time update for existing session.**" : ""); MailTask mt = new MailTask(emailList,substitutions,MailMessages.EXTERNAL_PARTICIPANT); mt.setMeetingInvite(buildIcsFile(session, participant)); mt.setSubject(internalParticipantSubject + ": " + session.getSessionName()); return mt; } @Override public void buildAndSendCancelationMeetingEmail(Session session) { List <ConferenceUser> users = new ArrayList<ConferenceUser>(); users.addAll(sessionService.getSessionChairs(session)); users.addAll(sessionService.getSessionNonChairs(session)); for(ConferenceUser user : users) { this.sendEmail(buildCancellationNoticeMailTask(user, session)); } } @Override public MailTask buildCancellationNoticeMailTask(ConferenceUser user, Session session) { List<String> emailList = new ArrayList<String>(); emailList.add(user.getEmail()); //substitutions Map<String, String> substitutions = createBaseSubstitutionMap(session); substitutions.put(MailSubstitutions.DISPLAY_NAME.toString(), user.getDisplayName()); substitutions.put(MailSubstitutions.SESSION_CREATOR_EMAIL.toString(), session.getCreator().getEmail()); substitutions.put(MailSubstitutions.SESSION_CREATOR_NAME.toString(), session.getCreator().getDisplayName()); MailTask mt = new MailTask(emailList,substitutions,MailMessages.SESSION_DELETION); mt.setMeetingInvite(buildIcsFile(session, user, true)); mt.setSubject(sessionDeletionSubject + ": " + session.getSessionName()); return mt; } private Map<String, String> createBaseSubstitutionMap(Session session) { Map<String, String> substitutions = new HashMap<String, String>(); substitutions.put(MailSubstitutions.SESSION_NAME.toString(), session.getSessionName()); substitutions.put(MailSubstitutions.SESSION_TYPE.toString(), session.getAccessType().getName()); substitutions.put(MailSubstitutions.SESSION_START_TIME.toString(), dateFormatter.print(session.getStartTime().withZone(timezone))); substitutions.put(MailSubstitutions.SESSION_END_TIME.toString(), dateFormatter.print(session.getEndTime().withZone(timezone))); substitutions.put(MailSubstitutions.SESSION_BOUNDARY_TIME.toString(), String.valueOf(session.getBoundaryTime())); return substitutions; } private Calendar buildIcsFile(Session session, ConferenceUser user) { return buildIcsFile(session, user, false); } private Calendar buildIcsFile(Session session, ConferenceUser user, boolean isCancellation) { //create meeting Invite .ics file String userSessionUrl = sessionService.getOrCreateSessionUrl(user, session); VEvent event = new VEvent(new DateTime(session.getStartTime().toDate()),new DateTime(session.getEndTime().toDate()),session.getSessionName()); Calendar cal = new Calendar(); event.getProperties().add(new Uid(String.valueOf(session.getSessionId()) + "-BlackboardCollaborate")); if(isCancellation) { event.getProperties().add(Status.VEVENT_CANCELLED); } else { event.getProperties().add(new Location(userSessionUrl)); Description desc = new Description("Your join URL: " + userSessionUrl + "\n\nGuest join URL: " + session.getGuestUrl() + "\n\nCreator: " + session.getCreator().getDisplayName() + " (" + session.getCreator().getEmail() + ")"); event.getProperties().add(desc); } cal.getComponents().add(event); return cal; } }