Package in.partake.daemon.impl

Source Code of in.partake.daemon.impl.SendMessageEnvelopeTask

package in.partake.daemon.impl;

import in.partake.app.PartakeApp;
import in.partake.base.DateTime;
import in.partake.base.PartakeException;
import in.partake.base.TimeUtil;
import in.partake.base.Util;
import in.partake.daemon.IPartakeDaemonTask;
import in.partake.model.IPartakeDAOs;
import in.partake.model.UserEx;
import in.partake.model.access.Transaction;
import in.partake.model.dao.DAOException;
import in.partake.model.dao.DataIterator;
import in.partake.model.dao.PartakeConnection;
import in.partake.model.dao.access.IUserTwitterLinkAccess;
import in.partake.model.daofacade.EventDAOFacade;
import in.partake.model.daofacade.UserDAOFacade;
import in.partake.model.dto.Event;
import in.partake.model.dto.EventTicket;
import in.partake.model.dto.Message;
import in.partake.model.dto.MessageEnvelope;
import in.partake.model.dto.TwitterMessage;
import in.partake.model.dto.UserNotification;
import in.partake.model.dto.UserPreference;
import in.partake.model.dto.UserReceivedMessage;
import in.partake.model.dto.UserTwitterLink;
import in.partake.model.dto.auxiliary.MessageDelivery;
import in.partake.view.util.Helper;

import java.util.UUID;

import javax.servlet.http.HttpServletResponse;

import play.Logger;
import twitter4j.TwitterException;

class SendMessageEnvelopeTask extends Transaction<Void> implements IPartakeDaemonTask {
    @Override
    public String getName() {
        return "SendMessageEnvelopeTask";
    }

    @Override
    public void run() throws Exception {
        this.execute();
    }

    @Override
    protected Void doExecute(PartakeConnection con, IPartakeDAOs daos) throws DAOException, PartakeException {
        DataIterator<MessageEnvelope> it = daos.getMessageEnvelopeAccess().getIterator(con);
        try {
            while (it.hasNext()) {
                MessageEnvelope envelope = it.next();
                if (envelope == null) {
                    it.remove();
                    continue;
                }

                // InvalidAfter 後であれば、message を update して envelope を消去
                // TODO: Refine this code!
                if (envelope.getInvalidAfter() != null && envelope.getInvalidAfter().isBefore(TimeUtil.getCurrentDateTime())) {
                    Logger.warn("run : envelope id " + envelope.getId() + " could not be sent : Time out.");
                    if (envelope.getUserMessageId() != null) {
                        UserReceivedMessage userMessage = daos.getUserReceivedMessageAccess().find(con, UUID.fromString(envelope.getUserMessageId()));
                        if (userMessage != null) {
                            UserReceivedMessage message = new UserReceivedMessage(userMessage);
                            message.setDelivery(MessageDelivery.FAIL);
                            message.setModifiedAt(TimeUtil.getCurrentDateTime());
                            daos.getUserReceivedMessageAccess().put(con, message);
                        }
                    }

                    if (envelope.getTwitterMessageId() != null) {
                        TwitterMessage twitterMessage = daos.getTwitterMessageAccess().find(con, envelope.getTwitterMessageId());
                        if (twitterMessage != null) {
                            TwitterMessage message = new TwitterMessage(twitterMessage);
                            message.setDelivery(MessageDelivery.FAIL);
                            message.setModifiedAt(TimeUtil.getCurrentDateTime());
                            daos.getTwitterMessageAccess().put(con, message);
                        }
                    }

                    if (envelope.getUserNotificationId() != null) {
                        UserNotification notification = daos.getUserNotificationAccess().find(con, envelope.getUserNotificationId());
                        if (notification != null) {
                            UserNotification message = new UserNotification(notification);
                            message.setDelivery(MessageDelivery.FAIL);
                            message.setModifiedAt(TimeUtil.getCurrentDateTime());
                            daos.getUserNotificationAccess().put(con, message);
                        }
                    }

                    it.remove();
                    continue;
                }

                // tryAfter 前であれば送らない。
                if (envelope.getTryAfter() != null && !envelope.getTryAfter().isBefore(TimeUtil.getCurrentDateTime())) {
                    Logger.debug("run : envelope id " + envelope.getId() + " should be sent after " + envelope.getTryAfter());
                    continue;
                }

                if (envelope.getTwitterMessageId() != null)
                    sendTwitterMessage(con, daos, it, envelope);
                else if (envelope.getUserMessageId() != null)
                    sendUserMessage(con, daos, it, envelope);
                else if (envelope.getUserNotificationId() != null)
                    sendUserNotification(con, daos, it, envelope);
                else {
                    // Hmm... shouldn't happen.
                    Logger.error("Shouldn't happen");
                    assert false;
                    it.remove();
                }
            }
        } finally {
            it.close();
        }

        return null;
    }

