org.dspace.disseminate.CitationDocument.java Source code

Java tutorial

Introduction

Here is the source code for org.dspace.disseminate.CitationDocument.java

Source

package org.dspace.disseminate;

import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.draw.LineSeparator;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.AuthorizeManager;
import org.dspace.content.*;
import org.dspace.content.Collection;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Context;
import org.dspace.handle.HandleManager;

import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.sql.SQLException;
import java.util.*;

/**
 * The Citation Document produces a dissemination package (DIP) that is different that the archival package (AIP).
 * In this case we append the descriptive metadata to the end (configurable) of the document. i.e. last page of PDF.
 * So instead of getting the original PDF, you get a cPDF (with citation information added).
 *
 * @author Peter Dietz (dietz.72@osu.edu)
 */
public class CitationDocument {
    /**
     * Class Logger
     */
    private static Logger log = Logger.getLogger(CitationDocument.class);

    /**
     * A set of MIME types that can have a citation page added to them. That is,
     * MIME types in this set can be converted to a PDF which is then prepended
     * with a citation page.
     */
    private static final Set<String> VALID_TYPES = new HashSet<String>(2);

    /**
     * A set of MIME types that refer to a PDF
     */
    private static final Set<String> PDF_MIMES = new HashSet<String>(2);

    /**
     * A set of MIME types that refer to a JPEG, PNG, or GIF
     */
    private static final Set<String> RASTER_MIMES = new HashSet<String>();
    /**
     * A set of MIME types that refer to a SVG
     */
    private static final Set<String> SVG_MIMES = new HashSet<String>();

    /**
     * Comma separated list of collections handles to enable citation for.
     * webui.citation.enabled_collections, default empty/none. ex: =1811/123, 1811/345
     */
    private static String citationEnabledCollections = null;

    /**
     * Comma separated list of community handles to enable citation for.
     * webui.citation.enabled_communties, default empty/none. ex: =1811/123, 1811/345
     */
    private static String citationEnabledCommunities = null;

    /**
     * List of all enabled collections, inherited/determined for those under communities.
     */
    private static ArrayList<String> citationEnabledCollectionsList;

    static {
        // Add valid format MIME types to set. This could be put in the Schema
        // instead.
        //Populate RASTER_MIMES
        SVG_MIMES.add("image/jpeg");
        SVG_MIMES.add("image/pjpeg");
        SVG_MIMES.add("image/png");
        SVG_MIMES.add("image/gif");
        //Populate SVG_MIMES
        SVG_MIMES.add("image/svg");
        SVG_MIMES.add("image/svg+xml");

        //Populate PDF_MIMES
        PDF_MIMES.add("application/pdf");
        PDF_MIMES.add("application/x-pdf");

        //Populate VALID_TYPES
        VALID_TYPES.addAll(PDF_MIMES);

        //Load enabled collections
        citationEnabledCollections = ConfigurationManager.getProperty("disseminate-citation",
                "enabled_collections");
        citationEnabledCollectionsList = new ArrayList<String>();
        if (citationEnabledCollections != null && citationEnabledCollections.length() > 0) {
            String[] collectionChunks = citationEnabledCollections.split(",");
            for (String collectionString : collectionChunks) {
                citationEnabledCollectionsList.add(collectionString.trim());
            }

        }

        //Load enabled communities, and add to collection-list
        citationEnabledCommunities = ConfigurationManager.getProperty("disseminate-citation",
                "enabled_communities");
        if (citationEnabledCollectionsList == null) {
            citationEnabledCollectionsList = new ArrayList<String>();
        }

        if (citationEnabledCommunities != null && citationEnabledCommunities.length() > 0) {
            try {
                String[] communityChunks = citationEnabledCommunities.split(",");
                for (String communityString : communityChunks) {
                    Context context = new Context();
                    DSpaceObject dsoCommunity = HandleManager.resolveToObject(context, communityString.trim());
                    if (dsoCommunity instanceof Community) {
                        Community community = (Community) dsoCommunity;
                        Collection[] collections = community.getAllCollections();

                        for (Collection collection : collections) {
                            citationEnabledCollectionsList.add(collection.getHandle());
                        }
                    } else {
                        log.error("Invalid community for citation.enabled_communities, value:"
                                + communityString.trim());
                    }

                }
            } catch (SQLException e) {
                log.error(e.getMessage());
            }

        }
    }

    public CitationDocument() {
    }

