Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use nested transaction where possible #575

Merged
merged 2 commits into from
Jan 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/main/java/alfio/manager/AdminReservationManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
Expand Down Expand Up @@ -160,22 +161,23 @@ public Result<Boolean> updateReservation(String eventName, String reservationId,
}

public Result<Pair<TicketReservation, List<Ticket>>> createReservation(AdminReservationModification input, String eventName, String username) {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
DefaultTransactionDefinition definition = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_NESTED);
TransactionTemplate template = new TransactionTemplate(transactionManager, definition);
return template.execute(status -> {
var savepoint = status.createSavepoint();
try {
Result<Pair<TicketReservation, List<Ticket>>> result = eventRepository.findOptionalByShortNameForUpdate(eventName)
.map(e -> validateTickets(input, e))
.map(r -> r.flatMap(p -> transactionalCreateReservation(p.getRight(), p.getLeft(), username)))
.orElse(Result.error(ErrorCode.EventError.NOT_FOUND));
if (!result.isSuccess()) {
log.debug("Error during update of reservation eventName: {}, username: {}, reservation: {}", eventName, username, AdminReservationModification.summary(input));
status.setRollbackOnly();
status.rollbackToSavepoint(savepoint);
}
return result;
} catch (Exception e) {
log.error("Error during update of reservation eventName: {}, username: {}, reservation: {}", eventName, username, AdminReservationModification.summary(input));
status.setRollbackOnly();
status.rollbackToSavepoint(savepoint);
return Result.error(singletonList(ErrorCode.custom(e instanceof DuplicateReferenceException ? "duplicate-reference" : "", e.getMessage())));
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,20 +115,21 @@ public Pair<Integer, Integer> processPendingReservations() {
}

private Result<Triple<TicketReservation, List<Ticket>, Event>> processReservation(AdminReservationRequest request, Pair<Event, User> p) {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
DefaultTransactionDefinition definition = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_NESTED);
TransactionTemplate template = new TransactionTemplate(transactionManager, definition);
return template.execute(status -> {
var savepoint = status.createSavepoint();
try {
String eventName = p.getLeft().getShortName();
String username = p.getRight().getUsername();
Result<Triple<TicketReservation, List<Ticket>, Event>> result = adminReservationManager.createReservation(request.getBody(), eventName, username)
.flatMap(r -> adminReservationManager.confirmReservation(eventName, r.getLeft().getId(), username));
if(!result.isSuccess()) {
status.setRollbackOnly();
status.rollbackToSavepoint(savepoint);
}
return result;
} catch(Exception ex) {
status.setRollbackOnly();
status.rollbackToSavepoint(savepoint);
return Result.error(singletonList(ErrorCode.custom("", ex.getMessage())));
}
});
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/alfio/manager/NotificationManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@
import org.springframework.security.crypto.codec.Hex;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;

import java.io.ByteArrayOutputStream;
Expand Down Expand Up @@ -100,7 +102,8 @@ public NotificationManager(Mailer mailer,
this.emailMessageRepository = emailMessageRepository;
this.eventRepository = eventRepository;
this.organizationRepository = organizationRepository;
this.tx = new TransactionTemplate(transactionManager);
DefaultTransactionDefinition definition = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_NESTED);
this.tx = new TransactionTemplate(transactionManager, definition);
this.configurationManager = configurationManager;
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Mailer.Attachment.class, new AttachmentConverter());
Expand Down
77 changes: 44 additions & 33 deletions src/main/java/alfio/manager/TicketReservationManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ public class TicketReservationManager {
private final TemplateManager templateManager;
private final TransactionTemplate requiresNewTransactionTemplate;
private final TransactionTemplate serializedTransactionTemplate;
private final TransactionTemplate nestedTransactionTemplate;
private final WaitingQueueManager waitingQueueManager;
private final TicketFieldRepository ticketFieldRepository;
private final AdditionalServiceRepository additionalServiceRepository;
Expand Down Expand Up @@ -189,6 +190,7 @@ public TicketReservationManager(EventRepository eventRepository,
DefaultTransactionDefinition serialized = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
serialized.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
this.serializedTransactionTemplate = new TransactionTemplate(transactionManager, serialized);
this.nestedTransactionTemplate = new TransactionTemplate(transactionManager, new DefaultTransactionDefinition((TransactionDefinition.PROPAGATION_NESTED)));
this.ticketFieldRepository = ticketFieldRepository;
this.additionalServiceRepository = additionalServiceRepository;
this.additionalServiceItemRepository = additionalServiceItemRepository;
Expand Down Expand Up @@ -905,13 +907,16 @@ public void cleanupExpiredOfflineReservations(Date expirationDate) {

private void cleanupOfflinePayment(String reservationId) {
try {
Event event = eventRepository.findByReservationId(reservationId);
boolean enabled = configurationManager.getBooleanConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), AUTOMATIC_REMOVAL_EXPIRED_OFFLINE_PAYMENT), true);
if(enabled) {
deleteOfflinePayment(event, reservationId, true, false, null);
} else {
log.trace("Will not cleanup reservation with id {} because the automatic removal has been disabled", reservationId);
}
nestedTransactionTemplate.execute((tc) -> {
Event event = eventRepository.findByReservationId(reservationId);
boolean enabled = configurationManager.getBooleanConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), AUTOMATIC_REMOVAL_EXPIRED_OFFLINE_PAYMENT), true);
if (enabled) {
deleteOfflinePayment(event, reservationId, true, false, null);
} else {
log.trace("Will not cleanup reservation with id {} because the automatic removal has been disabled", reservationId);
}
return null;
});
} catch (Exception e) {
log.error("error during reservation cleanup (id "+reservationId+")", e);
}
Expand Down Expand Up @@ -1446,19 +1451,22 @@ public void sendReminderForOptionalData() {
}