    /**
     * Envelope を送信する。true を返すと送ることができた / もうこれ以上送ってはいけないという意味になる。
     * @param envelope
     * @return
     */
    private void sendUserMessage(PartakeConnection con, IPartakeDAOs daos, DataIterator<MessageEnvelope> it, MessageEnvelope envelope) throws DAOException {
        UserReceivedMessage userMessage = daos.getUserReceivedMessageAccess().find(con, UUID.fromString(envelope.getUserMessageId()));
        if (userMessage == null) {
            didSendUserMessage(con, daos, it, envelope, userMessage, MessageDelivery.FAIL);
            return;
        }

        UserEx receiver = UserDAOFacade.getUserEx(con, daos, userMessage.getReceiverId());
        if (receiver == null) {
            didSendUserMessage(con, daos, it, envelope, userMessage, MessageDelivery.FAIL);
            return;
        }

        UserPreference pref = daos.getUserPreferenceAccess().find(con, receiver.getId());
        if (pref == null)
            pref = UserPreference.getDefaultPreference(receiver.getId());

        // twitter message を受け取らない設定になっていれば送らない。
        if (!pref.isReceivingTwitterMessage()) {
            didSendUserMessage(con, daos, it, envelope, userMessage, MessageDelivery.NOT_DELIVERED);
            return;
        }

        UserTwitterLink twitterLinkage = receiver.getTwitterLinkage();
        if (twitterLinkage == null || !twitterLinkage.isAuthorized()) {
            Logger.warn("sendDirectMessage : envelope id " + envelope.getId() + " could not be sent : No access token");
            didSendUserMessage(con, daos, it, envelope, userMessage, MessageDelivery.FAIL);
            return;
        }

        try {
            Message message = daos.getMessageAccess().find(con, UUID.fromString(userMessage.getMessageId()));
            long twitterId = twitterLinkage.getTwitterId();

            Event event = null;
            if (userMessage.getEventId() != null)
                event = daos.getEventAccess().find(con, userMessage.getEventId());

            String url = "http://partake.in/messages/" + userMessage.getId();
            String messageBody;
            if (event != null) {
                int rest = 140;
                String format = "[PRTK] %s 「%s」に関する新着メッセージがあります。 : %s";
                rest -= Util.codePointCount(format);

                rest -= EventDAOFacade.URL_LENGTH;

                String title = Util.shorten(event.getTitle(), 30);
                rest -= Util.codePointCount(title);

                String body = Util.shorten(message.getSubject(), rest);
                messageBody = String.format(format, url, title, body);
            } else {
                int rest = 140;

                String format = "[PRTK] %s 新着メッセージがあります。: %s";
                rest -= Util.codePointCount(format);

                rest -= EventDAOFacade.URL_LENGTH;

                String subject = Util.shorten(message.getSubject(), rest);

                messageBody = String.format(format, url, subject);
            }

            PartakeApp.getTwitterService().sendDirectMesage(
                    twitterLinkage.getAccessToken(), twitterLinkage.getAccessTokenSecret(), twitterId, messageBody);
            didSendUserMessage(con, daos, it, envelope, userMessage, MessageDelivery.SUCCESS);
            Logger.info("sendDirectMessage : direct message has been sent to " + twitterLinkage.getScreenName());
        } catch (NumberFormatException e) {
            Logger.error("twitterId has not a number.", e);
            didSendUserMessage(con, daos, it, envelope, userMessage, MessageDelivery.FAIL);
        } catch (TwitterException e) {
            if (updateEnvelopeByTwitterException(con, daos, receiver, envelope, it, e))
                didSendUserMessage(con, daos, it, envelope, userMessage, MessageDelivery.FAIL);
        }
    }