    /**
     * Boolean to determine is citation-functionality is enabled globally for entire site.
     * config/module/disseminate-citation: enable_globally, default false. true=on, false=off
     */
    private static Boolean citationEnabledGlobally = null;

    private static boolean isCitationEnabledGlobally() {
        if (citationEnabledGlobally == null) {
            citationEnabledGlobally = ConfigurationManager.getBooleanProperty("disseminate-citation",
                    "enable_globally", false);
        }

        return citationEnabledGlobally;
    }

    private static boolean isCitationEnabledThroughCollection(Bitstream bitstream) throws SQLException {
        //TODO Should we re-check configs, and set the collections list?

        //Reject quickly if no-enabled collections
        if (citationEnabledCollectionsList.size() == 0) {
            return false;
        }

        DSpaceObject owningDSO = bitstream.getParentObject();
        if (owningDSO instanceof Item) {
            Item item = (Item) owningDSO;

            Collection[] collections = item.getCollections();

            for (Collection collection : collections) {
                if (citationEnabledCollectionsList.contains(collection.getHandle())) {
                    return true;
                }
            }
        }

        // If previous logic didn't return true, then we're false
        return false;
    }

    /**
     * Repository policy can specify to have a custom citation cover/tail page to the document, which embeds metadata.
     * We need to determine if we will intercept this bitstream download, and give out a citation dissemination rendition.
     *
     * What will trigger a redirect/intercept?
     *  Citation enabled globally (all citable bitstreams will get "watermarked") modules/disseminate-citation: enable_globally
     *    OR
     *  The container is this object is whitelist enabled.
     *      - community:  modules/disseminate-citation: enabled_communities
     *      - collection: modules/disseminate-citation: enabled_collections
     * AND
     *  This User is not an admin. (Admins need to be able to view the "raw" original instead.)
     * AND
     *  This object is citation-able (presently, just PDF)
     *
     *  The module must be enabled, before the permission level checks happen.
     * @param bitstream
     * @return
     */
    public static Boolean isCitationEnabledForBitstream(Bitstream bitstream, Context context) throws SQLException {
        if (isCitationEnabledGlobally() || isCitationEnabledThroughCollection(bitstream)) {

            boolean adminUser = AuthorizeManager.isAdmin(context);

            if (!adminUser && canGenerateCitationVersion(bitstream)) {
                return true;
            }

        }

        // If previous logic didn't return true, then we're false.
        return false;
    }

    /**
     * Should the citation page be the first page of the document, or the last page?
     * default => true. true => first page, false => last page
     * citation_as_first_page=true
     */
    private static Boolean citationAsFirstPage = null;

    private static Boolean isCitationFirstPage() {
        if (citationAsFirstPage == null) {
            citationAsFirstPage = ConfigurationManager.getBooleanProperty("disseminate-citation",
                    "citation_as_first_page", true);
        }

        return citationAsFirstPage;
    }

    public static boolean canGenerateCitationVersion(Bitstream bitstream) {
        return VALID_TYPES.contains(bitstream.getFormat().getMIMEType());
    }

    public File makeCitedDocument(Bitstream bitstream) {
        try {

            Item item = (Item) bitstream.getParentObject();
            CitationMeta cm = new CitationMeta(item);
            if (cm == null) {
                log.error("CitationMeta was null");
            }

            File citedDocumentFile = makeCitedDocument(bitstream, cm);
            if (citedDocumentFile == null) {
                log.error("Got a null citedDocumentFile in makeCitedDocument for bitstream");
            }
            return citedDocumentFile;
        } catch (Exception e) {
            log.error("makeCitedDocument from Bitstream fail!" + e.getMessage());
            return null;
        }

    }

