org.dspace.eperson.Subscribe.java Source code

Java tutorial

Introduction

Here is the source code for org.dspace.eperson.Subscribe.java

Source

/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.eperson;

import java.io.IOException;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.TimeZone;

import javax.mail.MessagingException;

import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;

import org.joda.time.DateMidnight;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.AuthorizeManager;
import org.dspace.content.Collection;
import org.dspace.content.MDValue;
import org.dspace.content.Item;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Context;
import org.dspace.core.Email;
import org.dspace.core.I18nUtil;
import org.dspace.core.LogManager;
import org.dspace.handle.HandleManager;
import org.dspace.search.Harvest;
import org.dspace.search.HarvestedItemInfo;
import org.dspace.storage.rdbms.DatabaseManager;
import org.dspace.storage.rdbms.TableRow;
import org.dspace.storage.rdbms.TableRowIterator;

/**
 * Class defining methods for sending new item e-mail alerts to users
 * 
 * @author Robert Tansley
 * @version $Revision: 6858 $
 */
public class Subscribe {
    /** log4j logger */
    private static Logger log = LoggerFactory.getLogger(Subscribe.class);

    // command-line options
    @Option(name = "-t", usage = "Run test session")
    private boolean test;

    @Option(name = "-h", usage = "Print helpful message")
    private boolean help;

    private Subscribe() {
    }

    /**
     * Subscribe an e-person to a collection. An e-mail will be sent every day a
     * new item appears in the collection.
     * 
     * @param context
     *            DSpace context
     * @param eperson
     *            EPerson to subscribe
     * @param collection
     *            Collection to subscribe to
     */
    public static void subscribe(Context context, EPerson eperson, Collection collection)
            throws SQLException, AuthorizeException {
        // Check authorisation. Must be administrator, or the eperson.
        if (AuthorizeManager.isAdmin(context)
                || ((context.getCurrentUser() != null) && (context.getCurrentUser().getID() == eperson.getID()))) {
            // already subscribed?          
            TableRowIterator r = DatabaseManager.query(context,
                    "SELECT * FROM subscription WHERE eperson_id= ? " + " AND collection_id= ? ", eperson.getID(),
                    collection.getID());

            try {
                if (!r.hasNext()) {
                    // Not subscribed, so add them
                    TableRow row = DatabaseManager.row("subscription");
                    row.setColumn("eperson_id", eperson.getID());
                    row.setColumn("collection_id", collection.getID());
                    DatabaseManager.insert(context, row);

                    log.info(LogManager.getHeader(context, "subscribe",
                            "eperson_id=" + eperson.getID() + ",collection_id=" + collection.getID()));
                }
            } finally {
                // close the TableRowIterator to free up resources
                if (r != null) {
                    r.close();
                }
            }
        } else {
            throw new AuthorizeException("Only admin or e-person themselves can subscribe");
        }
    }

    /**
     * Unsubscribe an e-person to a collection. Passing in <code>null</code>
     * for the collection unsubscribes the e-person from all collections they
     * are subscribed to.
     * 
     * @param context
     *            DSpace context
     * @param eperson
     *            EPerson to unsubscribe
     * @param collection
     *            Collection to unsubscribe from
     */
    public static void unsubscribe(Context context, EPerson eperson, Collection collection)
            throws SQLException, AuthorizeException {
        // Check authorisation. Must be administrator, or the eperson.
        if (AuthorizeManager.isAdmin(context)
                || ((context.getCurrentUser() != null) && (context.getCurrentUser().getID() == eperson.getID()))) {
            if (collection == null) {
                // Unsubscribe from all
                DatabaseManager.updateQuery(context, "DELETE FROM subscription WHERE eperson_id= ? ",
                        eperson.getID());
            } else {
                DatabaseManager.updateQuery(context,
                        "DELETE FROM subscription WHERE eperson_id= ? " + "AND collection_id= ? ", eperson.getID(),
                        collection.getID());

                log.info(LogManager.getHeader(context, "unsubscribe",
                        "eperson_id=" + eperson.getID() + ",collection_id=" + collection.getID()));
            }
        } else {
            throw new AuthorizeException("Only admin or e-person themselves can unsubscribe");
        }
    }