    private void didSendUserMessage(PartakeConnection con, IPartakeDAOs daos,
            DataIterator<MessageEnvelope> it, MessageEnvelope envelope, UserReceivedMessage message, MessageDelivery delivery) throws DAOException {
        UserReceivedMessage userMessage = new UserReceivedMessage(message);
        userMessage.setDelivery(delivery);
        userMessage.setModifiedAt(TimeUtil.getCurrentDateTime());

        daos.getUserReceivedMessageAccess().put(con, userMessage);
        it.remove();
    }

    private void sendTwitterMessage(PartakeConnection con, IPartakeDAOs daos, DataIterator<MessageEnvelope> it, MessageEnvelope envelope) throws DAOException {
        TwitterMessage message = daos.getTwitterMessageAccess().find(con, envelope.getTwitterMessageId());

        if (message == null) {
            Logger.warn("SendMessageEnvelopeTask.sendTwitterMessage : message was null.");

            // Since the message was null, we cannot update the message status. So we silently remove this MessageEnvelope.
            it.remove();
            return;
        }

        UserEx sender = UserDAOFacade.getUserEx(con, daos, message.getUserId());
        if (sender == null) {
            Logger.warn("sendTwitterMessage : sender is null.");
            failedSendingTwitterMessage(con, daos, it, envelope, message);
            return;
        }

        UserTwitterLink twitterLinkage = sender.getTwitterLinkage();
        if (twitterLinkage == null || !twitterLinkage.isAuthorized()) {
            Logger.warn("sendTwitterMessage : envelope id " + envelope.getId() + " could not be sent : No access token");
            failedSendingTwitterMessage(con, daos, it, envelope, message);
            return;
        }

        try {
            PartakeApp.getTwitterService().updateStatus(twitterLinkage.getAccessToken(), twitterLinkage.getAccessTokenSecret(), message.getMessage());
            succeededSendingTwitterMessage(con, daos, it, envelope, message);
            return;
        } catch (TwitterException e) {
            if (updateEnvelopeByTwitterException(con, daos, sender, envelope, it, e))
                failedSendingTwitterMessage(con, daos, it, envelope, message);
        }
    }

    private void succeededSendingTwitterMessage(PartakeConnection con, IPartakeDAOs daos, DataIterator<MessageEnvelope> it, MessageEnvelope envelope, TwitterMessage message) throws DAOException {
        TwitterMessage twitterMessage = new TwitterMessage(message);
        twitterMessage.setDelivery(MessageDelivery.SUCCESS);
        twitterMessage.setModifiedAt(TimeUtil.getCurrentDateTime());

        daos.getTwitterMessageAccess().put(con, twitterMessage);
        it.remove();
    }

    private void failedSendingTwitterMessage(PartakeConnection con, IPartakeDAOs daos, DataIterator<MessageEnvelope> it, MessageEnvelope envelope, TwitterMessage message) throws DAOException {
        TwitterMessage twitterMessage = new TwitterMessage(message);
        twitterMessage.setDelivery(MessageDelivery.FAIL);
        twitterMessage.setModifiedAt(TimeUtil.getCurrentDateTime());

        daos.getTwitterMessageAccess().put(con, twitterMessage);
        it.remove();
    }

