org.vaadin.tori.service.LiferayToriMailService.java Source code

Java tutorial

Introduction

Here is the source code for org.vaadin.tori.service.LiferayToriMailService.java

Source

/*
 * Copyright 2014 Vaadin Ltd.
 * 
 * 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 org.vaadin.tori.service;

import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

import javax.mail.internet.InternetAddress;
import javax.portlet.PortletPreferences;
import javax.portlet.PortletRequest;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.log4j.Logger;
import org.fit.cssbox.css.CSSNorm;
import org.fit.cssbox.css.DOMAnalyzer;
import org.jsoup.Jsoup;
import org.vaadin.tori.PortletRequestAware;
import org.vaadin.tori.data.LiferayDataSource;
import org.vaadin.tori.data.entity.LiferayEntityFactoryUtil;
import org.vaadin.tori.util.DOMBuilder;
import org.vaadin.tori.util.ToriMailService;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import com.liferay.mail.service.MailServiceUtil;
import com.liferay.portal.NoSuchUserException;
import com.liferay.portal.kernel.exception.NestableException;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.mail.Account;
import com.liferay.portal.kernel.mail.MailMessage;
import com.liferay.portal.kernel.mail.SMTPAccount;
import com.liferay.portal.kernel.util.PropsKeys;
import com.liferay.portal.kernel.util.PropsUtil;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.WebKeys;
import com.liferay.portal.model.Company;
import com.liferay.portal.model.Subscription;
import com.liferay.portal.model.User;
import com.liferay.portal.service.CompanyLocalServiceUtil;
import com.liferay.portal.service.ServiceContext;
import com.liferay.portal.service.ServiceContextFactory;
import com.liferay.portal.service.SubscriptionLocalServiceUtil;
import com.liferay.portal.service.UserLocalServiceUtil;
import com.liferay.portal.theme.ThemeDisplay;
import com.liferay.portlet.PortletPreferencesFactoryUtil;
import com.liferay.portlet.messageboards.model.MBMailingList;
import com.liferay.portlet.messageboards.model.MBMessage;
import com.liferay.portlet.messageboards.model.MBMessageConstants;
import com.liferay.portlet.messageboards.model.MBThread;
import com.liferay.portlet.messageboards.service.MBMailingListLocalServiceUtil;
import com.liferay.portlet.messageboards.service.MBMessageLocalServiceUtil;

public class LiferayToriMailService implements ToriMailService, PortletRequestAware {

    private String mailTemplateHtml;
    private String mailThemeCss;
    private String imagePath;
    private ServiceContext mbMessageServiceContext;
    private PortletRequest request;

    @Override
    public void setMailTheme(final String mailThemeCss) {
        this.mailThemeCss = mailThemeCss;
    }

    @Override
    public void setPostMailTemplate(final String mailTemplateHtml) {
        this.mailTemplateHtml = mailTemplateHtml;
    }

    private SMTPAccount getSMTPAccout(final MBMessage mbMessage) {
        SMTPAccount account = null;
        try {

            MBMailingList mailingList = MBMailingListLocalServiceUtil.getCategoryMailingList(mbMessage.getGroupId(),
                    mbMessage.getCategoryId());
            if (mailingList.isOutCustom()) {
                String protocol = Account.PROTOCOL_SMTP;

                if (mailingList.isOutUseSSL()) {
                    protocol = Account.PROTOCOL_SMTPS;
                }

                account = (SMTPAccount) Account.getInstance(protocol, mailingList.getOutServerPort());

                account.setHost(mailingList.getOutServerName());
                account.setUser(mailingList.getOutUserName());
                account.setPassword(mailingList.getOutPassword());
            }
        } catch (NestableException e) {
            getLogger().warn("Exception while determining SMTPAccount", e);
        }
        return account;
    }

    private String formMailBody(final MBMessage mbMessage, final String formattedPostBody)
            throws IOException, SAXException, PortalException, SystemException {

        User user = null;
        String avatarUrl = "";
        try {
            user = UserLocalServiceUtil.getUser(mbMessage.getUserId());
            String userAvatarUrl = LiferayEntityFactoryUtil.getAvatarUrl(user.getPortraitId(), imagePath,
                    user.isFemale());
            if (userAvatarUrl != null) {
                avatarUrl = mbMessageServiceContext.getPortalURL() + userAvatarUrl;
            }
        } catch (NestableException e) {
            // Ignore
        }

        MBMessage rootMessage = MBMessageLocalServiceUtil.getMessage(mbMessage.getThread().getRootMessageId());
        String threadTopic = rootMessage.getSubject();
        threadTopic = stripTags(threadTopic);

        String userDisplayName = user != null ? user.getFullName() : "Anonymous";
        if (user != null && LiferayEntityFactoryUtil.usesScreennameOnTori(user)) {
            userDisplayName = user.getScreenName();
        }
        userDisplayName = stripTags(userDisplayName);

        String headerImage = getPreferenceValue(LiferayDataSource.PREFS_EMAIL_HEADER_IMAGE_URL, null);

        String threadUrl = mbMessageServiceContext.getLayoutFullURL() + "#!/thread/" + mbMessage.getThreadId();

        String permaLink = threadUrl + "/" + mbMessage.getMessageId();

        String postHtml = populateTemplate(mailTemplateHtml, avatarUrl, threadTopic, userDisplayName,
                formattedPostBody, headerImage, threadUrl, permaLink);
        return formatInlineCSS(postHtml, mailThemeCss);

    }

    static String populateTemplate(final String htmlTemplate, final String avatarUrl, final String threadTopic,
            final String userDisplayName, final String bodyFormatted, final String headerImage,
            final String threadUrl, final String permaLink) {
        // @formatter:off
        return StringUtil.replace(htmlTemplate,
                new String[] { "[$MESSAGE_USER_AVATAR_URL$]", "[$MESSAGE_USER_NAME$]", "[$MESSAGE_BODY$]",
                        "[$MESSAGE_THREAD_TOPIC$]", "[$MESSAGE_HEADER_IMAGE$]", "[$MESSAGE_THREAD_URL$]",
                        "[$MESSAGE_PERMALINK$]", "[$MESSAGE_USER_ANONYMOUS$]", "[$MESSAGE_HEADER_DEFAULT_IMAGE$]" },
                new String[] { avatarUrl, userDisplayName, bodyFormatted, threadTopic,
                        headerImage != null ? headerImage : "", threadUrl, permaLink,
                        Boolean.toString(avatarUrl == null || avatarUrl.isEmpty()),
                        Boolean.toString(headerImage == null || headerImage.isEmpty()) });
        // @formatter:on
    }

    static String formatInlineCSS(final String html, final String css) throws IOException, SAXException {
        org.jsoup.nodes.Document parsed = Jsoup.parse(html, "UTF-8");
        parsed.outputSettings().charset("UTF-8");
        Document doc = DOMBuilder.jsoup2DOM(parsed);

        DOMAnalyzer da = new DOMAnalyzer(doc);
        da.attributesToStyles();
        da.addStyleSheet(null, CSSNorm.stdStyleSheet(), DOMAnalyzer.Origin.AGENT);
        da.addStyleSheet(null, css, null);

        da.getStyleSheets();

        da.stylesToDomInherited();

        String result = toString(doc);

        result = result.replaceAll("class=\"topiclinkwrapper\" style=\"",
                "class=\"topiclinkwrapper\" style=\"text-overflow: ellipsis;");

        // Remove all line breaks
        result = result.replaceAll("\\n", "");

        return result;
    }

    public static String toString(final Document doc) {
        try {
            StringWriter sw = new StringWriter();
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");

            transformer.transform(new DOMSource(doc), new StreamResult(sw));
            return sw.toString();
        } catch (Exception ex) {
            throw new RuntimeException("Error converting to String", ex);
        }
    }

    protected InternetAddress[] parseAddresses(final MBMessage mbMessage) {

        List<Subscription> subscriptions = new ArrayList<Subscription>();

        // Threads
        try {
            subscriptions.addAll(SubscriptionLocalServiceUtil.getSubscriptions(mbMessage.getCompanyId(),
                    MBThread.class.getName(), mbMessage.getThreadId()));
        } catch (SystemException e1) {
            getLogger().warn("Unable to get thread subscriptions for thread " + mbMessage.getThreadId(), e1);
        }

        Collection<Long> sent = new HashSet<Long>();
        List<InternetAddress> addresses = new ArrayList<InternetAddress>();

        for (Subscription subscription : subscriptions) {
            long subscribedUserId = subscription.getUserId();

            // Don't send email to the message author
            if (subscribedUserId == mbMessage.getUserId() || sent.contains(subscribedUserId)) {
                continue;
            } else {
                sent.add(subscribedUserId);
            }

            try {
                User user = UserLocalServiceUtil.getUserById(subscribedUserId);
                if (user.isActive()) {
                    InternetAddress userAddress = new InternetAddress(user.getEmailAddress(), user.getFullName());

                    addresses.add(userAddress);
                }
            } catch (NoSuchUserException nsue) {
                if (getLogger().isInfoEnabled()) {
                    getLogger().info(
                            "Subscription " + subscription.getSubscriptionId() + " is stale and will be deleted");
                }

                try {
                    SubscriptionLocalServiceUtil.deleteSubscription(subscription.getSubscriptionId());
                } catch (NestableException e) {
                    getLogger().warn("Unable to delete subscription", e);
                }
            } catch (NestableException e) {
                getLogger().warn("Unable to parse address for userId " + subscribedUserId, e);
            } catch (UnsupportedEncodingException e) {
                getLogger().warn("Unable to parse address for userId " + subscribedUserId, e);
            }
        }

        return addresses.toArray(new InternetAddress[addresses.size()]);

    }

    @Override
    public void sendUserAuthored(final long postId, final String formattedPostBody) {
        try {
            MBMessage mbMessage = MBMessageLocalServiceUtil.getMBMessage(postId);

            InternetAddress[] bulkAddresses = parseAddresses(mbMessage);

            if (bulkAddresses.length > 0) {
                String mailId = getMailId(mbMessage.getCompanyId(), mbMessage.getCategoryId(),
                        mbMessage.getMessageId());
                String body = formMailBody(mbMessage, formattedPostBody);

                String subject = "[" + mbMessage.getCategory().getName() + "] " + mbMessage.getSubject();
                Company company = CompanyLocalServiceUtil.getCompany(mbMessage.getCompanyId());
                String companyEmail = company.getEmailAddress();

                String fromAddress = getPreferenceValue(LiferayDataSource.PREFS_EMAIL_FROM_ADDRESS, null);
                if (fromAddress == null) {
                    fromAddress = companyEmail;
                }

                String fromName = getPreferenceValue(LiferayDataSource.PREFS_EMAIL_FROM_NAME, null);
                if (fromName == null) {
                    fromName = company.getName() + " forums";
                }

                String replyToAddress = getPreferenceValue(LiferayDataSource.PREFS_EMAIL_REPLY_TO_ADDRESS, null);
                if (replyToAddress == null) {
                    replyToAddress = fromAddress;
                }

                SMTPAccount account = getSMTPAccout(mbMessage);

                String inReplyTo = null;
                if (mbMessage.getParentMessageId() != MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID) {
                    inReplyTo = getMailId(mbMessage.getCompanyId(), mbMessage.getCategoryId(),
                            mbMessage.getParentMessageId());
                }

                InternetAddress from = new InternetAddress(fromAddress, fromName);

                InternetAddress to = new InternetAddress(replyToAddress, replyToAddress);

                InternetAddress replyTo = new InternetAddress(replyToAddress, replyToAddress);

                MailMessage message = new MailMessage(from, to, subject, body, true);
                message.setBulkAddresses(bulkAddresses);
                message.setMessageId(mailId);
                message.setInReplyTo(inReplyTo);
                message.setReplyTo(new InternetAddress[] { replyTo });
                message.setSMTPAccount(account);

                MailServiceUtil.sendEmail(message);
            }
        } catch (Exception e) {
            getLogger().warn("Unable to form email notification", e);
        }
    }

    public static final String POP_PORTLET_PREFIX = "mb.";

    private static String getMailId(final long companyId, final long categoryId, final long messageId)
            throws PortalException, SystemException {

        Company company = CompanyLocalServiceUtil.getCompany(companyId);
        String mx = company.getMx();
        StringBundler sb = new StringBundler(10);

        sb.append(StringPool.LESS_THAN);
        sb.append(POP_PORTLET_PREFIX);
        sb.append(categoryId);
        sb.append(StringPool.PERIOD);
        sb.append(messageId);
        sb.append(StringPool.AT);

        String sd = PropsUtil.get(PropsKeys.POP_SERVER_SUBDOMAIN);
        if (sd != null && !"null".equals(sd.toLowerCase())) {
            sb.append(sd);
            sb.append(StringPool.PERIOD);
        }

        sb.append(mx);
        sb.append(StringPool.GREATER_THAN);

        return sb.toString();
    }

    private Logger getLogger() {
        return Logger.getLogger(LiferayToriMailService.class);
    }

    @Override
    public void setRequest(final PortletRequest request) {
        this.request = request;

        try {
            imagePath = ((ThemeDisplay) request.getAttribute(WebKeys.THEME_DISPLAY)).getPathImage();
            mbMessageServiceContext = ServiceContextFactory.getInstance(MBMessage.class.getName(), request);
        } catch (NestableException e) {
            getLogger().error("Unable to initialize mail service", e);
        } catch (NullPointerException e) {
            getLogger().error("Unable to initialize mail service", e);
        }
    }

    private String getPreferenceValue(final String preferenceKey, final String defaultValue) {
        String result = defaultValue;
        PortletPreferences portletPreferences;
        try {
            portletPreferences = PortletPreferencesFactoryUtil.getPortletSetup(request);
            result = portletPreferences.getValue(preferenceKey, defaultValue);
        } catch (NestableException e) {
            getLogger().error("Unable to get PortletPreferences", e);
            e.printStackTrace();
        }
        return result;
    }

    private String stripTags(final String html) {
        return html.replaceAll("\\<.*?>", "");
    }

}