com.drunkendev.confluence.plugins.attachments.PurgeAttachmentsJob.java Source code

Java tutorial

Introduction

Here is the source code for com.drunkendev.confluence.plugins.attachments.PurgeAttachmentsJob.java

Source

/*
 * PurgeAttachmentsJob.java    Jul 9 2012, 04:31
 *
 * Copyright 2012 Drunken Dev. All rights reserved.
 * Use is subject to license terms.
 */

package com.drunkendev.confluence.plugins.attachments;

import com.atlassian.confluence.mail.template.ConfluenceMailQueueItem;
import com.atlassian.confluence.pages.Attachment;
import com.atlassian.confluence.pages.AttachmentManager;
import com.atlassian.confluence.setup.settings.SettingsManager;
import com.atlassian.confluence.spaces.Space;
import com.atlassian.confluence.spaces.SpaceManager;
import com.atlassian.core.task.MultiQueueTaskManager;
import com.atlassian.core.util.FileSize;
import com.atlassian.mail.MailException;
import com.atlassian.quartz.jobs.AbstractJob;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Purge old attachment versions.
 *
 * @author  Brett Ryan
 */
public class PurgeAttachmentsJob extends AbstractJob {

    private static final Logger LOG = LoggerFactory.getLogger(PurgeAttachmentsJob.class);
    private AttachmentManager attachmentManager;
    private SpaceManager spaceManager;
    private PurgeAttachmentsSettingsService settingSvc;
    private MultiQueueTaskManager mailQueueTaskManager;
    private SettingsManager settingsManager;

    /**
     * Creates a new {@code PurgeAttachmentsJob} instance.
     */
    public PurgeAttachmentsJob() {
        LOG.debug("Creating purge-old-attachment-job instance.");
    }

    public void setAttachmentManager(AttachmentManager attachmentManager) {
        this.attachmentManager = attachmentManager;
    }

    public void setSpaceManager(SpaceManager spaceManager) {
        this.spaceManager = spaceManager;
    }

    public void setPurgeAttachmentsSettingsService(
            PurgeAttachmentsSettingsService purgeAttachmentsSettingsService) {
        this.settingSvc = purgeAttachmentsSettingsService;
    }

    public void setMultiQueueTaskManager(MultiQueueTaskManager mailQueueTaskManager) {
        this.mailQueueTaskManager = mailQueueTaskManager;
    }

    public void setSettingsManager(SettingsManager settingsManager) {
        this.settingsManager = settingsManager;
    }

    private PurgeAttachmentSettings getSettings(Space space, PurgeAttachmentSettings dflt) {
        if (space == null) {
            return null;
        }
        PurgeAttachmentSettings sng = settingSvc.getSettings(space.getKey());

        // Use global.
        if (sng == null || sng.getMode() == PurgeAttachmentSettings.MODE_GLOBAL) {
            return dflt;
        }

        // Explicitely disabled.
        if (sng.getMode() == PurgeAttachmentSettings.MODE_DISABLED) {
            sng = null;
        }

        return sng;
    }

    private Map<String, PurgeAttachmentSettings> getAllSpaceSettings(PurgeAttachmentSettings dflt) {
        Map<String, PurgeAttachmentSettings> r = new HashMap<String, PurgeAttachmentSettings>();
        for (Space s : spaceManager.getAllSpaces()) {
            if (s.getKey() == null || r.containsKey(s.getKey())) {
                continue;
            }
            PurgeAttachmentSettings stg = getSettings(s, dflt);
            if (stg != null) {
                r.put(s.getKey(), stg);
            }
        }
        return r;
    }