    /**
     * Creates a
     * cited document from the given bitstream of the given item. This
     * requires that bitstream is contained in item.
     * <p>
     * The Process for adding a cover page is as follows:
     * <ol>
     *  <li> Load source file into PdfReader and create a
     *     Document to put our cover page into.</li>
     *  <li> Create cover page and add content to it.</li>
     *  <li> Concatenate the coverpage and the source
     *     document.</li>
     * </p>
     *
     * @param bitstream The source bitstream being cited. This must be a PDF.
     * @param cMeta The citation information used to generate the coverpage.
     * @return The temporary File that is the finished, cited document.
     * @throws com.itextpdf.text.DocumentException
     * @throws java.io.FileNotFoundException
     * @throws SQLException
     * @throws org.dspace.authorize.AuthorizeException
     */
    private File makeCitedDocument(Bitstream bitstream, CitationMeta cMeta)
            throws DocumentException, IOException, SQLException, AuthorizeException {
        //Read the source bitstream
        PdfReader source = new PdfReader(bitstream.retrieve());

        Document citedDoc = new Document(PageSize.LETTER);

        File coverTemp = File.createTempFile(bitstream.getName(), ".cover.pdf");

        //Need a writer instance to make changed to the document.
        PdfWriter writer = PdfWriter.getInstance(citedDoc, new FileOutputStream(coverTemp));

        //Call helper function to add content to the coverpage.
        this.generateCoverPage(citedDoc, writer, cMeta);

        //Create reader from finished cover page.
        PdfReader cover = new PdfReader(new FileInputStream(coverTemp));

        //Get page labels from source document
        String[] labels = PdfPageLabels.getPageLabels(source);

        //Concatenate the finished cover page with the source document.
        File citedTemp = File.createTempFile(bitstream.getName(), ".cited.pdf");
        OutputStream citedOut = new FileOutputStream(citedTemp);
        PdfConcatenate concat = new PdfConcatenate(citedOut);
        concat.open();

        //Is the citation-page the first page or last-page?
        if (isCitationFirstPage()) {
            //citation as cover page
            concat.addPages(cover);
            concat.addPages(source);
        } else {
            //citation as tail page
            concat.addPages(source);
            concat.addPages(cover);
        }

        //Put all of our labels in from the orignal document.
        if (labels != null) {
            PdfPageLabels citedPageLabels = new PdfPageLabels();
            log.debug("Printing arbitrary page labels.");

            for (int i = 0; i < labels.length; i++) {
                citedPageLabels.addPageLabel(i + 1, PdfPageLabels.EMPTY, labels[i]);
                log.debug("Label for page: " + (i + 1) + " -> " + labels[i]);
            }
            citedPageLabels.addPageLabel(labels.length + 1, PdfPageLabels.EMPTY, "Citation Page");
            concat.getWriter().setPageLabels(citedPageLabels);
        }

        //Close it up
        concat.close();

        //Close the cover-page
        writer.close();
        coverTemp.delete();

        citedTemp.deleteOnExit();
        return citedTemp;
    }

    /**
     * Takes a DSpace {@link Bitstream} and uses its associated METADATA to
     * create a cover page.
     *
     * @param cDoc The cover page document to add cited information to.
     * @param writer
     * @param cMeta
     *            METADATA retrieved from the parent collection.
     * @throws IOException
     * @throws DocumentException
     */
    private void generateCoverPage(Document cDoc, PdfWriter writer, CitationMeta cMeta) throws DocumentException {
        cDoc.open();
        writer.setCompressionLevel(0);

        Item item = cMeta.getItem();

        //Set up some fonts
        Font helv26 = FontFactory.getFont(FontFactory.HELVETICA, 26f, BaseColor.BLACK);
        Font helv16 = FontFactory.getFont(FontFactory.HELVETICA, 16f, BaseColor.BLACK);
        Font helv12 = FontFactory.getFont(FontFactory.HELVETICA, 12f, BaseColor.BLACK);
        Font helv12_italic = FontFactory.getFont(FontFactory.HELVETICA_OBLIQUE, 12f, BaseColor.BLACK);
        Font helv11_bold = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 11f, BaseColor.BLACK);
        Font helv9 = FontFactory.getFont(FontFactory.HELVETICA, 9f, BaseColor.BLACK);

        // 1 - Header:
        //  University Name
        //  Repository Name                                                        repository.url
        Paragraph university = new Paragraph("The Ohio State University", helv11_bold);
        cDoc.add(university);

        PdfPTable repositoryTable = new PdfPTable(2);
        repositoryTable.setWidthPercentage(100);

        Chunk repositoryName = new Chunk("Knowledge Bank", helv11_bold);
        PdfPCell nameCell = new PdfPCell();
        nameCell.setBorderWidth(0);
        nameCell.addElement(repositoryName);

        Chunk repositoryURL = new Chunk("kb.osu.edu", helv11_bold);
        repositoryURL.setAnchor("http://kb.osu.edu");

        PdfPCell urlCell = new PdfPCell();
        urlCell.setHorizontalAlignment(Element.ALIGN_RIGHT);
        urlCell.setBorderWidth(0);
        urlCell.addElement(repositoryURL);

        repositoryTable.addCell(nameCell);
        repositoryTable.addCell(urlCell);

        repositoryTable.setSpacingAfter(5);

        cDoc.add(repositoryTable);