    /**
     * Find out which collections an e-person is subscribed to
     * 
     * @param context
     *            DSpace context
     * @param eperson
     *            EPerson
     * @return array of collections e-person is subscribed to
     */
    public static Collection[] getSubscriptions(Context context, EPerson eperson) throws SQLException {
        TableRowIterator tri = DatabaseManager.query(context,
                "SELECT collection_id FROM subscription WHERE eperson_id= ? ", eperson.getID());

        List<Collection> collections = new ArrayList<Collection>();

        try {
            while (tri.hasNext()) {
                TableRow row = tri.next();

                collections.add(Collection.find(context, row.getIntColumn("collection_id")));
            }
        } finally {
            // close the TableRowIterator to free up resources
            if (tri != null) {
                tri.close();
            }
        }

        Collection[] collArray = new Collection[collections.size()];

        return (Collection[]) collections.toArray(collArray);
    }

    /**
     * Is that e-person subscribed to that collection?
     * 
     * @param context
     *            DSpace context
     * @param eperson
     *            find out if this e-person is subscribed
     * @param collection
     *            find out if subscribed to this collection
     * @return <code>true</code> if they are subscribed
     */
    public static boolean isSubscribed(Context context, EPerson eperson, Collection collection)
            throws SQLException {
        TableRowIterator tri = DatabaseManager.query(context,
                "SELECT * FROM subscription WHERE eperson_id= ? " + "AND collection_id= ? ", eperson.getID(),
                collection.getID());

        try {
            return tri.hasNext();
        } finally {
            // close the TableRowIterator to free up resources
            if (tri != null) {
                tri.close();
            }
        }
    }

    /**
     * Process subscriptions. This must be invoked only once a day. Messages are
     * only sent out when a collection has actually received new items, so that
     * people's mailboxes are not clogged with many "no new items" mails.
     * <P>
     * Yesterday's newly available items are included. If this is run at for
     * example midday, any items that have been made available during the
     * current day will not be included, but will be included in the next day's
     * run.
     * <P>
     * For example, if today's date is 2002-10-10 (in UTC) items made available
     * during 2002-10-09 (UTC) will be included.
     * 
     * @param context
     *            DSpace context object
     * @param test 
     */
    public static void processDaily(Context context, boolean test) throws SQLException, IOException {
        // Grab the subscriptions
        TableRowIterator tri = DatabaseManager.query(context, "SELECT * FROM subscription ORDER BY eperson_id");

        EPerson currentEPerson = null;
        List<Collection> collections = null; // List of Collections

        try {
            // Go through the list collating subscriptions for each e-person
            while (tri.hasNext()) {
                TableRow row = tri.next();

                // Does this row relate to the same e-person as the last?
                if ((currentEPerson == null) || (row.getIntColumn("eperson_id") != currentEPerson.getID())) {
                    // New e-person. Send mail for previous e-person
                    if (currentEPerson != null) {

                        try {
                            sendEmail(context, currentEPerson, collections, test);
                        } catch (MessagingException me) {
                            log.error("Failed to send subscription to eperson_id=" + currentEPerson.getID());
                            log.error("Error sending email", me);
                        }
                    }

                    currentEPerson = EPerson.find(context, row.getIntColumn("eperson_id"));
                    collections = new ArrayList<Collection>();
                }

                collections.add(Collection.find(context, row.getIntColumn("collection_id")));
            }
        } finally {
            // close the TableRowIterator to free up resources
            if (tri != null) {
                tri.close();
            }
        }

        // Process the last person
        if (currentEPerson != null) {
            try {
                sendEmail(context, currentEPerson, collections, test);
            } catch (MessagingException me) {
                log.error("Failed to send subscription to eperson_id=" + currentEPerson.getID());
                log.error("Error sending email", me);
            }
        }
    }

