org.xwiki.contrib.mailarchive.timeline.internal.TimeLineGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.contrib.mailarchive.timeline.internal.TimeLineGenerator.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.contrib.mailarchive.timeline.internal;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.jfree.util.Log;
import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.phase.Initializable;
import org.xwiki.component.phase.InitializationException;
import org.xwiki.context.Execution;
import org.xwiki.context.ExecutionContext;
import org.xwiki.contrib.mailarchive.IMAUser;
import org.xwiki.contrib.mailarchive.IMailArchiveConfiguration;
import org.xwiki.contrib.mailarchive.IMailingList;
import org.xwiki.contrib.mailarchive.IType;
import org.xwiki.contrib.mailarchive.exceptions.MailArchiveException;
import org.xwiki.contrib.mailarchive.timeline.ITimeLineGenerator;
import org.xwiki.contrib.mailarchive.utils.DecodedMailContent;
import org.xwiki.contrib.mailarchive.utils.IMailUtils;
import org.xwiki.contrib.mailarchive.utils.ITextUtils;
import org.xwiki.contrib.mailarchive.xwiki.internal.XWikiPersistence;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.query.Query;
import org.xwiki.query.QueryException;
import org.xwiki.query.QueryManager;
import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter;

import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseObject;

/**
 * @version $Id$
 */
@Component
@Singleton
public class TimeLineGenerator implements Initializable, ITimeLineGenerator {

    public static final int DEFAULT_MAX_ITEMS = 200;

    @Inject
    private IMailArchiveConfiguration config;

    @Inject
    private Logger logger;

    @Inject
    private QueryManager queryManager;

    /** Provides access to the request context. */
    @Inject
    public Execution execution;

    private XWiki xwiki;

    private XWikiContext context;

    @Inject
    private DocumentReferenceResolver<String> docResolver;

    @Inject
    @Named("simile")
    ITimeLineDataWriter timelineWriter;

    @Inject
    private IMailUtils mailUtils;

    @Inject
    private ITextUtils textUtils;

    private String userStatsUrl = "";

    /**
     * {@inheritDoc}
     * 
     * @see org.xwiki.component.phase.Initializable#initialize()
     */
    @Override
    public void initialize() throws InitializationException {
        ExecutionContext context = execution.getContext();
        this.context = (XWikiContext) context.getProperty("xwikicontext");
        this.xwiki = this.context.getWiki();

    }