        // Line Separator
        LineSeparator lineSeparator = new LineSeparator();
        cDoc.add(lineSeparator);

        // 2 - Bread Crumbs
        // Community Name                                                          Collection Name
        PdfPTable breadcrumbTable = new PdfPTable(2);
        breadcrumbTable.setWidthPercentage(100);

        Chunk communityName = new Chunk(getOwningCommunity(item), helv9);
        PdfPCell commCell = new PdfPCell();
        commCell.setBorderWidth(0);
        commCell.addElement(communityName);

        Chunk collectionName = new Chunk(getOwningCollection(item), helv9);
        PdfPCell collCell = new PdfPCell();
        collCell.setHorizontalAlignment(Element.ALIGN_RIGHT);
        collCell.setBorderWidth(0);
        collCell.addElement(collectionName);

        breadcrumbTable.addCell(commCell);
        breadcrumbTable.addCell(collCell);

        breadcrumbTable.setSpacingBefore(5);
        breadcrumbTable.setSpacingAfter(5);

        cDoc.add(breadcrumbTable);

        // Line Separator
        cDoc.add(lineSeparator);

        // 3 - Metadata
        // date.issued
        // dc.title
        // dc.creator; dc.creator
        Paragraph dateIssued = new Paragraph(getFirstMetadata(item, "dc.date.issued"), helv12);
        dateIssued.setSpacingBefore(20);
        cDoc.add(dateIssued);

        Paragraph title = new Paragraph(item.getName(), helv26);
        title.setSpacingBefore(15);
        cDoc.add(title);

        Paragraph creators = new Paragraph(getAllMetadataSeperated(item, "dc.creator"), helv16);
        creators.setSpacingBefore(30);
        creators.setSpacingAfter(20);
        cDoc.add(creators);

        // Line Separator
        cDoc.add(lineSeparator);

        // 4 - Citation
        // dc.identifier.citation
        // dc.identifier.uri
        Paragraph citation = new Paragraph(getFirstMetadata(item, "dc.identifier.citation"), helv12);

        Chunk identifierChunk = new Chunk(getFirstMetadata(item, "dc.identifier.uri"), helv12);
        identifierChunk.setAnchor(getFirstMetadata(item, "dc.identifier.uri"));

        Paragraph identifier = new Paragraph();
        identifier.add(identifierChunk);

        cDoc.add(citation);
        cDoc.add(identifier);

        // 5 - License
        // Downloaded from the Knowledge Bank, The Ohio State University's institutional repository
        Paragraph license = new Paragraph(
                "Downloaded from the Knowledge Bank, The Ohio State University's institutional repository",
                helv12_italic);
        license.setSpacingBefore(10);
        cDoc.add(license);