    /**
     * Sends an email to the given e-person with details of new items in the
     * given collections, items that appeared yesterday. No e-mail is sent if
     * there aren't any new items in any of the collections.
     * 
     * @param context
     *            DSpace context object
     * @param eperson
     *            eperson to send to
     * @param collections
     *            List of collection IDs (Integers)
     * @param test 
     */
    public static void sendEmail(Context context, EPerson eperson, List<Collection> collections, boolean test)
            throws IOException, MessagingException, SQLException {
        // Get a resource bundle according to the eperson language preferences
        Locale supportedLocale = I18nUtil.getEPersonLocale(eperson);
        ResourceBundle labels = ResourceBundle.getBundle("Messages", supportedLocale);

        // Get the start and end dates for yesterday

        // The date should reflect the timezone as well. Otherwise we stand to lose that information 
        // in truncation and roll to an earlier date than intended.

        DateTimeFormatter fmt = ISODateTimeFormat.dateTimeNoMillis();
        String midnightYesterday = fmt.print(new DateMidnight(System.currentTimeMillis()));

        // FIXME: text of email should be more configurable from an
        // i18n viewpoint
        StringBuffer emailText = new StringBuffer();
        boolean isFirst = true;

        for (int i = 0; i < collections.size(); i++) {
            Collection c = collections.get(i);

            try {
                boolean includeAll = ConfigurationManager
                        .getBooleanProperty("harvest.includerestricted.subscription", true);

                // we harvest all the changed item from yesterday until now
                List<HarvestedItemInfo> itemInfos = Harvest.harvest(context, c, midnightYesterday, null, 0, // Limit
                        // and
                        // offset
                        // zero,
                        // get
                        // everything
                        0, true, // Need item objects
                        false, // But not containers
                        false, // Or withdrawals
                        includeAll);

                if (ConfigurationManager.getBooleanProperty("eperson.subscription.onlynew", false)) {
                    // get only the items archived yesterday
                    itemInfos = filterOutModified(itemInfos);
                } else {
                    // strip out the item archived today or 
                    // not archived yesterday and modified today
                    itemInfos = filterOutToday(itemInfos);
                }

                // Only add to buffer if there are new items
                if (itemInfos.size() > 0) {
                    if (!isFirst) {
                        emailText.append("\n---------------------------------------\n");
                    } else {
                        isFirst = false;
                    }

                    emailText.append(labels.getString("org.dspace.eperson.Subscribe.new-items")).append(" ")
                            .append(c.getMetadata("name")).append(": ").append(itemInfos.size()).append("\n\n");

                    for (int j = 0; j < itemInfos.size(); j++) {
                        HarvestedItemInfo hii = (HarvestedItemInfo) itemInfos.get(j);

                        List<MDValue> titles = hii.item.getMetadata("dc", "title", null, MDValue.ANY);
                        emailText.append("      ").append(labels.getString("org.dspace.eperson.Subscribe.title"))
                                .append(" ");

                        if (titles.size() > 0) {
                            emailText.append(titles.get(0).getValue());
                        } else {
                            emailText.append(labels.getString("org.dspace.eperson.Subscribe.untitled"));
                        }

                        List<MDValue> authors = hii.item.getMetadata("dc", "contributor", MDValue.ANY, MDValue.ANY);

                        if (authors.size() > 0) {
                            emailText.append("\n    ")
                                    .append(labels.getString("org.dspace.eperson.Subscribe.authors")).append(" ")
                                    .append(authors.get(0).getValue());

                            for (int k = 1; k < authors.size(); k++) {
                                emailText.append("\n             ").append(authors.get(k).getValue());
                            }
                        }

                        emailText.append("\n         ").append(labels.getString("org.dspace.eperson.Subscribe.id"))
                                .append(" ").append(HandleManager.getCanonicalForm(hii.handle)).append("\n\n");
                    }
                }
            } catch (ParseException pe) {
                // This should never get thrown as the Dates are auto-generated
            }
        }

        // Send an e-mail if there were any new items
        if (emailText.length() > 0) {

            if (test) {
                log.info(LogManager.getHeader(context, "subscription:", "eperson=" + eperson.getEmail()));
                log.info(LogManager.getHeader(context, "subscription:", "text=" + emailText.toString()));

            } else {

                Email email = Email.fromTemplate(context,
                        I18nUtil.getEmailFilename(supportedLocale, "subscription"));
                email.addRecipient(eperson.getEmail());
                email.addArgument(emailText.toString());
                email.send();

                log.info(LogManager.getHeader(context, "sent_subscription", "eperson_id=" + eperson.getID()));

            }
        }
    }

