com.xpn.xwiki.plugin.watchlist.WatchListJob.java Source code

Java tutorial

Introduction

Here is the source code for com.xpn.xwiki.plugin.watchlist.WatchListJob.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 com.xpn.xwiki.plugin.watchlist;

import java.util.Date;
import java.util.List;

import javax.servlet.ServletException;

import org.apache.commons.lang3.StringUtils;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.container.Container;
import org.xwiki.container.servlet.ServletContainerException;
import org.xwiki.container.servlet.ServletContainerInitializer;
import org.xwiki.context.Execution;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.plugin.scheduler.AbstractJob;
import com.xpn.xwiki.plugin.watchlist.WatchListStore.ElementType;
import com.xpn.xwiki.web.Utils;

/**
 * WatchList abstract implementation of Quartz's Job.
 * 
 * @version $Id: b56b8c60c41391a08ed44e10004d449c85e56b6e $
 */
public class WatchListJob extends AbstractJob implements Job {
    /**
     * Wiki page which contains the default watchlist email template.
     */
    public static final String DEFAULT_EMAIL_TEMPLATE = "XWiki.WatchListMessage";

    /**
     * Logger.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(WatchListPlugin.class);

    /**
     * Scheduler Job XObject.
     */
    private BaseObject schedulerJobObject;

    /**
     * Watchlist Job XObject.
     */
    private BaseObject watchListJobObject;

    /**
     * XWiki context.
     */
    private XWikiContext context;

    /**
     * Caller plugin.
     */
    private WatchListPlugin plugin;

    /**
     * Sets objects required by the Job : XWiki, XWikiContext, WatchListPlugin, etc.
     * 
     * @param jobContext Context of the request
     * @throws Exception when the init of components fails
     */
    public void init(JobExecutionContext jobContext) throws Exception {
        JobDataMap data = jobContext.getJobDetail().getJobDataMap();
        // clone the context to make sure we have a new one per run
        this.context = (XWikiContext) ((XWikiContext) data.get("context")).clone();
        // clean up the database connections
        this.context.getWiki().getStore().cleanUp(this.context);
        this.plugin = (WatchListPlugin) this.context.getWiki().getPlugin(WatchListPlugin.ID, this.context);
        this.schedulerJobObject = (BaseObject) data.get("xjob");
        this.watchListJobObject = this.context.getWiki()
                .getDocument(this.schedulerJobObject.getName(), this.context)
                .getObject(WatchListJobManager.WATCHLIST_JOB_CLASS);
        initializeComponents(this.context);
    }

    /**
     * Initialize container context.
     * 
     * @param context The XWiki context.
     * @throws ServletException If the container initialization fails.
     */
    protected void initializeComponents(XWikiContext context) throws ServletException {
        // Initialize the Container fields (request, response, session).
        // Note that this is a bridge between the old core and the component architecture.
        // In the new component architecture we use ThreadLocal to transport the request,
        // response and session to components which require them.
        // In the future this Servlet will be replaced by the XWikiPlexusServlet Servlet.
        ServletContainerInitializer containerInitializer = Utils.getComponent(ServletContainerInitializer.class);

        try {
            containerInitializer.initializeRequest(context.getRequest().getHttpServletRequest(), context);
            containerInitializer.initializeResponse(context.getResponse().getHttpServletResponse());
            containerInitializer.initializeSession(context.getRequest().getHttpServletRequest());
        } catch (ServletContainerException e) {
            throw new ServletException("Failed to initialize Request/Response or Session", e);
        }
    }

    /**
     * Clean the container context.
     */
    protected void cleanupComponents() {
        Container container = Utils.getComponent(Container.class);
        Execution execution = Utils.getComponent(Execution.class);

        // We must ensure we clean the ThreadLocal variables located in the Container and Execution
        // components as otherwise we will have a potential memory leak.
        container.removeRequest();
        container.removeResponse();
        container.removeSession();
        execution.removeContext();
    }

    /**
     * @return ID of the job
     */
    public String getId() {
        String className = this.getClass().getName();
        return className.substring(className.lastIndexOf(".") + 1);
    }

    /**
     * @return the previous job fire time
     */
    private Date getPreviousFireTime() {
        return this.watchListJobObject.getDateValue(WatchListJobManager.WATCHLIST_JOB_LAST_FIRE_TIME_PROP);
    }