    @Override
    public void doExecute(JobExecutionContext jec) throws JobExecutionException {
        LOG.info("Purge old attachments started.");
        Date started = new Date();

        PurgeAttachmentSettings systemSettings = settingSvc.getSettings();
        if (systemSettings == null) {
            systemSettings = settingSvc.createDefault();
        }
        Map<String, PurgeAttachmentSettings> sl = getAllSpaceSettings(systemSettings);

        Map<String, List<MailLogEntry>> mailEntries = new HashMap<String, List<MailLogEntry>>();
        //List<MailLogEntry> mailEntries = new ArrayList<MailLogEntry>();

        Iterator<Attachment> i = attachmentManager.getAttachmentDao().findLatestVersionsIterator();

        while (i.hasNext()) {
            Attachment att = i.next();

            if (att.getVersion() > 1 && att.getSpace() != null && sl.containsKey(att.getSpace().getKey())) {
                PurgeAttachmentSettings settings = sl.get(att.getSpace().getKey());

                List<Attachment> toDelete = findDeletions(att, settings);
                List<Integer> deletedVersions = new ArrayList<Integer>();
                for (Attachment tt : toDelete) {
                    deletedVersions.add(tt.getVersion());
                }
                if (toDelete.size() > 0) {
                    long spaceSaved = 0;
                    for (Attachment p : toDelete) {
                        if (settings.isReportOnly() || systemSettings.isReportOnly()) {
                        } else {
                            attachmentManager.removeAttachmentVersionFromServer(p);
                        }
                        spaceSaved += p.getFileSize();
                    }
                    MailLogEntry mle = new MailLogEntry(att, deletedVersions,
                            settings.isReportOnly() || systemSettings.isReportOnly(), settings == systemSettings,
                            spaceSaved);
                    if (settings != systemSettings && StringUtils.isNotBlank(settings.getReportEmailAddress())) {
                        if (!mailEntries.containsKey(settings.getReportEmailAddress())) {
                            mailEntries.put(settings.getReportEmailAddress(), new ArrayList<MailLogEntry>());
                        }
                        mailEntries.get(settings.getReportEmailAddress()).add(mle);
                    }
                    //TODO: I know this will log twice if system email and space
                    //      email are the same, will fix later, just hacking atm.
                    if (StringUtils.isNotBlank(systemSettings.getReportEmailAddress())) {
                        if (!mailEntries.containsKey(systemSettings.getReportEmailAddress())) {
                            mailEntries.put(systemSettings.getReportEmailAddress(), new ArrayList<MailLogEntry>());
                        }
                        mailEntries.get(systemSettings.getReportEmailAddress()).add(mle);
                    }
                }
            }
        }
        Date end = new Date();
        try {
            //mailResultsPlain(mailEntries);
            mailResultsHtml(mailEntries, started, end);
        } catch (MailException ex) {
            LOG.error("Exception raised while trying to mail results.", ex);
        }
        LOG.info("Purge old attachments completed.");
    }

    private List<Attachment> findDeletions(Attachment a, PurgeAttachmentSettings stng) {
        List<Attachment> prior = attachmentManager.getPreviousVersions(a);
        if (prior.isEmpty()) {
            return Collections.<Attachment>emptyList();
        }
        Collections.sort(prior, new Comparator<Attachment>() {
            @Override
            public int compare(Attachment t, Attachment t1) {
                return t.getVersion() - t1.getVersion();
            }
        });

        int to = -1;
        int n;
        if (stng.isRevisionCountRuleEnabled()) {
            n = filterRevisionCount(prior, stng.getMaxRevisions());
            if (n > to) {
                to = n;
            }
        }
        if (stng.isAgeRuleEnabled()) {
            n = filterAge(prior, stng.getMaxDaysOld());
            if (n > to) {
                to = n;
            }
        }
        if (stng.isMaxSizeRuleEnabled()) {
            n = filterSize(prior, stng.getMaxTotalSize());
            if (n > to) {
                to = n;
            }
        }
        if (to == -1) {
            return Collections.<Attachment>emptyList();
        }
        if (to >= prior.size() - 1) {
            return prior;
        }
        return prior.subList(0, to + 1);
    }

    private int filterRevisionCount(List<Attachment> prior, int maxRevisions) {
        if (prior.size() > maxRevisions) {
            return (prior.size() - maxRevisions) - 1;
        } else {
            return -1;
        }
    }

    private int filterAge(List<Attachment> prior, int maxDaysOld) {
        Calendar dateFrom = Calendar.getInstance();
        dateFrom.add(Calendar.DAY_OF_MONTH, -(maxDaysOld));
        Calendar modDate = Calendar.getInstance();

        for (int i = prior.size() - 1; i >= 0; i--) {
            if (prior.get(i).getLastModificationDate() != null) {
                modDate.setTime(prior.get(i).getLastModificationDate());
                if (dateFrom.after(modDate)) {
                    return i;
                }
            }
        }

        return -1;
    }

    private int filterSize(List<Attachment> prior, long maxTotalSize) {
        long maxSizeKiB = maxTotalSize * 1024 * 1024;
        long total = 0;
        for (int i = prior.size() - 1; i >= 0; i--) {
            total += prior.get(i).getFileSize();
            if (total > maxSizeKiB) {
                return i;
            }
        }
        return -1;
    }