private void sendOptionalDataReminder(Pair<Event, List<Ticket>> eventAndTickets) {
Event event = eventAndTickets.getLeft();
int daysBeforeStart = configurationManager.getIntConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), ConfigurationKeys.ASSIGNMENT_REMINDER_START), 10);
List<Ticket> tickets = eventAndTickets.getRight().stream().filter(t -> !ticketFieldRepository.hasOptionalData(t.getId())).collect(toList());
Set<String> notYetNotifiedReservations = tickets.stream().map(Ticket::getTicketsReservationId).distinct().filter(rid -> findByIdForNotification(rid, event.getZoneId(), daysBeforeStart).isPresent()).collect(toSet());
tickets.stream()
.filter(t -> notYetNotifiedReservations.contains(t.getTicketsReservationId()))
.forEach(t -> {
int result = ticketRepository.flagTicketAsReminderSent(t.getId());
Validate.isTrue(result == 1);
Map<String, Object> model = TemplateResource.prepareModelForReminderTicketAdditionalInfo(organizationRepository.getById(event.getOrganizationId()), event, t, ticketUpdateUrl(event, t.getUuid()));
Locale locale = Optional.ofNullable(t.getUserLanguage()).map(Locale::forLanguageTag).orElseGet(() -> findReservationLanguage(t.getTicketsReservationId()));
notificationManager.sendSimpleEmail(event, t.getEmail(), messageSource.getMessage("reminder.ticket-additional-info.subject", new Object[]{event.getDisplayName()}, locale), () -> templateManager.renderTemplate(event, TemplateResource.REMINDER_TICKET_ADDITIONAL_INFO, model, locale));
});
nestedTransactionTemplate.execute(ts -> {
Event event = eventAndTickets.getLeft();
int daysBeforeStart = configurationManager.getIntConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), ConfigurationKeys.ASSIGNMENT_REMINDER_START), 10);
List<Ticket> tickets = eventAndTickets.getRight().stream().filter(t -> !ticketFieldRepository.hasOptionalData(t.getId())).collect(toList());
Set<String> notYetNotifiedReservations = tickets.stream().map(Ticket::getTicketsReservationId).distinct().filter(rid -> findByIdForNotification(rid, event.getZoneId(), daysBeforeStart).isPresent()).collect(toSet());
tickets.stream()
.filter(t -> notYetNotifiedReservations.contains(t.getTicketsReservationId()))
.forEach(t -> {
int result = ticketRepository.flagTicketAsReminderSent(t.getId());
Validate.isTrue(result == 1);
Map<String, Object> model = TemplateResource.prepareModelForReminderTicketAdditionalInfo(organizationRepository.getById(event.getOrganizationId()), event, t, ticketUpdateUrl(event, t.getUuid()));
Locale locale = Optional.ofNullable(t.getUserLanguage()).map(Locale::forLanguageTag).orElseGet(() -> findReservationLanguage(t.getTicketsReservationId()));
notificationManager.sendSimpleEmail(event, t.getEmail(), messageSource.getMessage("reminder.ticket-additional-info.subject", new Object[]{event.getDisplayName()}, locale), () -> templateManager.renderTemplate(event, TemplateResource.REMINDER_TICKET_ADDITIONAL_INFO, model, locale));
});
return null;
});
}

Stream<Event> getNotifiableEventsStream() {
Expand All @@ -1473,19 +1481,22 @@ Stream<Event> getNotifiableEventsStream() {

private void sendAssignmentReminder(Pair<Event, Set<String>> p) {
try {
Event event = p.getLeft();
ZoneId eventZoneId = event.getZoneId();
int quietPeriod = configurationManager.getIntConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), ConfigurationKeys.ASSIGNMENT_REMINDER_INTERVAL), 3);
p.getRight().stream()
.map(id -> findByIdForNotification(id, eventZoneId, quietPeriod))
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(reservation -> {
Map<String, Object> model = prepareModelForReservationEmail(event, reservation);
ticketReservationRepository.updateLatestReminderTimestamp(reservation.getId(), ZonedDateTime.now(eventZoneId));
Locale locale = findReservationLanguage(reservation.getId());
notificationManager.sendSimpleEmail(event, reservation.getEmail(), messageSource.getMessage("reminder.ticket-not-assigned.subject", new Object[]{event.getDisplayName()}, locale), () -> templateManager.renderTemplate(event, TemplateResource.REMINDER_TICKETS_ASSIGNMENT_EMAIL, model, locale));
});
nestedTransactionTemplate.execute(ts -> {
Event event = p.getLeft();
ZoneId eventZoneId = event.getZoneId();
int quietPeriod = configurationManager.getIntConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), ConfigurationKeys.ASSIGNMENT_REMINDER_INTERVAL), 3);
p.getRight().stream()
.map(id -> findByIdForNotification(id, eventZoneId, quietPeriod))
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(reservation -> {
Map<String, Object> model = prepareModelForReservationEmail(event, reservation);
ticketReservationRepository.updateLatestReminderTimestamp(reservation.getId(), ZonedDateTime.now(eventZoneId));
Locale locale = findReservationLanguage(reservation.getId());
notificationManager.sendSimpleEmail(event, reservation.getEmail(), messageSource.getMessage("reminder.ticket-not-assigned.subject", new Object[]{event.getDisplayName()}, locale), () -> templateManager.renderTemplate(event, TemplateResource.REMINDER_TICKETS_ASSIGNMENT_EMAIL, model, locale));
});
return null;
});
} catch (Exception ex) {
log.warn("cannot send reminder message", ex);
}
Expand Down