package de.flower.rmt.service;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.mysema.query.types.expr.BooleanExpression;
import de.flower.common.util.Check;
import de.flower.rmt.model.db.entity.Activity;
import de.flower.rmt.model.db.entity.Activity_;
import de.flower.rmt.model.db.entity.BArticle;
import de.flower.rmt.model.db.entity.BComment;
import de.flower.rmt.model.db.entity.Comment;
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.QActivity;
import de.flower.rmt.model.db.entity.event.Event;
import de.flower.rmt.model.db.entity.event.Event_;
import de.flower.rmt.model.db.type.activity.BlogUpdateMessage;
import de.flower.rmt.model.db.type.activity.EmailSentMessage;
import de.flower.rmt.model.db.type.activity.EventUpdateMessage;
import de.flower.rmt.model.db.type.activity.EventUpdateMessage.Type;
import de.flower.rmt.model.db.type.activity.InvitationUpdateMessage2;
import de.flower.rmt.repository.IActivityRepo;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* @author flowerrrr
*/
@Service
@Transactional(readOnly = true, propagation = Propagation.REQUIRED)
public class ActivityManager extends AbstractService implements IActivityManager {
@Autowired
private IActivityRepo activityRepo;
@Autowired
private IInvitationManager invitationManager;
@Autowired
private IEventManager eventManager;
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void save(Activity entity) {
validate(entity);
activityRepo.save(entity);
}
private Activity newInstance() {
Activity entity = new Activity(getClub());
entity.setUser(securityService.getUser());
entity.setDate(new Date());
return entity;
}
@Override
public Activity loadById(final Long id) {
return activityRepo.findOne(id);
}
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void onCreateOrUpdateEvent(Event event, EventUpdateMessage.Type type) {
event = eventManager.loadById(event.getId(), Event_.team);
Activity entity = newInstance();
EventUpdateMessage message = new EventUpdateMessage(event);
message.setType(type);
message.setTeamName(event.getTeam().getName());
message.setManagerName(entity.getUser().getFullname());
entity.setMessage(message);
save(entity);
}
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void onLineupPublished(final Lineup lineup) {
Activity entity = newInstance();
EventUpdateMessage message = new EventUpdateMessage(lineup.getEvent());
message.setType(Type.LINEUP_PUBLISHED);
message.setManagerName(entity.getUser().getFullname());
entity.setMessage(message);
save(entity);
}
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void onInvitationMailSent(final Event event) {
Activity entity = newInstance();
EmailSentMessage message = new EmailSentMessage(event);
message.setManagerName(entity.getUser().getFullname());
entity.setMessage(message);
save(entity);
}
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void onInvitationUpdated(final Invitation invitation, final Invitation origInvitation, final String comment, final String origComment) {
Check.isTrue(invitation != origInvitation);
if (origInvitation.getEvent().isCanceled()) {
// RMT-676: avoid useless 'user xy is not attending event ...' messages.
return;
}
boolean changed = false;
InvitationUpdateMessage2 message = new InvitationUpdateMessage2(origInvitation.getEvent());
message.setInvitationUserName(origInvitation.getName());
message.setAuthorUserName(securityService.getUser().getFullname());
if (ObjectUtils.notEqual(invitation.getStatus(), origInvitation.getStatus())) {
message.setStatus(invitation.getStatus());
changed = true;
}
// do not track when comments are removed. what to display then?
if (!StringUtils.isBlank(comment) && ObjectUtils.notEqual(comment, origComment)) {
message.setComment(comment);
changed = true;
}
if (changed) {
Activity activity = newInstance();
activity.setMessage(message);
removeDuplicates(activity, message);
save(activity);
}
}
private void removeDuplicates(final Activity entity, final InvitationUpdateMessage2 message) {
// reading the activity table is always a bit unsafe due to the de-serialization errors
// that might occur when the message classes are changed. so better try/catch.
try {
Collection<Activity> list = findDuplicates(entity, message);
if (!list.isEmpty()) {
log.info("Removing duplicates {}", list);
activityRepo.delete(list);
}
} catch (Exception e) {
log.error("Error in removeDuplicates: " + e.getMessage(), e);
}
}
/**
* If user updates his comment twice, e.g. to correct a wrong spelling both submits
* will be listed in activity feed. that's not nice.
* <p/>
* <pre>
* Duplicates are identified:
* - by user
* - message type
* - event
* - username (as Activity.user might be the manager updating invitations on behalf of the users).
*
* </pre>
*/
private Collection<Activity> findDuplicates(final Activity entity, final InvitationUpdateMessage2 message) {
BooleanExpression isSameUser = QActivity.activity.user.eq(entity.getUser());
BooleanExpression isInsideOneHour = QActivity.activity.date.after(new DateTime().minusHours(1).toDate());
Collection<Activity> list = activityRepo.findAll(isSameUser.and(isInsideOneHour));
list = Collections2.filter(list, new Predicate<Activity>() {
@Override
public boolean apply(final Activity activity) {
Serializable msg = activity.getMessage();
if (msg instanceof InvitationUpdateMessage2) {
InvitationUpdateMessage2 ium = (InvitationUpdateMessage2) msg;
// event and user must match. event alone is not ok cause manager might update
// several invitations.
// equality of authorUserName is ensured by isSameUser expression above.
if (ium.getEventId().equals(message.getEventId())
&& ium.getInvitationUserName().equals(message.getInvitationUserName())) {
return true;
}
}
return false;
}
});
if (list.size() > 1) {
log.warn("Error in findDuplicates(): There should only be one duplicate.");
}
return list;
}
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void onCommentUpdated(final Comment comment, final Comment origComment) {
boolean changed = false;
Invitation invitation = invitationManager.loadById(comment.getInvitation().getId(), Invitation_.event, Invitation_.user);
InvitationUpdateMessage2 message = new InvitationUpdateMessage2(invitation.getEvent());
message.setInvitationUserName(invitation.getName());
message.setAuthorUserName(securityService.getUser().getFullname());
String text = comment.getText();
String origText = (origComment == null) ? null : origComment.getText();
// do not track when comments are removed. what to display then?
if (!StringUtils.isBlank(text) && ObjectUtils.notEqual(text, origText)) {
message.setComment(text);
changed = true;
}
if (changed) {
Activity activity = newInstance();
activity.setMessage(message);
removeDuplicates(activity, message);
save(activity);
}
}
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void onBArticleCreate(final BArticle article) {
BlogUpdateMessage message = new BlogUpdateMessage(article);
Activity activity = newInstance();
activity.setMessage(message);
save(activity);
}
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void onBCommentCreate(final BComment comment) {
BlogUpdateMessage message = new BlogUpdateMessage(comment.getArticle(), comment);
Activity activity = newInstance();
activity.setMessage(message);
save(activity);
}
@Override
public List<Activity> findLastN(final int page, final int size) {
return activityRepo.findAll(new PageRequest(page, size, Sort.Direction.DESC, Activity_.date.getName())).getContent();
}
}