        cDoc.close();
    }

    /**
     * Attempts to add a Logo to the document from the given resource. Returns
     * true on success and false on failure.
     *
     * @param doc The document to add the logo to. (Added to the top right
     * corner of the first page.
     * @param writer The writer associated with the given Document.
     * @param res The resource/path to the logo file. This file can be any of
     * the following formats:
     *  GIF, PNG, JPEG, PDF
     *
     * @return Succesfully added logo to document.
     */
    private boolean addLogoToDocument(Document doc, PdfWriter writer, String res) {
        boolean ret = false;
        try {
            //First we try to get the logo as if it is a Java Resource
            URL logoURL = this.getClass().getResource(res);
            log.debug(res + " -> " + logoURL.toString());
            if (logoURL == null) {
                logoURL = new URL(res);
            }

            if (logoURL != null) {
                String mtype = URLConnection.guessContentTypeFromStream(logoURL.openStream());
                if (mtype == null) {
                    mtype = URLConnection.guessContentTypeFromName(res);
                }
                log.debug("Determined MIMETYPE of logo: " + mtype);
                if (PDF_MIMES.contains(mtype)) {
                    //Handle pdf logos.
                    PdfReader reader = new PdfReader(logoURL);
                    PdfImportedPage logoPage = writer.getImportedPage(reader, 1);
                    Image logo = Image.getInstance(logoPage);
                    float x = doc.getPageSize().getWidth() - doc.rightMargin() - logo.getScaledWidth();
                    float y = doc.getPageSize().getHeight() - doc.topMargin() - logo.getScaledHeight();
                    logo.setAbsolutePosition(x, y);
                    doc.add(logo);
                    ret = true;
                } else if (RASTER_MIMES.contains(mtype)) {
                    //Use iText's Image class
                    Image logo = Image.getInstance(logoURL);

                    //Determine the position of the logo (upper-right corner) and
                    //place it there.
                    float x = doc.getPageSize().getWidth() - doc.rightMargin() - logo.getScaledWidth();
                    float y = doc.getPageSize().getHeight() - doc.topMargin() - logo.getScaledHeight();
                    logo.setAbsolutePosition(x, y);
                    writer.getDirectContent().addImage(logo);
                    ret = true;
                } else if (SVG_MIMES.contains(mtype)) {
                    //Handle SVG Logos
                    log.error("SVG Logos are not supported yet.");
                } else {
                    //Cannot use other mimetypes
                    log.debug("Logo MIMETYPE is not supported.");
                }
            } else {
                log.debug("Could not create URL to Logo resource: " + res);
            }
        } catch (Exception e) {
            log.error("Could not add logo (" + res + ") to cited document: " + e.getMessage());
            ret = false;
        }
        return ret;
    }

    public String getOwningCommunity(Item item) {
        try {
            Community[] comms = item.getCommunities();
            if (comms.length > 0) {
                return comms[0].getName();
            } else {
                return " ";
            }

        } catch (SQLException e) {
            log.error(e.getMessage());
            return e.getMessage();
        }
    }

    public String getOwningCollection(Item item) {
        try {
            return item.getOwningCollection().getName();
        } catch (SQLException e) {
            log.error(e.getMessage());
            return e.getMessage();
        }
    }

    public String getFirstMetadata(Item item, String metadataKey) {
        DCValue[] dcValues = item.getMetadata(metadataKey);
        if (dcValues != null && dcValues.length > 0) {
            return dcValues[0].value;
        } else {
            return " ";
        }
    }

    public String getAllMetadataSeperated(Item item, String metadataKey) {
        DCValue[] dcValues = item.getMetadata(metadataKey);
        ArrayList<String> valueArray = new ArrayList<String>();

        for (DCValue dcValue : dcValues) {
            valueArray.add(dcValue.value);
        }

        return StringUtils.join(valueArray.toArray(), "; ");
    }

    /**
     * This wraps the item used in its constructor to make it easier to access
     * METADATA.
     */
    private class CitationMeta {
        private Collection parent;
        private Map<String, String> metaData;
        private Item myItem;

        /**
         * Constructs CitationMeta object from an Item. It uses item specific
         * METADATA as well as METADATA from the owning collection.
         *
         * @param item An Item to get METADATA from.
         * @throws java.sql.SQLException
         */
        public CitationMeta(Item item) throws SQLException {
            this.myItem = item;
            this.metaData = new HashMap<String, String>();
            //Get all METADATA from our this.myItem
            DCValue[] dcvs = this.myItem.getMetadata(Item.ANY, Item.ANY, Item.ANY, Item.ANY);
            //Put METADATA in a Map for easy access.
            for (DCValue dsv : dcvs) {
                String[] dsvParts = { dsv.schema, dsv.element, dsv.qualifier, dsv.language, dsv.authority };
                StringBuilder keyBuilder = new StringBuilder();
                for (String part : dsvParts) {
                    if (part != null && part != "") {
                        keyBuilder.append(part + '.');
                    }
                }
                //Remove the trailing '.'
                keyBuilder.deleteCharAt(keyBuilder.length() - 1);
                this.metaData.put(keyBuilder.toString(), dsv.value);
            }

            //Get METADATA from the owning Collection
            this.parent = this.myItem.getOwningCollection();
        }

        /**
         * Returns a map of the METADATA for the item associated with this
         * instance of CitationMeta.
         *
         * @return a Map of the METADATA for the associated item.
         */
        public Map<String, String> getMetaData() {
            return this.metaData;
        }

        public Item getItem() {
            return this.myItem;
        }

        public Collection getCollection() {
            return this.parent;
        }

        /**
         * {@inheritDoc}
         * @see Object#toString()
         * @return A string with the format:
         *  CitationPage.CitationMeta {
         *      CONTENT
         *  }
         *  Where CONTENT is the METADATA derived by this class.
         */
        @Override
        public String toString() {
            StringBuilder ret = new StringBuilder(CitationMeta.class.getName());
            ret.append(" {<br />\n\t");
            ret.append(this.parent.getName());
            ret.append("\n\t");
            ret.append(this.myItem.getName());
            ret.append("\n\t");
            ret.append(this.metaData);
            ret.append("\n}\n");
            return ret.toString();
        }
    }

}