/*
* Copyright 2015-2017 EuregJUG.
*
* Licensed 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
*
* 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 eu.euregjug.site.events;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Entities.EscapeMode;
import org.jsoup.parser.Parser;
import org.jsoup.safety.Cleaner;
import org.jsoup.safety.Whitelist;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring4.SpringTemplateEngine;
/**
* @author Michael J. Simons, 2015-12-29
*/
@Service
@Slf4j
public class RegistrationService {
public static class InvalidRegistrationException extends RuntimeException {
private static final long serialVersionUID = -2624768968401255945L;
private final String localizedMessage;
public InvalidRegistrationException(final String message, final String localizedMessage) {
super(message);
this.localizedMessage = localizedMessage;
}
@Override
public String getLocalizedMessage() {
return localizedMessage;
}
}
private final EventRepository eventRepository;
private final RegistrationRepository registrationRepository;
/**
* Needed to render the HTML template.
*/
private final SpringTemplateEngine templateEngine;
/**
* Sender address for confirmation emails, taken from
* <code>spring.mail.properties.mail.smtp.from</code>
*/
private final String mailFrom;
/**
* Spring Abstraction over JavaMail.
*/
private final JavaMailSender mailSender;
/**
* Needed for translating some email properties.
*/
private final MessageSource messageSource;
public RegistrationService(
final EventRepository eventRepository,
final RegistrationRepository registrationRepository,
final JavaMailSender mailSender,
final SpringTemplateEngine templateEngine,
final MessageSource messageSource,
@Value("${spring.mail.properties.mail.smtp.from}") final String mailFrom
) {
this.eventRepository = eventRepository;
this.registrationRepository = registrationRepository;
this.mailFrom = mailFrom;
this.mailSender = mailSender;
this.templateEngine = templateEngine;
this.messageSource = messageSource;
}
@Transactional
public RegistrationEntity register(final Integer eventId, final Registration newRegistration) {
final EventEntity event = this.eventRepository
.findOne(eventId)
.orElseThrow(() -> new InvalidRegistrationException(String.format("No event with the id %d", eventId), "invalidEvent"));
final String email = newRegistration.getEmail().toLowerCase();
if (!event.isNeedsRegistration()) {
throw new InvalidRegistrationException(String.format("Event %d doesn't need a registration", eventId), "eventNeedNoRegistration");
} else if (!event.isOpenForRegistration()) {
throw new InvalidRegistrationException(String.format("Event %d doesn't isn't open", eventId), "eventNotOpen");
} else if (this.registrationRepository.findByEventAndEmail(event, email).isPresent()) {
throw new InvalidRegistrationException(String.format("Guest '%s' already registered for event %d", email, eventId), "alreadyRegistered");
} else {
return this.registrationRepository.save(
new RegistrationEntity(event, email, newRegistration.getName(), newRegistration.getFirstName(), newRegistration.isSubscribeToNewsletter())
);
}
}
@Async
public void sendConfirmationMail(final RegistrationEntity registrationEntity, final Locale locale) {
try {
final Context context = new Context();
context.setLocale(locale);
context.setVariable("registration", registrationEntity);
final Writer w = new StringWriter();
templateEngine.process("registered", context, w);
final String htmlText = w.toString();
mailSender.send(mimeMessage -> {
final MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true, StandardCharsets.UTF_8.name());
message.setFrom(mailFrom);
message.setTo(registrationEntity.getEmail());
message.setSubject(messageSource.getMessage("registrationConfirmationSubject", new Object[]{registrationEntity.getEvent().getName()}, locale));
message.setText(htmlTextToPlainText(htmlText), htmlText);
});
log.info("Sent confirmation email for '{}' to '{}'.", registrationEntity.getEvent().getName(), registrationEntity.getEmail());
} catch (MailException e) {
log.warn("Could not send an email to {} for event '{}': {}", registrationEntity.getEmail(), registrationEntity.getEvent().getName(), e.getMessage());
log.debug("Full error", e);
}
}
/**
* Cleans some html text by stripping all tags but <code>br</code> and then
* unescapes named entitiesl like '"e';. brs will be replaced by
* newlines.
*
* @param htmlText
* @return
*/
String htmlTextToPlainText(final String htmlText) {
final Whitelist whitelist = Whitelist.none();
whitelist.addTags("br");
final Cleaner cleaner = new Cleaner(whitelist);
final Document cleanedDocument = cleaner.clean(Jsoup.parse(htmlText));
cleanedDocument
.outputSettings()
.prettyPrint(false)
.escapeMode(EscapeMode.xhtml)
.charset(StandardCharsets.UTF_8);
return Parser.unescapeEntities(cleanedDocument.body().html().trim(), true).replaceAll("<br(?: ?/)?>", "\r\n");
}
}