package de.flower.rmt.service; import com.mysema.query.types.EntityPath; import com.mysema.query.types.Order; import com.mysema.query.types.OrderSpecifier; import com.mysema.query.types.expr.BooleanExpression; import de.flower.common.model.EntityHelper; import de.flower.common.util.Check; import de.flower.rmt.model.db.entity.Invitation; import de.flower.rmt.model.db.entity.Invitation_; import de.flower.rmt.model.db.entity.Lineup; import de.flower.rmt.model.db.entity.Team; import de.flower.rmt.model.db.entity.User; import de.flower.rmt.model.db.entity.event.AbstractSoccerEvent; import de.flower.rmt.model.db.entity.event.AbstractSoccerEvent_; import de.flower.rmt.model.db.entity.event.Event; import de.flower.rmt.model.db.entity.event.Event_; import de.flower.rmt.model.db.entity.event.QEvent; import de.flower.rmt.model.db.type.EventType; import de.flower.rmt.model.db.type.RSVPStatus; import de.flower.rmt.model.db.type.activity.EventUpdateMessage; import de.flower.rmt.model.dto.Notification; import de.flower.rmt.repository.IEventRepo; import de.flower.rmt.service.mail.IMailService; import de.flower.rmt.service.mail.INotificationService; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import javax.persistence.metamodel.Attribute; import java.util.List; import static de.flower.rmt.repository.Specs.eq; import static de.flower.rmt.repository.Specs.fetch; import static org.springframework.data.jpa.domain.Specifications.where; /** * @author flowerrrr */ @Service @Transactional(readOnly = true, propagation = Propagation.REQUIRED) public class EventManager extends AbstractService implements IEventManager { @Autowired private IEventRepo eventRepo; @Autowired private IInvitationManager invitationManager; @Autowired private INotificationService notificationService; @Autowired private IUserManager userManager; @Autowired private IMailService mailService; @Autowired private IActivityManager activityManager; @Autowired private ILineupManager lineupManager; @Value("${event.closed.before.hours}") private Integer eventClosedBeforeHours; @Autowired private MessageSourceAccessor messageSource; @Override @Transactional(readOnly = false) public void save(Event entity) { validate(entity); EventUpdateMessage.Type type = (entity.isNew()) ? EventUpdateMessage.Type.CREATED : EventUpdateMessage.Type.UPDATED; eventRepo.save(entity); activityManager.onCreateOrUpdateEvent(entity, type); } @Override @Transactional(readOnly = false) public void create(final Event entity, final boolean createInvitations) { Check.notNull(entity); save(entity); if (createInvitations) { // for every user that is a player of the team of this event a invitation will be created List<User> users = userManager.findAllByTeam(entity.getTeam()); invitationManager.addUsers(entity, EntityHelper.convertEntityListToIdList(users)); lineupManager.createLineup(entity); } } @Override public Event loadById(Long id, final Attribute... attributes) { Specification fetch = fetch(attributes); Event entity = eventRepo.findOne(where(eq(Event_.id, id)).and(fetch)); Check.notNull(entity, "Event [" + id + "] not found"); assertClub(entity); // init transient date fields entity.initTransientFields(); return entity; } @Override public long getNumEventsByUser(final User user) { if (user != null) { BooleanExpression isUser = QEvent.event.invitations.any().user.eq(user); return eventRepo.count(isUser); } else { return eventRepo.count(); } } /** * Uses spring data spec instead of querydsl in order to be able to eager fetch opponents. */ @Override public List<Event> findAll(EntityPath<?>... attributes) { return eventRepo.findAll(null, attributes); } @Override public List<Event> findAll(final int page, final int size, final User user, final EntityPath<?>... attributes) { BooleanExpression isUser = null; if (user != null) { isUser = QEvent.event.invitations.any().user.eq(user); } return eventRepo.findAll(isUser, new PageRequest(page, size, Sort.Direction.DESC, Event_.dateTime.getName()), attributes).getContent(); } @Override public Event findNextEvent(final User user) { BooleanExpression isUser = null; if (user != null) { isUser = QEvent.event.invitations.any().user.eq(user); } BooleanExpression isUpcomming = QEvent.event.dateTime.after(new LocalDate().toDateTimeAtStartOfDay()); BooleanExpression notCanceled = QEvent.event.canceled.isTrue().not(); List<Event> upcoming = eventRepo.findAll(isUpcomming.and(isUser).and(notCanceled), new PageRequest(0, 1, Sort.Direction.ASC, Event_.dateTime.getName())).getContent(); return (upcoming.isEmpty()) ? null : upcoming.get(0); } @Override public List<Event> findAllNextNHours(final int hours) { // when: 5 days before event, but at least 48 h after invitation mail DateTime now = new DateTime(); BooleanExpression insideNextNDays = QEvent.event.dateTime.between(now, now.plusHours(hours)); BooleanExpression notCanceled = QEvent.event.canceled.ne(true); return eventRepo.findAll(insideNextNDays.and(notCanceled)); } @Override public List<Event> findAllByDateRange(final DateTime start, final DateTime end, EntityPath<?>... attributes) { BooleanExpression isBeetween = QEvent.event.dateTime.between(start, end); return eventRepo.findAll(isBeetween, attributes); } @Override public List<Event> findAllByDateRangeAndUser(final DateTime start, final DateTime end, final User user, EntityPath<?>... attributes) { BooleanExpression isBeetween = QEvent.event.dateTime.between(start, end); BooleanExpression isUser = null; if (user != null) { isUser = QEvent.event.invitations.any().user.eq(user); } return eventRepo.findAll(isBeetween.and(isUser), new OrderSpecifier(Order.DESC, QEvent.event.dateTime), attributes); } @Override @Transactional(readOnly = false) public void delete(Long id) { Event entity = loadById(id); assertClub(entity); List<Invitation> invitations = invitationManager.findAllByEvent(entity); for (Invitation invitation : invitations) { invitationManager.delete(invitation.getId()); } Lineup lineup = lineupManager.findLineup(entity); if (lineup != null) { lineupManager.delete(lineup.getId()); } eventRepo.delete(entity); } @Override @Transactional(readOnly = false) public void deleteByTeam(Team team) { for (Event event : eventRepo.findAllByTeam(team)) { eventRepo.softDelete(event); } } @Override public Event newInstance(EventType eventType) { Check.notNull(eventType); Event event = eventType.newInstance(getClub()); event.setCreatedBy(securityService.getUser()); return event; } @Override public void sendInvitationMail(final Long eventId, final Notification notification) { Event event = loadById(eventId); mailService.sendMassMail(notification); event.setInvitationSent(true); eventRepo.save(event); // update all invitations and mark them also with invitationSent = true (allows to later add invitations and send mails to new participants) invitationManager.markInvitationSent(event, notification.getAddressList(), null); activityManager.onInvitationMailSent(event); } @Override public boolean isEventClosed(Event event) { // RMT-733 if (event.isCanceled()) { return false; } DateTime now = new DateTime(); // eventRepo.reattach(event); return now.plusHours(eventClosedBeforeHours).isAfter(event.getDateTime()); } @Override public void cancelEvent(final Long id) { Event event = loadById(id); event.setCanceled(true); String summary = messageSource.getMessage("event.summary.canceled.prefix") + " " + event.getSummary(); event.setSummary(summary); eventRepo.save(event); // notify accepted and unsure invitees sendEventCanceledMessage(event); activityManager.onCreateOrUpdateEvent(event, EventUpdateMessage.Type.CANCELED); } @Override public Event copyOf(final Event event) { Event e; if (event instanceof AbstractSoccerEvent) { e = loadById(event.getId(), Event_.team, Event_.venue, Event_.opponent, AbstractSoccerEvent_.uniform); }else { e = loadById(event.getId(), Event_.team, Event_.venue, Event_.opponent); } Event newEvent = newInstance(e.getEventType()); newEvent.copyFrom(e); return newEvent; } private void sendEventCanceledMessage(final Event event) { List<Invitation> invitations = invitationManager.findAllByEventAndStatus(event, RSVPStatus.ACCEPTED, Invitation_.user); invitations.addAll(invitationManager.findAllByEventAndStatus(event, RSVPStatus.UNSURE, Invitation_.user)); notificationService.sendEventCanceledMessage(event, invitations); } }