    /**
     * Save the date of the execution in the watchlist job object.
     * 
     * @throws XWikiException if the job document can't be retrieved or if the save action fails
     */
    private void setPreviousFireTime() throws XWikiException {
        XWikiDocument doc = this.context.getWiki().getDocument(this.watchListJobObject.getName(), this.context);
        this.watchListJobObject.setDateValue(WatchListJobManager.WATCHLIST_JOB_LAST_FIRE_TIME_PROP, new Date());
        // Prevent version changes
        doc.setMetaDataDirty(false);
        doc.setContentDirty(false);
        this.context.getWiki().saveDocument(doc, "Updated last fire time", true, this.context);
    }

    /**
     * @param userWiki wiki from which the user comes from
     * @return the name of the page that should be used as email template for this job
     */
    private String getEmailTemplate(String userWiki) {
        String fullName = this.watchListJobObject.getStringValue(WatchListJobManager.WATCHLIST_JOB_EMAIL_PROP);
        String prefixedFullName;

        if (fullName.contains(WatchListStore.WIKI_SPACE_SEP)) {
            // If the configured template is already an absolute reference it's meant to force the template.
            prefixedFullName = fullName;
        } else {
            prefixedFullName = userWiki + WatchListStore.WIKI_SPACE_SEP + fullName;
            if (this.context.getWiki().exists(prefixedFullName, this.context)) {
                // If the configured template exists in the user wiki, use it.
                return prefixedFullName;
            }
        }

        return fullName;
    }

    /**
     * Retrieves all the XWiki.XWikiUsers who have requested to be notified by changes, i.e. who have an Object of class
     * WATCHLIST_CLASS attached AND who have choosen the current job for their notifications.
     * 
     * @return a collection of document names pointing to the XWikiUsers wishing to get notified.
     */
    private List<String> getSubscribers() {
        return this.plugin.getStore().getSubscribersForJob(this.schedulerJobObject.getName());
    }

    /**
     * @return true if this job has subscribers, false otherwise
     */
    private boolean hasSubscribers() {
        List<String> subscribers = getSubscribers();

        if (subscribers.isEmpty()) {
            return false;
        }

        return true;
    }

    /**
     * Method called from the scheduler.
     * 
     * @param jobContext Context of the request
     * @throws JobExecutionException if the job execution fails.
     */
    @Override
    public void executeJob(JobExecutionContext jobContext) throws JobExecutionException {
        try {
            init(jobContext);

            if (this.watchListJobObject == null) {
                return;
            }

            List<String> subscribers = getSubscribers();
            Date previousFireTime = getPreviousFireTime();
            WatchListEventMatcher eventMatcher = new WatchListEventMatcher(previousFireTime, this.context);
            setPreviousFireTime();

            if (!hasSubscribers()) {
                return;
            }

            if (eventMatcher.getEventNumber() == 0) {
                return;
            }

            for (String subscriber : subscribers) {
                try {
                    List<String> wikis = this.plugin.getStore().getWatchedElements(subscriber, ElementType.WIKI,
                            this.context);
                    List<String> spaces = this.plugin.getStore().getWatchedElements(subscriber, ElementType.SPACE,
                            this.context);
                    List<String> documents = this.plugin.getStore().getWatchedElements(subscriber,
                            ElementType.DOCUMENT, this.context);
                    List<String> users = this.plugin.getStore().getWatchedElements(subscriber, ElementType.USER,
                            this.context);
                    List<WatchListEvent> matchingEvents = eventMatcher.getMatchingEvents(wikis, spaces, documents,
                            users, subscriber, this.context);
                    String userWiki = StringUtils.substringBefore(subscriber, WatchListStore.WIKI_SPACE_SEP);

                    // If events have occurred on at least one element watched by the user, send the email
                    if (matchingEvents.size() > 0) {
                        this.plugin.getNotifier().sendEmailNotification(subscriber, matchingEvents,
                                getEmailTemplate(userWiki), previousFireTime, this.context);
                    }
                } catch (Exception e) {
                    LOGGER.error("Failed to send watchlist notification to user [{}]", subscriber, e);
                }
            }
        } catch (Exception e) {
            // We're in a job, we don't throw exceptions
            LOGGER.error("Exception while running job", e);
        } finally {
            this.context.getWiki().getStore().cleanUp(this.context);
            cleanupComponents();
        }
    }
}