    private void mailResultsPlain(Map<String, List<MailLogEntry>> mailEntries1) throws MailException {
        String p = settingsManager.getGlobalSettings().getBaseUrl();

        for (Map.Entry<String, List<MailLogEntry>> n : mailEntries1.entrySet()) {
            StringBuilder sb = new StringBuilder();
            for (MailLogEntry me : n.getValue()) {
                Attachment a = me.getAttachment();

                sb.append(a.getDisplayTitle()).append("\n").append("    URL: ").append(p)
                        .append(a.getContent().getAttachmentsUrlPath()).append("\n").append("    File Size: ")
                        .append(a.getNiceFileSize()).append("\n").append("    Space: ")
                        .append(a.getSpace().getName()).append("\n").append("    Space URL: ").append(p)
                        .append(a.getSpace().getUrlPath()).append("\n").append("    Report Only: ")
                        .append(me.isReportOnly() ? "Yes" : "No").append("\n").append("    Global Settings: ")
                        .append(me.isGlobalSettings() ? "Yes" : "No").append("\n").append("    Current Version: ")
                        .append(a.getAttachmentVersion()).append("\n");

                sb.append("    Versions Deleted: ");
                int c = 0;
                for (Integer dl : me.getDeletedVersions()) {
                    if (c++ > 0) {
                        sb.append(", ");
                    }
                    sb.append(dl);
                }
                sb.append("\n\n");
            }

            ConfluenceMailQueueItem mail = new ConfluenceMailQueueItem(n.getKey(), "Purged attachments",
                    sb.toString(), "text/plain");
            mailQueueTaskManager.getTaskQueue("mail").addTask(mail);
            LOG.debug("Mail Sent");
        }
    }

    private void mailResultsHtml(Map<String, List<MailLogEntry>> mailEntries1, Date started, Date ended)
            throws MailException {
        String p = settingsManager.getGlobalSettings().getBaseUrl();
        String subject = "Purged old attachments";

        DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);