    private void sendUserNotification(PartakeConnection con, IPartakeDAOs daos, DataIterator<MessageEnvelope> it, MessageEnvelope envelope) throws DAOException {
        UserNotification notification = daos.getUserNotificationAccess().find(con, envelope.getUserNotificationId());
        if (notification == null) {
            failedSendingUserNotification(con, daos, it, envelope, notification);
            return;
        }

        UserEx sender = UserDAOFacade.getUserEx(con, daos, notification.getUserId());
        if (sender == null) {
            Logger.warn("sendTwitterMessage : sender is null.");
            failedSendingUserNotification(con, daos, it, envelope, notification);
            return;
        }

        UserTwitterLink twitterLinkage = sender.getTwitterLinkage();
        if (twitterLinkage == null || !twitterLinkage.isAuthorized()) {
            Logger.warn("sendTwitterMessage : envelope id " + envelope.getId() + " could not be sent : No access token");
            failedSendingUserNotification(con, daos, it, envelope, notification);
            return;
        }

        EventTicket ticket = daos.getEventTicketAccess().find(con, notification.getTicketId());
        if (ticket == null) {
            failedSendingUserNotification(con, daos, it, envelope, notification);
            return;
        }

        Event event = daos.getEventAccess().find(con, ticket.getEventId());
        if (event == null) {
            failedSendingUserNotification(con, daos, it, envelope, notification);
            return;
        }

        String messageBody = buildUserNotificationMessageBody(notification, event, ticket);
        assert messageBody != null;
        if (messageBody == null) {
            failedSendingUserNotification(con, daos, it, envelope, notification);
            return;
        }

        try {
            PartakeApp.getTwitterService().sendDirectMesage(
                    twitterLinkage.getAccessToken(), twitterLinkage.getAccessTokenSecret(), twitterLinkage.getTwitterId(), messageBody);
            succeededSendingUserNotification(con, daos, it, envelope, notification);
            return;
        } catch (TwitterException e) {
            if (updateEnvelopeByTwitterException(con, daos, sender, envelope, it, e))
                failedSendingUserNotification(con, daos, it, envelope, notification);
        }
    }

    static String buildUserNotificationMessageBody(UserNotification notification, Event event, EventTicket ticket) {
        switch (notification.getNotificationType()) {
        case EVENT_ONEDAY_BEFORE_REMINDER: {
            int rest = 140;
            String format = "[PRTK] 「%s」は%sに開始です。あなたの参加は確定しています。 %s";
            rest -= Util.codePointCount(format);

            String beginDate = Helper.readableDate(event.getBeginDate());
            rest -= Util.codePointCount(beginDate);

            String url = event.getEventURL();
            rest -= EventDAOFacade.URL_LENGTH;

            String title = Util.shorten(event.getTitle(), rest);
            return String.format(format, title, beginDate, url);
        }
        case HALF_DAY_BEFORE_REMINDER_FOR_RESERVATION: {
            int rest = 140;
            String format = "[PRTK] 「%s」の締め切りは%sです。参加・不参加を確定してください。 %s";
            rest -= Util.codePointCount(format);

            String deadline = Helper.readableDate(ticket.acceptsTill(event));
            rest -= Util.codePointCount(deadline);

            String url = event.getEventURL();
            rest -= EventDAOFacade.URL_LENGTH;

            String title = Util.shorten(event.getTitle(), rest);
            return String.format(format, title, deadline, url);
        }
        case ONE_DAY_BEFORE_REMINDER_FOR_RESERVATION: {
            int rest = 140;
            String format = "[PRTK] 「%s」の締め切りは%sです。参加・不参加を確定してください。 %s";
            rest -= Util.codePointCount(format);

            String deadline = Helper.readableDate(ticket.acceptsTill(event));
            rest -= Util.codePointCount(deadline);

            String url = event.getEventURL();
            rest -= EventDAOFacade.URL_LENGTH;

            String title = Util.shorten(event.getTitle(), rest);
            return String.format(format, title, deadline, url);
        }
        case BECAME_TO_BE_CANCELLED: {
            int rest = 140;
            String format = "[PRTK] 「%s」で参加者から補欠へ繰り下がりました。 %s";
            rest -= Util.codePointCount(format);

            String url = event.getEventURL();
            rest -= EventDAOFacade.URL_LENGTH;

            String title = Util.shorten(event.getTitle(), rest);
            return String.format(format, title, url);
        }
        case BECAME_TO_BE_ENROLLED: {
            int rest = 140;
            String format = "[PRTK] 「%s」で補欠から参加者へ繰り上がりました。 %s";
            rest -= Util.codePointCount(format);

            String url = event.getEventURL();
            rest -= EventDAOFacade.URL_LENGTH;

            String title = Util.shorten(event.getTitle(), rest);
            return String.format(format, title, url);
        }
        default:
            assert false;
            return null;
        }
    }