    public String compute() {
        return compute(Integer.MAX_VALUE);
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.xwiki.contrib.mailarchive.timeline.ITimeLineGenerator#compute()
     */
    @Override
    public String compute(int maxItems) {
        try {
            config.reloadConfiguration();
        } catch (MailArchiveException e) {
            logger.error("Could not load mail archive configuration", e);
            return null;
        }
        Map<String, IMailingList> mailingLists = config.getMailingLists();
        Map<String, IType> types = config.getMailTypes();

        try {
            this.userStatsUrl = xwiki.getDocument(docResolver.resolve("MailArchive.ViewUserMessages"), context)
                    .getURL("view", context);
        } catch (XWikiException e1) {
            logger.warn("Could not retrieve user stats url {}", ExceptionUtils.getRootCauseMessage(e1));
        }

        TreeMap<Long, TimeLineEvent> sortedEvents = new TreeMap<Long, TimeLineEvent>();

        // Set loading user in context (for rights)
        String loadingUser = config.getLoadingUser();
        context.setUserReference(docResolver.resolve(loadingUser));

        try {
            // Search topics
            String xwql = "select doc.fullName, topic.author, topic.subject, topic.topicid, topic.startdate, topic.lastupdatedate from Document doc, doc.object("
                    + XWikiPersistence.CLASS_TOPICS + ") as topic order by topic.lastupdatedate desc";
            List<Object[]> result = queryManager.createQuery(xwql, Query.XWQL).setLimit(maxItems).execute();

            for (Object[] item : result) {
                XWikiDocument doc = null;
                try {
                    String docurl = (String) item[0];
                    String author = (String) item[1];
                    String subject = (String) item[2];
                    String topicId = (String) item[3];
                    Date date = (Date) item[4];
                    Date end = (Date) item[5];

                    String action = "";

                    // Retrieve associated emails
                    TreeMap<Long, TopicEventBubble> emails = getTopicMails(topicId, subject);

                    if (emails == null || emails.isEmpty()) {
                        // Invalid topic, not emails attached, do not show it
                        logger.warn("Invalid topic, no emails attached " + doc);
                    } else {
                        if (date != null && end != null && date.equals(end)) {
                            // Add 10 min just to see the tape
                            end.setTime(end.getTime() + 600000);
                        }

                        doc = xwiki.getDocument(docResolver.resolve(docurl), context);
                        final List<String> tagsList = doc.getTagsList(context);
                        List<String> topicTypes = doc.getListValue(XWikiPersistence.CLASS_TOPICS, "type");

                        // Email type icon
                        List<String> icons = new ArrayList<String>();
                        for (String topicType : topicTypes) {
                            IType type = types.get(topicType);
                            if (type != null && !StringUtils.isEmpty(type.getIcon())) {
                                icons.add(xwiki.getSkinFile("icons/silk/" + type.getIcon() + ".png", context));
                                // http://localhost:8080/xwiki/skins/colibri/icons/silk/bell
                                // http://localhost:8080/xwiki/resources/icons/silk/bell.png

                            }
                        }

                        // Author and avatar
                        final IMAUser wikiUser = mailUtils.parseUser(author, config.isMatchLdap());
                        final String authorAvatar = getAuthorAvatar(wikiUser.getWikiProfile());

                        final TimeLineEvent timelineEvent = new TimeLineEvent();
                        TimeLineEvent additionalEvent = null;
                        timelineEvent.beginDate = date;
                        timelineEvent.title = subject;
                        timelineEvent.icons = icons;
                        timelineEvent.lists = doc.getListValue(XWikiPersistence.CLASS_TOPICS, "list");
                        timelineEvent.author = wikiUser.getDisplayName();
                        timelineEvent.authorAvatar = authorAvatar;
                        timelineEvent.extract = getExtract(topicId);

                        if (emails.size() == 1) {
                            logger.debug("Adding instant event for email '" + subject + "'");
                            // Unique email, we show a punctual email event
                            timelineEvent.url = emails.firstEntry().getValue().link;
                            timelineEvent.action = "New Email ";

                        } else {
                            // For email with specific type icon, and a duration, both a band and a point should be added (so 2 events)
                            // because no icon is displayed for duration events.
                            if (CollectionUtils.isNotEmpty(icons)) {
                                logger.debug(
                                        "Adding additional instant event to display type icon for first email in topic");
                                additionalEvent = new TimeLineEvent(timelineEvent);
                                additionalEvent.url = emails.firstEntry().getValue().link;
                                additionalEvent.action = "New Email ";
                            }

                            // Email thread, we show a topic event (a range)
                            logger.debug("Adding duration event for topic '" + subject + "'");
                            timelineEvent.endDate = end;
                            timelineEvent.url = doc.getURL("view", context);
                            timelineEvent.action = "New Topic ";
                            timelineEvent.messages = emails;
                        }

                        // Add the generated Event to the list
                        if (sortedEvents.containsKey(date.getTime())) {
                            // Avoid having more than 1 event at exactly the same time, because some timeline don't like it
                            date.setTime(date.getTime() + 1);
                        }
                        sortedEvents.put(date.getTime(), timelineEvent);
                        if (additionalEvent != null) {
                            sortedEvents.put(date.getTime() + 1, additionalEvent);
                        }
                    }

                } catch (Throwable t) {
                    logger.warn("Exception for " + doc, t);
                }

            }

        } catch (Throwable e) {
            logger.warn("could not compute timeline data", e);
        }

        return printEvents(sortedEvents);

    }

    public String toString(TreeMap<Long, TimeLineEvent> sortedEvents) {
        return printEvents(sortedEvents);
    }

    private String printEvents(TreeMap<Long, TimeLineEvent> sortedEvents) {

        DefaultWikiPrinter printer = new DefaultWikiPrinter();
        timelineWriter.setWikiPrinter(printer);
        timelineWriter.print(sortedEvents);

        logger.debug("Loaded " + sortedEvents.size() + " into Timeline feed");
        logger.debug("Timeline data {}", printer.toString());
        return printer.toString();

    }

    /**
     * Formats a timeline bubble content, ie html presenting list of mails related to a given topic.
     * 
     * @param topicid
     * @param topicsubject
     * @return
     * @throws QueryException
     * @throws XWikiException
     */
    protected TreeMap<Long, TopicEventBubble> getTopicMails(String topicid, String topicsubject)
            throws QueryException, XWikiException {
        // TODO there should/could be an api to retrieve mails related to a topic somewhere else than in timeline
        // generator ...

        logger.debug("Retrieving emails linked to topic with id " + topicid);

        final TreeMap<Long, TopicEventBubble> bubblesInfo = new TreeMap<Long, TopicEventBubble>();
        String xwql_topic = "select doc.fullName, mail.date, mail.messagesubject ,mail.from from Document doc, "
                + "doc.object(" + XWikiPersistence.CLASS_MAILS + ") as  mail where  mail.topicid='" + topicid
                + "' and doc.space='MailArchiveItems' order by mail.date asc";
        final List<Object[]> msgs = queryManager.createQuery(xwql_topic, Query.XWQL).execute();

        String previousSubject = StringUtils.normalizeSpace(topicsubject);

        for (Object[] msg : msgs) {
            final String docfullname = (String) msg[0];
            final Date maildate = (Date) msg[1];
            String mailmessagesubject = (String) msg[2];
            final String mailfrom = (String) msg[3];

            IMAUser parsedUser = mailUtils.parseUser(mailfrom, config.isMatchLdap());
            String user = parsedUser.getDisplayName();
            String link = this.userStatsUrl + "?user=" + mailfrom;
            if (StringUtils.isNotEmpty(parsedUser.getWikiProfile())) {
                link += "&wikiUser=" + parsedUser.getWikiProfile();
            }

            mailmessagesubject = StringUtils.normalizeSpace(mailmessagesubject);
            String subject = mailmessagesubject.replace(previousSubject, "...");
            previousSubject = mailmessagesubject;

            TopicEventBubble bubbleEvent = new TopicEventBubble();
            bubbleEvent.date = maildate;
            bubbleEvent.url = xwiki.getDocument(docResolver.resolve(docfullname), context).getURL("view", context);
            bubbleEvent.subject = subject;
            bubbleEvent.link = link;
            bubbleEvent.user = user;
            bubblesInfo.put(maildate.getTime(), bubbleEvent);
        }

        return bubblesInfo;

    }

    private String getAuthorAvatar(final String user) {
        String authorAvatar = null;
        String imgName = null;
        try {
            XWikiDocument userDoc = xwiki.getDocument(user, context);
            if (userDoc != null && !userDoc.isNew()) {
                BaseObject userObj = userDoc.getObject("XWiki.XWikiUsers");
                if (userObj != null) {
                    imgName = userObj.getStringValue("avatar");
                }
            }
            if (imgName == null) {
                authorAvatar = xwiki.getDocument("XWiki.XWikiUserSheet", context).getURL("download", context)
                        + "/noavatar.png";
            } else {
                authorAvatar = userDoc.getURL("download", context) + '/' + imgName;
            }
        } catch (XWikiException e) {
            logger.error("Failed to retrieve author avatar", e);
        }

        return authorAvatar;
    }

    /**
     * Formats a timeline bubble content, ie html presenting list of mails related to a given topic.
     * 
     * @param topicid
     * @param topicsubject
     * @return
     * @throws QueryException
     * @throws XWikiException
     */
    protected String getExtract(String topicid) throws QueryException, XWikiException {
        String extract = "";

        logger.debug("Retrieving first email linked to topic with id " + topicid);

        String xwql_topic = "select mail.body, mail.bodyhtml from Document doc, " + "doc.object("
                + XWikiPersistence.CLASS_MAILS + ") as  mail where  mail.topicid='" + topicid
                + "' and doc.space='MailArchiveItems' order by mail.date asc";
        final List<Object[]> msgs = queryManager.createQuery(xwql_topic, Query.XWQL).setLimit(1).execute();

        if (CollectionUtils.isNotEmpty(msgs)) {

            final String body = (String) msgs.get(0)[0];
            final String bodyhtml = (String) msgs.get(0)[1];

            extract = body;

            try {
                DecodedMailContent decoded = mailUtils.decodeMailContent(bodyhtml, body, true);
                if (decoded != null) {
                    if (decoded.isHtml() && StringUtils.isEmpty(decoded.getText())) {
                        extract = textUtils.htmlToPlainText(bodyhtml);
                    } else {
                        extract = decoded.getText();
                    }
                }

            } catch (IOException e) {
                Log.warn("Could not decoded HTML content {}", e);
            }

            extract = StringUtils.normalizeSpace(extract);
            extract = StringUtils.abbreviate(extract, 200);

        }

        return extract;

    }

}