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: + *

    + *
  1. Load source file into PdfReader and create a + * Document to put our cover page into.
  2. + *
  3. Create cover page and add content to it.
  4. + *
  5. Concatenate the coverpage and the source + * document.
  6. + *

    + * + * @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