    /**
     * Method for invoking subscriptions via the command line
     * 
     * @param args
     *            command-line arguments, none used yet
     */
    public static void main(String[] args) {
        Subscribe subscribe = new Subscribe();
        CmdLineParser parser = new CmdLineParser(subscribe);
        Context context = null;
        try {
            parser.parseArgument(args);
            if (subscribe.help) {
                parser.printUsage(System.err);
            } else {
                context = new Context();
                processDaily(context, subscribe.test);
                context.complete();
            }
            System.exit(0);
        } catch (CmdLineException clE) {
            System.err.println(clE.getMessage());
            parser.printUsage(System.err);
        } catch (Exception e) {
            log.error("Error processing daily", e);
            System.err.println(e.getMessage());
        } finally {
            if (context != null && context.isValid()) {
                // Nothing is actually written
                context.abort();
            }
        }
        System.exit(1);
    }

    private static List<HarvestedItemInfo> filterOutToday(List<HarvestedItemInfo> completeList) {
        log.debug("Filtering out all today item to leave new items list size=" + completeList.size());
        List<HarvestedItemInfo> filteredList = new ArrayList<HarvestedItemInfo>();

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String today = sdf.format(new Date());
        // Get the start and end dates for yesterday
        Date thisTimeYesterday = new Date(System.currentTimeMillis() - (24 * 60 * 60 * 1000));
        String yesterday = sdf.format(thisTimeYesterday);

        for (HarvestedItemInfo infoObject : completeList) {
            Date lastUpdate = infoObject.item.getLastModified();
            String lastUpdateStr = sdf.format(lastUpdate);

            // has the item modified today?
            if (lastUpdateStr.equals(today)) {
                List<MDValue> dateAccArr = infoObject.item.getMetadata("dc", "date", "accessioned", MDValue.ANY);
                // we need only the item archived yesterday
                if (dateAccArr != null && dateAccArr.size() > 0) {
                    for (MDValue date : dateAccArr) {
                        if (date != null && date.getValue() != null) {
                            // if it hasn't been archived today
                            if (date.getValue().startsWith(yesterday)) {
                                filteredList.add(infoObject);
                                log.debug("adding : " + dateAccArr.get(0).getValue() + " : " + today + " : "
                                        + infoObject.handle);
                                break;
                            } else {
                                log.debug("ignoring : " + dateAccArr.get(0).getValue() + " : " + today + " : "
                                        + infoObject.handle);
                            }
                        }
                    }
                } else {
                    log.debug("no date accessioned, adding  : " + infoObject.handle);
                    filteredList.add(infoObject);
                }
            } else {
                // the item has been modified yesterday... 
                filteredList.add(infoObject);
            }
        }

        return filteredList;
    }

    private static List<HarvestedItemInfo> filterOutModified(List<HarvestedItemInfo> completeList) {
        log.debug("Filtering out all modified to leave new items list size=" + completeList.size());
        List<HarvestedItemInfo> filteredList = new ArrayList<HarvestedItemInfo>();

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        // Get the start and end dates for yesterday
        Date thisTimeYesterday = new Date(System.currentTimeMillis() - (24 * 60 * 60 * 1000));
        String yesterday = sdf.format(thisTimeYesterday);

        for (HarvestedItemInfo infoObject : completeList) {
            List<MDValue> dateAccArr = infoObject.item.getMetadata("dc", "date", "accessioned", MDValue.ANY);

            if (dateAccArr != null && dateAccArr.size() > 0) {
                for (MDValue date : dateAccArr) {
                    if (date != null && date.getValue() != null) {
                        // if it has been archived yesterday
                        if (date.getValue().startsWith(yesterday)) {
                            filteredList.add(infoObject);
                            log.debug("adding : " + dateAccArr.get(0).getValue() + " : " + yesterday + " : "
                                    + infoObject.handle);
                            break;
                        } else {
                            log.debug("ignoring : " + dateAccArr.get(0).getValue() + " : " + yesterday + " : "
                                    + infoObject.handle);
                        }
                    }
                }

            } else {
                log.debug("no date accessioned, adding  : " + infoObject.handle);
                filteredList.add(infoObject);
            }
        }

        return filteredList;
    }
}