    private void succeededSendingUserNotification(PartakeConnection con, IPartakeDAOs daos, DataIterator<MessageEnvelope> it, MessageEnvelope envelope, UserNotification notification) throws DAOException {
        UserNotification userNotification = new UserNotification(notification);
        userNotification.setDelivery(MessageDelivery.SUCCESS);
        userNotification.setModifiedAt(TimeUtil.getCurrentDateTime());

        daos.getUserNotificationAccess().put(con, userNotification);
        it.remove();
    }

    private void failedSendingUserNotification(PartakeConnection con, IPartakeDAOs daos, DataIterator<MessageEnvelope> it, MessageEnvelope envelope, UserNotification notification) throws DAOException {
        UserNotification userNotification = new UserNotification(notification);
        userNotification.setDelivery(MessageDelivery.FAIL);
        userNotification.setModifiedAt(TimeUtil.getCurrentDateTime());

        daos.getUserNotificationAccess().put(con, userNotification);
        it.remove();
    }

    /**
     * @return true if <code>it</code> was removed.
     */
    private boolean updateEnvelopeByTwitterException(PartakeConnection con, IPartakeDAOs daos,
            UserEx user, MessageEnvelope envelope, DataIterator<MessageEnvelope> it, TwitterException e) throws DAOException {
        if (e.isCausedByNetworkIssue()) {
            Logger.warn("Twitter Unreachable?", e);
            // Retry after 10 minutes later.
            DateTime retryAfter = new DateTime(TimeUtil.getCurrentTime() + 600 * 1000);
            MessageEnvelope newEnvelope = new MessageEnvelope(envelope);
            newEnvelope.updateForSendingFailure(retryAfter);
            it.update(newEnvelope);
            return false;
        }

        if (e.exceededRateLimitation()) {
            Logger.warn("Twitter Rate Limination : " + envelope.getId() + " was failed to deliver.", e);
            DateTime retryAfter = new DateTime(TimeUtil.getCurrentTime() + e.getRetryAfter() * 1000);

            MessageEnvelope newEnvelope = new MessageEnvelope(envelope);
            newEnvelope.updateForSendingFailure(retryAfter);
            it.update(newEnvelope);
            return false;
        }

        if (e.getStatusCode() == HttpServletResponse.SC_UNAUTHORIZED) {
            markAsUnauthorizedUser(con, daos, user);
            Logger.info("Unauthorized User : " + envelope.getId() + " was failed to deliver.", e);

            // We cannot send envelopes to unauthorized user.
            it.remove();
            return true;
        }

        // Unknown error. Retry.
        Logger.warn("Unknown Error : " + envelope.getId() + " was failed to deliver.", e);
        // Retry after 5 minutes later.
        DateTime retryAfter = new DateTime(TimeUtil.getCurrentTime() + 600 * 1000);
        MessageEnvelope newEnvelope = new MessageEnvelope(envelope);
        newEnvelope.updateForSendingFailure(retryAfter);
        it.update(newEnvelope);
        return false;
    }

    private void markAsUnauthorizedUser(PartakeConnection con, IPartakeDAOs daos, UserEx user) {
        IUserTwitterLinkAccess access = daos.getTwitterLinkageAccess();
        UserTwitterLink linkage = new UserTwitterLink(user.getTwitterLinkage());
        linkage.markAsUnauthorized();

        try {
            // TODO UserExが参照するTwitterLinkageが更新されたため、UserExのキャッシュを破棄あるいは更新する必要がある
            access.put(con, linkage);
        } catch (DAOException ignore) {
            Logger.warn("DAOException is thrown but it's ignored.", ignore);
        }
    }
}
TOP

Related Classes of in.partake.daemon.impl.SendMessageEnvelopeTask

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.