diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml
index 3ef3c19d09..297e08d2f4 100644
--- a/dspace-api/pom.xml
+++ b/dspace-api/pom.xml
@@ -618,6 +618,19 @@
com.google.code.findbugs
annotations
+
+
+ com.itextpdf
+ itextpdf
+ 5.1.2
+ jar
+
+
+ com.itextpdf.tool
+ xmlworker
+ 1.1.0
+ jar
+
diff --git a/dspace-api/src/main/java/org/dspace/curate/CitationPage.java b/dspace-api/src/main/java/org/dspace/curate/CitationPage.java
new file mode 100644
index 0000000000..6f0c425631
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/curate/CitationPage.java
@@ -0,0 +1,232 @@
+/**
+ * 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.curate;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.log4j.Logger;
+import org.dspace.authorize.AuthorizeException;
+import org.dspace.content.*;
+import org.dspace.disseminate.CitationDocument;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * CitationPage
+ *
+ * This task is used to generate a cover page with citation information for text
+ * documents and then to add that cover page to a PDF version of the document
+ * replacing the originally uploaded document form the user's perspective.
+ *
+ * @author Ryan McGowan
+ */
+
+@Distributive
+@Mutative
+public class CitationPage extends AbstractCurationTask {
+ /**
+ * Class Logger
+ */
+ private static Logger log = Logger.getLogger(CitationPage.class);
+
+ private int status = Curator.CURATE_UNSET;
+ private String result = null;
+ /**
+ * A StringBuilder to handle result string building process.
+ */
+ private StringBuilder resBuilder;
+
+
+
+
+ /**
+ * The name to give the bundle we add the cited pages to.
+ */
+ private static final String DISPLAY_BUNDLE_NAME = "DISPLAY";
+ /**
+ * The name of the bundle to move source documents into after they have been
+ * cited.
+ */
+ private static final String PRESERVATION_BUNDLE_NAME = "PRESERVATION";
+
+ /**
+ * {@inheritDoc}
+ * @see CurationTask#perform(DSpaceObject)
+ */
+ @Override
+ public int perform(DSpaceObject dso) throws IOException {
+
+ // Deal with status and result as well as call distribute.
+ this.resBuilder = new StringBuilder();
+ this.distribute(dso);
+ this.result = this.resBuilder.toString();
+ this.setResult(this.result);
+ this.report(this.result);
+
+ return this.status;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @see AbstractCurationTask#performItem(Item)
+ */
+ @Override
+ protected void performItem(Item item) throws SQLException {
+ //Determine if the DISPLAY bundle exits. If not, create it.
+ Bundle[] dBundles = item.getBundles(CitationPage.DISPLAY_BUNDLE_NAME);
+ Bundle dBundle = null;
+ if (dBundles == null || dBundles.length == 0) {
+ try {
+ dBundle = item.createBundle(CitationPage.DISPLAY_BUNDLE_NAME);
+ } catch (AuthorizeException e) {
+ log.error("User not authroized to create bundle on item \""
+ + item.getName() + "\": " + e.getMessage());
+ }
+ } else {
+ dBundle = dBundles[0];
+ }
+
+ //Create a map of the bitstreams in the displayBundle. This is used to
+ //check if the bundle being cited is already in the display bundle.
+ Map displayMap = new HashMap();
+ for (Bitstream bs : dBundle.getBitstreams()) {
+ displayMap.put(bs.getName(), bs);
+ }
+
+ //Determine if the preservation bundle exists and add it if we need to.
+ //Also, set up bundles so it contains all ORIGINAL and PRESERVATION
+ //bitstreams.
+ Bundle[] pBundles = item.getBundles(CitationPage.PRESERVATION_BUNDLE_NAME);
+ Bundle pBundle = null;
+ Bundle[] bundles = null;
+ if (pBundles != null && pBundles.length > 0) {
+ pBundle = pBundles[0];
+ bundles = (Bundle[]) ArrayUtils.addAll(item.getBundles("ORIGINAL"), pBundles);
+ } else {
+ try {
+ pBundle = item.createBundle(CitationPage.PRESERVATION_BUNDLE_NAME);
+ } catch (AuthorizeException e) {
+ log.error("User not authroized to create bundle on item \""
+ + item.getName() + "\": " + e.getMessage());
+ }
+ bundles = item.getBundles("ORIGINAL");
+ }
+
+ //Start looping through our bundles. Anything that is citable in these
+ //bundles will be cited.
+ for (Bundle bundle : bundles) {
+ Bitstream[] bitstreams = bundle.getBitstreams();
+
+ // Loop through each file and generate a cover page for documents
+ // that are PDFs.
+ for (Bitstream bitstream : bitstreams) {
+ BitstreamFormat format = bitstream.getFormat();
+
+ //If bitstream is a PDF document then it is citable.
+ CitationDocument citationDocument = new CitationDocument();
+
+ if(citationDocument.canGenerateCitationVersion(bitstream)) {
+ this.resBuilder.append(item.getHandle() + " - "
+ + bitstream.getName() + " is citable.");
+ try {
+ //Create the cited document
+ File citedDocument = citationDocument.makeCitedDocument(bitstream);
+ //Add the cited document to the approiate bundle
+ this.addCitedPageToItem(citedDocument, bundle, pBundle,
+ dBundle, displayMap, item, bitstream);
+ } catch (Exception e) {
+ //Could be many things, but nothing that should be
+ //expected.
+ //Print out some detailed information for debugging.
+ e.printStackTrace();
+ StackTraceElement[] stackTrace = e.getStackTrace();
+ StringBuilder stack = new StringBuilder();
+ int numLines = Math.min(stackTrace.length, 12);
+ for (int j = 0; j < numLines; j++) {
+ stack.append("\t" + stackTrace[j].toString() + "\n");
+ }
+ if (stackTrace.length > numLines) {
+ stack.append("\t. . .\n");
+ }
+
+ log.error(e.toString() + " -> \n" + stack.toString());
+ this.resBuilder.append(", but there was an error generating the PDF.\n");
+ this.status = Curator.CURATE_ERROR;
+ }
+ } else {
+ //bitstream is not a document
+ this.resBuilder.append(item.getHandle() + " - "
+ + bitstream.getName() + " is not citable.\n");
+ this.status = Curator.CURATE_SUCCESS;
+ }
+ }
+ }
+ }
+
+ /**
+ * A helper function for {@link CitationPage#performItem(Item)}. This function takes in the
+ * cited document as a File and adds it to DSpace properly.
+ *
+ * @param citedTemp The temporary File that is the cited document.
+ * @param bundle The bundle the cited file is from.
+ * @param pBundle The preservation bundle. The original document should be
+ * put in here if it is not already.
+ * @param dBundle The display bundle. The cited document gets put in here.
+ * @param displayMap The map of bitstream names to bitstreams in the display
+ * bundle.
+ * @param item The item containing the bundles being used.
+ * @param bitstream The original source bitstream.
+ * @throws SQLException
+ * @throws AuthorizeException
+ * @throws IOException
+ */
+ private void addCitedPageToItem(File citedTemp, Bundle bundle, Bundle pBundle,
+ Bundle dBundle, Map displayMap, Item item,
+ Bitstream bitstream) throws SQLException, AuthorizeException, IOException {
+ //If we are modifying a file that is not in the
+ //preservation bundle then we have to move it there.
+ if (bundle.getID() != pBundle.getID()) {
+ pBundle.addBitstream(bitstream);
+ bundle.removeBitstream(bitstream);
+ Bitstream[] originalBits = bundle.getBitstreams();
+ if (originalBits == null || originalBits.length == 0) {
+ item.removeBundle(bundle);
+ }
+ }
+
+ //Create an input stream form the temporary file
+ //that is the cited document and create a
+ //bitstream from it.
+ InputStream inp = new FileInputStream(citedTemp);
+ if (displayMap.containsKey(bitstream.getName())) {
+ dBundle.removeBitstream(displayMap.get(bitstream.getName()));
+ }
+ Bitstream citedBitstream = dBundle.createBitstream(inp);
+ inp.close(); //Close up the temporary InputStream
+
+ //Setup a good name for our bitstream and make
+ //it the same format as the source document.
+ citedBitstream.setName(bitstream.getName());
+ citedBitstream.setFormat(bitstream.getFormat());
+ citedBitstream.setDescription(bitstream.getDescription());
+
+ this.resBuilder.append(" Added "
+ + citedBitstream.getName()
+ + " to the " + CitationPage.DISPLAY_BUNDLE_NAME + " bundle.\n");
+
+ //Run update to propagate changes to the
+ //database.
+ item.update();
+ this.status = Curator.CURATE_SUCCESS;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/disseminate/CitationDocument.java b/dspace-api/src/main/java/org/dspace/disseminate/CitationDocument.java
new file mode 100644
index 0000000000..bbbc24b23b
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/disseminate/CitationDocument.java
@@ -0,0 +1,665 @@
+/**
+ * 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.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 VALID_TYPES = new HashSet(2);
+
+ /**
+ * A set of MIME types that refer to a PDF
+ */
+ private static final Set PDF_MIMES = new HashSet(2);
+
+ /**
+ * A set of MIME types that refer to a JPEG, PNG, or GIF
+ */
+ private static final Set RASTER_MIMES = new HashSet();
+ /**
+ * A set of MIME types that refer to a SVG
+ */
+ private static final Set SVG_MIMES = new HashSet();
+
+ /**
+ * 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 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();
+ 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();
+ }
+
+ 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.
+ *
+ * The Process for adding a cover page is as follows:
+ *
+ * - Load source file into PdfReader and create a
+ * Document to put our cover page into.
+ * - Create cover page and add content to it.
+ * - Concatenate the coverpage and the source
+ * document.
+ *
+ *
+ * @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(item.getMetadata("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(item.getMetadata("dc.identifier.citation"), helv12);
+
+ Chunk identifierChunk = new Chunk(item.getMetadata("dc.identifier.uri"), helv12);
+ identifierChunk.setAnchor(item.getMetadata("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 getAllMetadataSeperated(Item item, String metadataKey) {
+ DCValue[] dcValues = item.getMetadataByMetadataString(metadataKey);
+
+ ArrayList valueArray = new ArrayList();
+
+ 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 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();
+ //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 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(" {
\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();
+ }
+ }
+
+}
diff --git a/dspace-xmlui/src/main/java/org/dspace/app/xmlui/cocoon/BitstreamReader.java b/dspace-xmlui/src/main/java/org/dspace/app/xmlui/cocoon/BitstreamReader.java
index 7a84db6dbb..95eb51da57 100644
--- a/dspace-xmlui/src/main/java/org/dspace/app/xmlui/cocoon/BitstreamReader.java
+++ b/dspace-xmlui/src/main/java/org/dspace/app/xmlui/cocoon/BitstreamReader.java
@@ -7,9 +7,7 @@
*/
package org.dspace.app.xmlui.cocoon;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
+import java.io.*;
import java.net.URLEncoder;
import java.sql.SQLException;
import java.util.Map;
@@ -42,6 +40,7 @@ import org.dspace.content.Item;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Constants;
import org.dspace.core.Context;
+import org.dspace.disseminate.CitationDocument;
import org.dspace.handle.HandleManager;
import org.dspace.usage.UsageEvent;
import org.dspace.utils.DSpace;
@@ -157,6 +156,9 @@ public class BitstreamReader extends AbstractReader implements Recyclable
/** True if user agent making this request was identified as spider. */
private boolean isSpider = false;
+ /** TEMP file for citation PDF. We will save here, so we can delete the temp file when done. */
+ private File tempFile;
+
/**
* Set up the bitstream reader.
*
@@ -317,11 +319,53 @@ public class BitstreamReader extends AbstractReader implements Recyclable
}
}
}
-
+
// Success, bitstream found and the user has access to read it.
// Store these for later retrieval:
- this.bitstreamInputStream = bitstream.retrieve();
- this.bitstreamSize = bitstream.getSize();
+
+ // Intercepting views to the original bitstream to instead show a citation altered version of the object
+ // We need to check if this resource falls under the "show watermarked alternative" umbrella.
+ // At which time we will not return the "bitstream", but will instead on-the-fly generate the citation rendition.
+
+ // What will trigger a redirect/intercept?
+ // 1) Intercepting Enabled
+ // 2) This User is not an admin
+ // 3) This object is citation-able
+ if (CitationDocument.isCitationEnabledForBitstream(bitstream, context)) {
+ // on-the-fly citation generator
+ log.info(item.getHandle() + " - " + bitstream.getName() + " is citable.");
+
+ FileInputStream fileInputStream = null;
+ CitationDocument citationDocument = new CitationDocument();
+
+ try {
+ //Create the cited document
+ tempFile = citationDocument.makeCitedDocument(bitstream);
+ if(tempFile == null) {
+ log.error("CitedDocument was null");
+ } else {
+ log.info("CitedDocument was ok," + tempFile.getAbsolutePath());
+ }
+
+
+ fileInputStream = new FileInputStream(tempFile);
+ if(fileInputStream == null) {
+ log.error("Error opening fileInputStream: ");
+ }
+
+ this.bitstreamInputStream = fileInputStream;
+ this.bitstreamSize = tempFile.length();
+
+ } catch (Exception e) {
+ log.error("Caught an error with intercepting the citation document:" + e.getMessage());
+ }
+
+ //End of CitationDocument
+ } else {
+ this.bitstreamInputStream = bitstream.retrieve();
+ this.bitstreamSize = bitstream.getSize();
+ }
+
this.bitstreamMimeType = bitstream.getFormat().getMIMEType();
this.bitstreamName = bitstream.getName();
if (context.getCurrentUser() == null)
@@ -351,8 +395,17 @@ public class BitstreamReader extends AbstractReader implements Recyclable
}
else
{
- // In case there is no bitstream name...
- bitstreamName = "bitstream";
+ // In-case there is no bitstream name...
+ if(name != null && name.length() > 0) {
+ bitstreamName = name;
+ if(name.endsWith(".jpg")) {
+ bitstreamMimeType = "image/jpeg";
+ } else if(name.endsWith(".png")) {
+ bitstreamMimeType = "image/png";
+ }
+ } else {
+ bitstreamName = "bitstream";
+ }
}
// Log that the bitstream has been viewed, this is non-cached and the complexity
diff --git a/dspace/config/modules/disseminate-citation.cfg b/dspace/config/modules/disseminate-citation.cfg
new file mode 100644
index 0000000000..0b4dd29198
--- /dev/null
+++ b/dspace/config/modules/disseminate-citation.cfg
@@ -0,0 +1,15 @@
+#Boolean to determine is citation-functionality is enabled globally for entire site.
+#default => false
+enable_globally=true
+
+#List of collection handles to enable, to apply to nested bitstreams within
+#default => empty. This is to be collection handles, separated by commas. ex: 1811/123, 1811/234
+#enabled_collections=
+
+#List of community handles to enable, to apply to nested bitstreams within
+#default => empty. This is to be community handles, separated by commas. ex: 1811/222, 1811/333
+#enabled_communities=
+
+#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
\ No newline at end of file