        for (Map.Entry<String, List<MailLogEntry>> n : mailEntries1.entrySet()) {
            List<MailLogEntry> entries = n.getValue();

            Collections.sort(entries, new Comparator<MailLogEntry>() {
                @Override
                public int compare(MailLogEntry o1, MailLogEntry o2) {
                    Attachment a1 = o1.getAttachment();
                    Attachment a2 = o2.getAttachment();

                    int comp = ObjectUtils.compare(a1.getSpace().getName(), a2.getSpace().getName());
                    if (comp != 0)
                        return comp;
                    comp = ObjectUtils.compare(a1.getDisplayTitle(), a2.getDisplayTitle());
                    if (comp != 0)
                        return comp;
                    return 0;
                }
            });

            StringBuilder sb = new StringBuilder();

            sb.append(
                    "<!DOCTYPE html><html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en-US\" lang=\"en-US\">");

            sb.append("<head>");
            //sb.append("<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />")
            sb.append("<title>").append(subject).append("</title>");
            sb.append("<style type=\"text/css\">");
            sb.append(
                    "body { font-family: Helvetica, Arial, sans-serif; font-size: 10pt; width: 100%; color: #333; text-align: left; }");
            sb.append("a { color: #326ca6; text-decoration: none; }");
            sb.append("a:hover { color: #336ca6; text-decoration: underline; }");
            sb.append("a:active { color: #326ca6; }");
            sb.append("div.note { border: solid 1px #F0C000; padding: 5px; background-color: #FFFFCE; }");
            sb.append("table { border-collapse: collapse; padding: 0; border: 0 none; }");
            sb.append(
                    "th, td { padding: 5px 7px; border: solid 1px #ddd; text-align: left; vertical-align: top; color: #333; margin: 0; }");
            sb.append("th { background-color: #f0f0f0 }");
            sb.append("tr.deleted td { background-color: #FFE7E7; /* border: solid 1px #DF9898; */ }");
            sb.append("</style>");
            sb.append("</head>");

            sb.append("<body>");

            sb.append("<p>");
            sb.append("This message is to inform you that the following prior");
            sb.append(" attachment versions have been removed from confluence");
            sb.append(" in order to conserve space. Current versions have not");
            sb.append(" been deleted.");
            sb.append("</p>");

            sb.append("<p>");
            sb.append("Versions deleted are listed in the 'Versions Deleted'");
            sb.append(" column. Rows shown in red have been processed, all");
            sb.append(" other rows are in report-only mode.");
            sb.append("</p>");

            sb.append("<p><strong>Started</strong>: ").append(df.format(started))
                    .append(" <strong>Ended</strong>: ").append(df.format(ended)).append("</p>");

            long deleted = 0;
            long report = 0;
            for (MailLogEntry me : entries) {
                if (me.isReportOnly()) {
                    report += me.getSpaceSaved();
                } else {
                    deleted += me.getSpaceSaved();
                }
            }
            if (deleted > 0) {
                sb.append("<p>");
                sb.append("A total of ").append(FileSize.format(deleted)).append(" space has been reclaimed.");
                sb.append("</p>");
            }
            if (report > 0) {
                sb.append("<p>");
                sb.append("A total of ").append(FileSize.format(report))
                        .append(" can be reclaimed from those in report mode.");
                sb.append("</p>");
            }
            sb.append("<table>");

            sb.append("<thead>");
            sb.append("<tr>");
            sb.append("<th>").append("Space").append("</th>");
            sb.append("<th>").append("File Name").append("</th>");
            sb.append("<th>").append("Space Freed").append("</th>");
            //sb.append("<th>").append("Global Settings?").append("</th>");
            sb.append("<th>").append("Version").append("</th>");
            sb.append("<th>").append("Versions Deleted").append("</th>");
            sb.append("</tr>");
            sb.append("</thead>");

            sb.append("<tbody>");
            for (MailLogEntry me : entries) {
                Attachment a = me.getAttachment();

                sb.append("<tr");
                if (!me.isReportOnly()) {
                    sb.append(" class=\"deleted\"");
                }
                sb.append(">");

                sb.append("<td>");
                sb.append("<a href=\"").append(p).append(a.getSpace().getUrlPath()).append("\">")
                        .append(a.getSpace().getName()).append("</a>");
                sb.append("</td>");

                sb.append("<td>");
                sb.append("<a href=\"").append(p).append(a.getContent().getAttachmentsUrlPath()).append("\">")
                        .append(a.getDisplayTitle()).append("</a>");
                sb.append("</td>");

                sb.append("<td>").append(me.getSpaceSavedPretty()).append("</td>");

                //sb.append("<td>").append(me.isGlobalSettings() ? "Yes" : "No").append("</td>");
                sb.append("<td>").append(a.getAttachmentVersion()).append("</td>");

                sb.append("<td>");
                int c = 0;
                for (Integer dl : me.getDeletedVersions()) {
                    if (c++ > 0) {
                        sb.append(", ");
                    }
                    sb.append(dl);
                }
                sb.append("</td>");

                sb.append("</tr>");
            }
            sb.append("</tbody></table>");

            sb.append("<p>");
            sb.append("This message has been sent by the confluence mail tools");
            sb.append(" - attachment purger.");
            sb.append("</p>");

            sb.append("</body></html>");

            ConfluenceMailQueueItem mail = new ConfluenceMailQueueItem(n.getKey(), subject, sb.toString(),
                    ConfluenceMailQueueItem.MIME_TYPE_HTML);
            mailQueueTaskManager.getTaskQueue("mail").addTask(mail);
            LOG.debug("Mail Sent");
        }
    }

    /**
     *
     */
    private class MailLogEntry {

        private final Attachment attachment;
        private final List<Integer> deletedVersions;
        private final boolean reportOnly;
        private final boolean globalSettings;
        private final long spaceSaved;

        private MailLogEntry(Attachment a, List<Integer> deletedVersions, boolean reportOnly,
                boolean globalSettings, long spaceSaved) {
            this.attachment = a;
            this.deletedVersions = deletedVersions;
            this.reportOnly = reportOnly;
            this.globalSettings = globalSettings;
            this.spaceSaved = spaceSaved;
        }

        private Attachment getAttachment() {
            return attachment;
        }

        private List<Integer> getDeletedVersions() {
            return deletedVersions;
        }

        private boolean isReportOnly() {
            return reportOnly;
        }

        private boolean isGlobalSettings() {
            return globalSettings;
        }

        private long getSpaceSaved() {
            return spaceSaved;
        }

        private String getSpaceSavedPretty() {
            return FileSize.format(spaceSaved);
        }

    }

}