/** * Licensed to The Apereo Foundation under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * * The Apereo Foundation licenses this file to you 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.txt * * 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.opencastproject.messages; import static org.opencastproject.kernel.mail.EmailAddress.getAddress; import static org.opencastproject.kernel.mail.EmailAddress.getName; import static org.opencastproject.util.data.Option.none; import static org.opencastproject.util.data.Option.option; import static org.opencastproject.util.data.Option.some; import org.opencastproject.kernel.mail.BaseSmtpService; import org.opencastproject.kernel.mail.EmailAddress; import org.opencastproject.messages.persistence.EmailConfigurationDto; import org.opencastproject.messages.persistence.MessageSignatureDto; import org.opencastproject.messages.persistence.MessageTemplateDto; import org.opencastproject.security.api.Organization; import org.opencastproject.security.api.OrganizationDirectoryService; import org.opencastproject.security.api.SecurityService; import org.opencastproject.security.api.UserDirectoryService; import org.opencastproject.security.util.SecurityUtil; import org.opencastproject.util.NotFoundException; import org.opencastproject.util.data.Effect0; import org.opencastproject.util.data.Option; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import javax.mail.Address; import javax.mail.MessagingException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.NoResultException; import javax.persistence.Query; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; /** * OSGi service that allows to send e-mails by templates using {@link BaseSmtpService} and implements permanent storage * for message templates signatures and the email configuration. */ public class MailService { public static final String PERSISTENCE_UNIT = "org.opencastproject.messages"; /** Logging utilities */ private static final Logger logger = LoggerFactory.getLogger(MailService.class); /** Factory used to create {@link EntityManager}s for transactions */ protected EntityManagerFactory emf; /** The user directory service */ protected UserDirectoryService userDirectoryService; /** The organization directory service */ protected OrganizationDirectoryService organizationDirectoryService; /** The security service */ protected SecurityService securityService; /** The SMTP service */ protected final BaseSmtpService smtpService = new BaseSmtpService(); /** * Creates {@link EntityManagerFactory} using persistence provider and properties passed via OSGi. * * @param cc */ public void activate(ComponentContext cc) { logger.info("Activating persistence manager for participation management"); for (Organization org : organizationDirectoryService.getOrganizations()) { SecurityUtil.runAs(securityService, org, SecurityUtil.createSystemUser(cc, org), new Effect0() { @Override protected void run() { try { updateSmtpConfiguration(getEmailConfiguration()); } catch (MailServiceException e) { logger.error("Unable to initialize the SMTP configuration from the database: {}", ExceptionUtils.getStackTrace(e)); } } }); } } /** OSGi DI */ public void setEntityManagerFactory(EntityManagerFactory emf) { this.emf = emf; } /** * OSGi callback to set user directory service. * * @param userDirectoryService * user directory service */ public void setUserDirectoryService(UserDirectoryService userDirectoryService) { this.userDirectoryService = userDirectoryService; } /** * OSGi callback to set the security service. * * @param securityService * the security service */ public void setSecurityService(SecurityService securityService) { this.securityService = securityService; } /** * OSGi callback to set the organization directory service. * * @param organizationDirectoryService * the organization directory service */ public void setOrganizationDirectoryService(OrganizationDirectoryService organizationDirectoryService) { this.organizationDirectoryService = organizationDirectoryService; } public List<MessageTemplate> findMessageTemplates(TemplateMessageQuery query) { EntityManager em = null; List<MessageTemplate> messageTemplates = new ArrayList<>(); try { em = emf.createEntityManager(); CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<MessageTemplateDto> q = cb.createQuery(MessageTemplateDto.class); Root<MessageTemplateDto> messageTemplateRoot = q.from(MessageTemplateDto.class); List<Predicate> predicates = new ArrayList<>(); q.select(messageTemplateRoot); String orgId = securityService.getOrganization().getId(); predicates.add(cb.equal(messageTemplateRoot.get("organization"), orgId)); if (!query.isIncludeHidden()) predicates.add(cb.isFalse(messageTemplateRoot.get("hidden").as(Boolean.class))); if (StringUtils.isNotEmpty(query.getName())) predicates.add(cb.equal(messageTemplateRoot.get("name"), query.getName())); if (StringUtils.isNotEmpty(query.getCreator())) predicates.add(cb.equal(messageTemplateRoot.get("creator"), query.getCreator())); if (query.getType() != null) predicates.add(cb.equal(messageTemplateRoot.get("type").as(TemplateType.Type.class), query.getType())); if (StringUtils.isNotEmpty(query.getFullText())) { List<Predicate> fullTextPredicates = new ArrayList<>(); fullTextPredicates.add(cb.like(messageTemplateRoot.<String> get("name"), "%" + query.getFullText() + "%")); fullTextPredicates.add(cb.like(messageTemplateRoot.<String> get("subject"), "%" + query.getFullText() + "%")); fullTextPredicates.add(cb.like(messageTemplateRoot.<String> get("body"), "%" + query.getFullText() + "%")); predicates.add(cb.or(fullTextPredicates.toArray(new Predicate[fullTextPredicates.size()]))); } q.where(cb.and(predicates.toArray(new Predicate[predicates.size()]))); TypedQuery<MessageTemplateDto> typedQuery = em.createQuery(q); List<MessageTemplateDto> messageTemplatesDto = typedQuery.getResultList(); for (MessageTemplateDto mt : messageTemplatesDto) { messageTemplates.add(mt.toMessageTemplate(userDirectoryService)); } return messageTemplates; } finally { if (em != null) em.close(); } } public MessageTemplate getMessageTemplate(Long id) throws MailServiceException, NotFoundException { EntityManager em = null; try { em = emf.createEntityManager(); String orgId = securityService.getOrganization().getId(); Option<MessageTemplateDto> template = findMessageTemplateById(id, orgId, em); if (template.isNone()) throw new NotFoundException(); return template.get().toMessageTemplate(userDirectoryService); } catch (NotFoundException e) { throw e; } catch (Exception e) { logger.error("Could not get message template {}: {}", id, e.getMessage()); throw new MailServiceException(e); } finally { if (em != null) em.close(); } } public List<MessageTemplate> getMessageTemplates() throws MailServiceException { EntityManager em = null; try { em = emf.createEntityManager(); String orgId = securityService.getOrganization().getId(); TypedQuery<MessageTemplateDto> q = em.createNamedQuery("MessageTemplate.findAll", MessageTemplateDto.class).setParameter("org", orgId); List<MessageTemplate> templates = new ArrayList<>(); List<MessageTemplateDto> result = q.getResultList(); for (MessageTemplateDto m : result) { templates.add(m.toMessageTemplate(userDirectoryService)); } return templates; } catch (Exception e) { logger.error("Could not get message templates: {}", ExceptionUtils.getStackTrace(e)); throw new MailServiceException(e); } finally { if (em != null) em.close(); } } public List<MessageTemplate> getMessageTemplateByName(String messageTemplateName) throws MailServiceException { EntityManager em = null; try { em = emf.createEntityManager(); String orgId = securityService.getOrganization().getId(); TypedQuery<MessageTemplateDto> q = em.createNamedQuery("MessageTemplate.findByName", MessageTemplateDto.class) .setParameter("org", orgId); q.setParameter("name", messageTemplateName); List<MessageTemplate> templates = new ArrayList<>(); List<MessageTemplateDto> result = q.getResultList(); for (MessageTemplateDto m : result) { templates.add(m.toMessageTemplate(userDirectoryService)); } return templates; } catch (Exception e) { logger.error("Could not get message templates: {}", ExceptionUtils.getStackTrace(e)); throw new MailServiceException(e); } finally { if (em != null) em.close(); } } public List<MessageTemplate> getMessageTemplatesStartingWith(String filterText) throws MailServiceException { EntityManager em = null; try { em = emf.createEntityManager(); String orgId = securityService.getOrganization().getId(); TypedQuery<MessageTemplateDto> q = em.createNamedQuery("MessageTemplate.likeName", MessageTemplateDto.class) .setParameter("org", orgId); q.setParameter("name", filterText + "%"); List<MessageTemplate> templates = new ArrayList<>(); List<MessageTemplateDto> result = q.getResultList(); for (MessageTemplateDto m : result) { templates.add(m.toMessageTemplate(userDirectoryService)); } return templates; } catch (Exception e) { logger.error("Could not get message templates: {}", ExceptionUtils.getStackTrace(e)); throw new MailServiceException(e); } finally { if (em != null) em.close(); } } public MessageTemplate updateMessageTemplate(MessageTemplate template) throws MailServiceException { EntityManager em = null; EntityTransaction tx = null; try { em = emf.createEntityManager(); tx = em.getTransaction(); tx.begin(); String orgId = securityService.getOrganization().getId(); MessageTemplateDto msgTmpl = mergeMessageTemplate(template, orgId, em); tx.commit(); return msgTmpl.toMessageTemplate(userDirectoryService); } catch (Exception e) { logger.error("Could not update message template '{}': {}", template, e.getMessage()); if (tx.isActive()) tx.rollback(); throw new MailServiceException(e); } finally { if (em != null) em.close(); } } public void deleteMessageTemplate(Long id) throws MailServiceException, NotFoundException { EntityManager em = null; EntityTransaction tx = null; try { em = emf.createEntityManager(); tx = em.getTransaction(); tx.begin(); String orgId = securityService.getOrganization().getId(); Option<MessageTemplateDto> templateOption = findMessageTemplateById(id, orgId, em); if (templateOption.isNone()) throw new NotFoundException(); em.remove(templateOption.get()); tx.commit(); } catch (NotFoundException e) { throw e; } catch (Exception e) { logger.error("Could not delete message template '{}': {}", id, e.getMessage()); if (tx.isActive()) tx.rollback(); throw new MailServiceException(e); } finally { if (em != null) em.close(); } } public List<MessageSignature> getMessageSignatures() throws MailServiceException { EntityManager em = null; try { em = emf.createEntityManager(); String orgId = securityService.getOrganization().getId(); Query q = em.createNamedQuery("MessageSignature.findAll").setParameter("org", orgId); List<MessageSignature> signatures = new ArrayList<>(); List<MessageSignatureDto> result = q.getResultList(); for (MessageSignatureDto m : result) { signatures.add(m.toMessageSignature(userDirectoryService)); } return signatures; } catch (Exception e) { logger.error("Could not get message signatures: {}", e.getMessage()); throw new MailServiceException(e); } finally { if (em != null) em.close(); } } /** * Get all of the message signatures for the current user. * @return A list of all of the message signatures. * @throws MailServiceException */ public List<MessageSignature> getMessageSignaturesByUserName() throws MailServiceException { EntityManager em = null; try { em = emf.createEntityManager(); String orgId = securityService.getOrganization().getId(); String username = securityService.getUser().getUsername(); Query q = em.createNamedQuery("MessageSignature.findByCreator").setParameter("username", username).setParameter("org", orgId); List<MessageSignature> signatures = new ArrayList<>(); List<MessageSignatureDto> result = q.getResultList(); for (MessageSignatureDto m : result) { signatures.add(m.toMessageSignature(userDirectoryService)); } return signatures; } catch (Exception e) { logger.error("Could not get message signatures: {}", ExceptionUtils.getStackTrace(e)); throw new MailServiceException(e); } finally { if (em != null) { em.close(); } } } /** * Get the current logged in user's signature * @return The message signature * @throws MailServiceException * @throws NotFoundException */ public MessageSignature getCurrentUsersSignature() throws MailServiceException, NotFoundException { EntityManager em = null; try { em = emf.createEntityManager(); String orgId = securityService.getOrganization().getId(); String username = securityService.getUser().getUsername(); TypedQuery<MessageSignatureDto> q = em.createNamedQuery("MessageSignature.findByCreator", MessageSignatureDto.class).setParameter("username", username).setParameter("org", orgId); MessageSignatureDto messageSignatureDto = q.getSingleResult(); return messageSignatureDto.toMessageSignature(userDirectoryService); } catch (NoResultException e) { throw new NotFoundException(e); } catch (Exception e) { logger.error("Could not get message signatures: {}", ExceptionUtils.getStackTrace(e)); throw new MailServiceException(e); } finally { if (em != null) em.close(); } } /** * @return Finds the total number of message signatures for the current user. * @throws MailServiceException */ public int getSignatureTotalByUserName() throws MailServiceException { EntityManager em = null; try { em = emf.createEntityManager(); String orgId = securityService.getOrganization().getId(); String username = securityService.getUser().getUsername(); Query q = em.createNamedQuery("MessageSignature.countByCreator").setParameter("username", username) .setParameter("org", orgId); Number countResult = (Number) q.getSingleResult(); return countResult.intValue(); } catch (Exception e) { logger.error("Could not count message signatures: {}", ExceptionUtils.getStackTrace(e)); throw new MailServiceException(e); } finally { if (em != null) em.close(); } } public MessageSignature getMessageSignature(Long id) throws MailServiceException, NotFoundException { EntityManager em = null; try { em = emf.createEntityManager(); String orgId = securityService.getOrganization().getId(); Option<MessageSignatureDto> signature = findMessageSignatureById(id, orgId, em); if (signature.isNone()) throw new NotFoundException(); return signature.get().toMessageSignature(userDirectoryService); } catch (NotFoundException e) { throw e; } catch (Exception e) { logger.error("Could not get message signature {}: {}", id, e.getMessage()); throw new MailServiceException(e); } finally { if (em != null) em.close(); } } public MessageSignature updateMessageSignature(MessageSignature signature) throws MailServiceException { EntityManager em = null; EntityTransaction tx = null; try { em = emf.createEntityManager(); tx = em.getTransaction(); tx.begin(); String orgId = securityService.getOrganization().getId(); MessageSignatureDto msgSign = mergeMessageSignature(signature, orgId, em); tx.commit(); return msgSign.toMessageSignature(userDirectoryService); } catch (Exception e) { logger.error("Could not update message signature '{}': {}", signature, e.getMessage()); if (tx.isActive()) tx.rollback(); throw new MailServiceException(e); } finally { if (em != null) em.close(); } } public void deleteMessageSignature(Long id) throws MailServiceException, NotFoundException { EntityManager em = null; EntityTransaction tx = null; try { em = emf.createEntityManager(); tx = em.getTransaction(); tx.begin(); String orgId = securityService.getOrganization().getId(); Option<MessageSignatureDto> signatureOption = findMessageSignatureById(id, orgId, em); if (signatureOption.isNone()) throw new NotFoundException(); em.remove(signatureOption.get()); tx.commit(); } catch (NotFoundException e) { throw e; } catch (Exception e) { logger.error("Could not delete message signature '{}': {}", id, e.getMessage()); if (tx.isActive()) tx.rollback(); throw new MailServiceException(e); } finally { if (em != null) em.close(); } } public EmailConfiguration getEmailConfiguration() throws MailServiceException { EntityManager em = null; try { em = emf.createEntityManager(); String orgId = securityService.getOrganization().getId(); Option<EmailConfigurationDto> emailOption = findEmailConfiguration(orgId, em); if (emailOption.isSome()) { return emailOption.get().toEmailConfiguration(); } else { return EmailConfiguration.DEFAULT; } } catch (Exception e) { logger.error("Could not get email configuration: {}", e.getMessage()); throw new MailServiceException(e); } finally { if (em != null) em.close(); } } public EmailConfiguration updateEmailConfiguration(EmailConfiguration emailConfiguration) throws MailServiceException { EntityManager em = null; EntityTransaction tx = null; try { em = emf.createEntityManager(); tx = em.getTransaction(); tx.begin(); String orgId = securityService.getOrganization().getId(); EmailConfigurationDto emailConfig = mergeEmailConfiguration(emailConfiguration, orgId, em); tx.commit(); EmailConfiguration updatedEmailConfiguration = emailConfig.toEmailConfiguration(); updateSmtpConfiguration(updatedEmailConfiguration); return updatedEmailConfiguration; } catch (Exception e) { logger.error("Could not update email configuration '{}': {}", emailConfiguration, e.getMessage()); if (tx.isActive()) tx.rollback(); throw new MailServiceException(e); } finally { if (em != null) em.close(); } } public void send(Mail mail) throws MailServiceException { MimeMessage mimeMessage; try { mimeMessage = toMimeMessage(mail); } catch (Exception e) { logger.error("Unable to create mime message: {}", ExceptionUtils.getStackTrace(e)); throw new MailServiceException(e); } try { smtpService.send(mimeMessage); } catch (MessagingException e) { logger.error("Unable to send the email: {}", ExceptionUtils.getStackTrace(e)); throw new MailServiceException(e); } } public static MessageTemplateDto mergeMessageTemplate(MessageTemplate template, String organization, EntityManager em) { Option<MessageTemplateDto> dtoOption = findMessageTemplate(option(template.getId()), template.getName(), organization, em); MessageTemplateDto dto; if (dtoOption.isSome()) { dto = dtoOption.get(); dto.setName(template.getName()); dto.setType(template.getType().getType()); dto.setCreator(template.getCreator().getUsername()); dto.setSubject(template.getSubject()); dto.setBody(template.getBody()); dto.setCreationDate(template.getCreationDate()); dto.setHidden(template.isHidden()); em.merge(dto); } else { dto = new MessageTemplateDto(template.getName(), organization, template.getCreator().getUsername(), template.getSubject(), template.getBody(), template.getType().getType(), template.getCreationDate()); dto.setHidden(template.isHidden()); em.persist(dto); } return dto; } public static Option<EmailConfigurationDto> findEmailConfiguration(String organization, EntityManager em) { Query q = em.createNamedQuery("EmailConfiguration.findAll").setParameter("org", organization).setMaxResults(1); try { return some((EmailConfigurationDto) q.getSingleResult()); } catch (NoResultException e) { return none(); } } public static EmailConfigurationDto mergeEmailConfiguration(EmailConfiguration config, String organization, EntityManager em) { Option<EmailConfigurationDto> configOption = findEmailConfiguration(organization, em); EmailConfigurationDto dto; if (configOption.isSome()) { dto = configOption.get(); dto.setTransport(config.getTransport()); dto.setPassword(config.getPassword()); dto.setPort(config.getPort()); dto.setServer(config.getServer()); dto.setSsl(config.isSsl()); dto.setUserName(config.getUserName()); em.merge(dto); } else { dto = new EmailConfigurationDto(organization, config.getTransport(), config.getServer(), config.getPort(), config.getUserName(), config.getPassword(), config.isSsl()); em.persist(dto); } return dto; } public static MessageSignatureDto mergeMessageSignature(MessageSignature signature, String organization, EntityManager em) { Option<MessageSignatureDto> signatureOption = findMessageSignature(option(signature.getId()), signature.getName(), organization, em); MessageSignatureDto dto; if (signatureOption.isSome()) { dto = signatureOption.get(); dto.setCreationDate(signature.getCreationDate()); dto.setCreator(signature.getCreator().getUsername()); dto.setSender(signature.getSender().getAddress()); dto.setSenderName(signature.getSender().getName()); dto.setReplyTo(signature.getReplyTo().map(getAddress).getOrElseNull()); dto.setReplyToName(signature.getReplyTo().map(getName).getOrElseNull()); dto.setName(signature.getName()); dto.setSignature(signature.getSignature()); em.merge(dto); } else { dto = new MessageSignatureDto(signature.getName(), organization, signature.getCreator().getUsername(), signature .getSender().getAddress(), signature.getSender().getName(), signature.getReplyTo().map(getAddress) .getOrElseNull(), signature.getReplyTo().map(getName).getOrElseNull(), signature.getSignature(), signature.getCreationDate()); em.persist(dto); } return dto; } public static Option<MessageTemplateDto> findMessageTemplate(Option<Long> id, String name, String orgId, EntityManager em) { Option<MessageTemplateDto> messageTemplateDto = Option.<MessageTemplateDto> none(); if (id.isSome()) messageTemplateDto = findMessageTemplateById(id.get(), orgId, em); if (messageTemplateDto.isSome()) return messageTemplateDto; Query q = em.createNamedQuery("MessageTemplate.findByName").setParameter("name", name).setParameter("org", orgId); try { return some((MessageTemplateDto) q.getSingleResult()); } catch (NoResultException e) { return none(); } } public static Option<MessageTemplateDto> findMessageTemplateById(long id, String orgId, EntityManager em) { Query q = em.createNamedQuery("MessageTemplate.findById").setParameter("id", id).setParameter("org", orgId); try { return some((MessageTemplateDto) q.getSingleResult()); } catch (NoResultException e) { return none(); } } public static Option<MessageSignatureDto> findMessageSignature(Option<Long> id, String name, String orgId, EntityManager em) { Option<MessageSignatureDto> messageSignatureDto = Option.<MessageSignatureDto> none(); if (id.isSome()) messageSignatureDto = findMessageSignatureById(id.get(), orgId, em); if (messageSignatureDto.isSome()) return messageSignatureDto; Query q = em.createNamedQuery("MessageSignature.findByName").setParameter("name", name).setParameter("org", orgId); try { return some((MessageSignatureDto) q.getSingleResult()); } catch (NoResultException e) { return none(); } } public static Option<MessageSignatureDto> findMessageSignatureById(long id, String orgId, EntityManager em) { Query q = em.createNamedQuery("MessageSignature.findById").setParameter("id", id).setParameter("org", orgId); try { return some((MessageSignatureDto) q.getSingleResult()); } catch (NoResultException e) { return none(); } } /** * Find the entity from the given type with the given id * * @param id * the identifier of the entity to find * @param em * The entity manager * @param entityClass * The class of the type to find * @return an {@link org.opencastproject.util.data.Option option} object */ public static <A> Option<A> find(Option<Long> id, EntityManager em, Class<A> entityClass) { for (Long a : id) { return option(em.find(entityClass, a)); } return none(); } /** Message -> MimeMessage. */ private MimeMessage toMimeMessage(Mail mail) throws Exception { final MimeMessage msg = smtpService.createMessage(); for (EmailAddress reply : mail.getReplyTo()) msg.setReplyTo(new Address[] { new InternetAddress(reply.getAddress(), reply.getName(), "UTF-8") }); // recipient for (EmailAddress recipient : mail.getRecipients()) { msg.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(recipient.getAddress(), recipient.getName(), "UTF-8")); } // subject msg.setSubject(mail.getSubject()); EmailAddress from = mail.getSender(); msg.setFrom(new InternetAddress(from.getAddress(), from.getName(), "UTF-8")); // body msg.setText(mail.getBody(), "UTF-8"); return msg; } private void updateSmtpConfiguration(EmailConfiguration emailConfiguration) { smtpService.setMailTransport(emailConfiguration.getTransport()); smtpService.setHost(emailConfiguration.getServer()); smtpService.setPort(emailConfiguration.getPort()); smtpService.setUser(emailConfiguration.getUserName()); smtpService.setPassword(emailConfiguration.getPassword()); smtpService.setSsl(emailConfiguration.isSsl()); smtpService.setDebug(false); smtpService.setSender(null); smtpService.configure(); } }