package uk.ac.jorum.dspace.utils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.dspace.app.util.SubmissionInfo;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Bitstream;
import org.dspace.content.BitstreamFormat;
import org.dspace.content.Bundle;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.content.WorkspaceItem;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.handle.HandleManager;
import uk.ac.jorum.utils.ExceptionLogger;
/**
* @author gwaller
*
*/
/**
* @author cgormle1
*
*/
public class BundleUtils {
private static Logger logger = Logger.getLogger(BundleUtils.class);
public static int highestSequenceNumInBundles(Item item){
int highestSeq = 0;
try{
Bundle[] bunds = item.getBundles();
// find the highest current sequence number
for (int i = 0; i < bunds.length; i++)
{
Bitstream[] streams = bunds[i].getBitstreams();
for (int k = 0; k < streams.length; k++)
{
if (streams[k].getSequenceID() > highestSeq)
{
highestSeq = streams[k].getSequenceID();
}
}
}
} catch (Exception e){
ExceptionLogger.logException(logger, e);
}
return highestSeq;
}
/**
* This method copies all bundles and bitstreams from one item to another, creating bundles as necessary.
* If a bundle in the destination item exits, the bitstreams are meerly copied but the sequence numbers in the
* copied bitstreams are adjusted i.e. they start at a sequence number greater than the highest sequence number
* already existing in any bundle in the destination item
* NOTE: update is called on each item to ensure the sequence numbers are set before any copying.
* @param fromItem - source item to copy from NOTE: sequence ids for bitstreams in this instance will be altered also!
* @param toItem - destination item to store the copied bundles/bitstreams
* @throws SQLException
* @throws AuthorizeException
*/
public static void copyBundlesAndResequence(WorkspaceItem fromItem, Item toItem, String[] exclusions) throws SQLException, AuthorizeException{
// Call update on the items to ensure the sequence numbers are set - do this with auth turned off as a non admin user may be doing this
// on an installed item!
try{
toItem.getContext().turnOffAuthorisationSystem();
toItem.update();
fromItem.getItem().getContext().turnOffAuthorisationSystem();
fromItem.getItem().update();
} finally{
toItem.getContext().restoreAuthSystemState();
fromItem.getItem().getContext().restoreAuthSystemState();
}
// Get the higest sequence number in the item we are copying to (rem sequence numbers have to be unique)
int highestSequence = highestSequenceNumInBundles(toItem);
Bundle[] packageBundles = fromItem.getItem().getBundles();
logger.debug("Found " + packageBundles.length + " bundles on submitted package item");
for (Bundle packageBundle:packageBundles){
// Check bundle name isn't in the exclusion list
boolean exclusionFound = false;
if (exclusions != null){
for (int i = 0; i < exclusions.length; i++){
if (exclusions[i].equals(packageBundle.getName())){
exclusionFound = true;
break;
}
}
}
// if we found an exclusion - move onto the next
if (exclusionFound){
continue;
}
logger.debug("Adding bundle to orig submission item: " + packageBundle );
Bundle[] matchingBundles = toItem.getBundles(packageBundle.getName());
if (matchingBundles.length > 0){
// add the bitstreams to the matching bundle - use the first one found
Bitstream[] packageStreams = packageBundle.getBitstreams();
for (int i = 0; i < packageStreams.length; i++){
// NOTE: THIS JUST COPIES THE POINTER - SHOULD REALLY CLONE HERE!!
Bitstream packageBitStream = packageStreams[i];
// Need to alter the sequence number of the bitstream if we are adding to an existing package
// NOTE: THIS WILL RESET THE SEQ ID IN THE BITSTREAM WE WISH TO COPY TOO!!
packageBitStream.setSequenceID(packageBitStream.getSequenceID() + highestSequence);
// Update the db
packageBitStream.update();
matchingBundles[0].addBitstream(packageStreams[i]);
}
// Do not delete the bundle here, even thought he bitstreams were copied - causes a Postgres Exception!
} else {
// No matching bundle with the same name in the initial Item - just copy the whole bundle from the package
//toItem.addBundle(packageBundle);
Bundle newBundle = toItem.createBundle(packageBundle.getName());
Bitstream[] streams = packageBundle.getBitstreams();
for (Bitstream b : streams){
b.setSequenceID(b.getSequenceID() + highestSequence);
b.update();
newBundle.addBitstream(b);
}
}
}
}
/**
* Helper method to either find the first bundle matching the name supplied or create one if it isn't found.
* This method differs from the one in JorumUploadStep as it does not delete any previous
* URL bundle.
* @param item the item the bundle should belong to
* @param bundleName the name of the bundle to find/create
* @return the first bundle found or a newly created one if it didn't previously exist
* @throws SQLException
* @throws AuthorizeException
*/
public static Bundle getBundleByName(Item item, String bundleName) throws
SQLException,
AuthorizeException{
Bundle result;
Bundle[] bundles = item.getBundles(bundleName);
if (bundles.length == 0){
// Create the bundle
result = item.createBundle(bundleName);
} else {
// Return the first one
result = bundles[0];
}
return result;
}
/**
* This helper method takes some bytes and stores them as a bitstream for an
* item in the specified bundle, with the given bitstream name.
* @param bundle The bundle to be written to
* @param bitstream_name The name to be given to the bundle
* @param format The format of the bundle
* @param bytes The bytes to be written to the bundle
* @param setAsPrimary If set to true, this stream is markes as the primary in the Bundle
* @throws SQLException
* @throws IOException
* @throws AuthorizeException
*/
// GWaller 20/8/09 Added param setAsPrimary and made public - used by IMSIngester
// GWaller 12/11/09 Modified signature - return the bitstream created
public static Bitstream setBitstreamFromBytes(Bundle bundle,
String bitstream_name,
BitstreamFormat format,
byte[] bytes,
boolean setAsPrimary)
throws SQLException, IOException, AuthorizeException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Bitstream bs = bundle.createBitstream(bais);
bs.setName(bitstream_name);
bs.setSource(bundle.getName());
bs.setFormat(format);
// commit everything
bs.update();
if (setAsPrimary){
//set as primary bitstream
bundle.setPrimaryBitstreamID(bs.getID());
bundle.update();
}
return bs;
}
//CG 09/10/09
/**
* Utility method to check for existence of a url bundle
* This should be used to check the type of submission when uploading via Manakin
*
* @param subInfo
* @return
* @throws SQLException
*/
public static boolean checkUrl(SubmissionInfo subInfo) throws SQLException{
Item item = subInfo.getSubmissionItem().getItem();
if(item.getBundles(Constants.URL_BUNDLE).length>0 && item.getBundles(Constants.ARCHIVED_CONTENT_PACKAGE_BUNDLE).length==0)
return true;
return false;
}
// START GWaller Support for feed URLs
public static String getFirstUrlInUrlBundle(Item item) throws SQLException{
String url = null;
Bundle urlBundles[] = item.getBundles(Constants.URL_BUNDLE);
if (urlBundles.length > 0){
// Get the first bitstream in the first url bundle
Bitstream streams[] = urlBundles[0].getBitstreams();
// url shoudl be the bitstream name
if (streams.length > 0){
url = streams[0].getName();
}
}
return url;
}
public static void setFirstUrlInUrlBundle(Context context, Item item, String origUrl, String newUrl) throws AuthorizeException, IOException, SQLException{
Bundle urlBundles[] = item.getBundles(Constants.URL_BUNDLE);
if (urlBundles.length > 0){
// Get the first bitstream in the first url bundle
Bitstream stream = urlBundles[0].getBitstreamByName(origUrl);
// delete the stream if we found it
if (stream != null){
urlBundles[0].removeBitstream(stream);
}
BitstreamFormat bs_format = BitstreamFormat.findByShortDescription(context, "Text");
// set the URL as the primary bitstream
BundleUtils.setBitstreamFromBytes(urlBundles[0], newUrl, bs_format, newUrl.getBytes(), true);
}
}
// END GWaller Support for feed URLs
//CG 30/10/09 Refactored from method originally in JorumUploadStep
/**
* Clears existing content in specified metadata element
* and adds new content.
*
* @param content The content to be added to the metadata
* @param item The submission item
* @param schema The schema to use (dc)
* @param element The dc element
* @param qualifier Any qualifier for the dc element
* @param lang The language of the entry
* @throws SQLException
* @throws AuthorizeException
*/
public static void clearAndSetMetadataElement(String content, Item item, String schema, String element, String qualifier, String lang) throws SQLException, AuthorizeException {
// Remove existing values
item.clearMetadata(schema, element, qualifier, Item.ANY);
if (content != null) {
item.addMetadata(schema, element, qualifier, lang, content.trim());
}
//If we don't write changes to the db and user navigates to previous screen, old value will persist
item.update();
}
/**
* Helper method to simply check for the existance of a named bundle
* @param item the item the bundle should belong to
* @param bundleName the name of the bundle to find/create
* @return true if a Bundle was found matching the name supplied
* @throws SQLException
* @throws AuthorizeException
*/
public static boolean hasBundle(Item item, String bundleName) throws
SQLException,
AuthorizeException{
Bundle[] bundles = item.getBundles(bundleName);
return bundles.length > 0;
}
/**
* Added by CG 10/11/09 to fix IssueID#134
*
* Works out which bundle should be displayed in the uploaded files section of
* the file upload step
*
* @param item
* @return
* @throws SQLException
*/
public static Bundle[] getBundlesForDisplay(Item item, Context context) throws SQLException {
// Get all potential bundles
Bundle[] original = item.getBundles("ORIGINAL");
int originalLength = original.length;
Bundle[] archived = item.getBundles(Constants.ARCHIVED_CONTENT_PACKAGE_BUNDLE);
Bundle[] relatedCP = item.getBundles(Constants.RELATED_CONTENT_PACKAGE_BUNDLE);
if (relatedCP.length > 0) {
// If user uploads more than 1 content package
// the original bundle will be empty, so we
// have to display the archived CPs from the
// related bundles
// Follow ids of the related CP so we can then get the Archived CPs for display
Bundle[] bundles = getRelatedCPBundles(context, relatedCP);
// if there is something in the original bitstream we have to display this as well
if (originalLength > 0) {
if ((original[0].getBitstreams().length > 0)) {
int bundlesLength = bundles.length;
// if someone uploads a normal file as well as a cp, need to display both
Bundle[] result = new Bundle[originalLength + bundlesLength];
System.arraycopy(original, 0, result, 0, originalLength);
System.arraycopy(bundles, 0, result, originalLength, bundlesLength);
return result;
}
}
return bundles;
} else if (originalLength > 0 && archived.length == 0) {
// 2 cases when this occurs:
// 1 User has just uploaded a cp - not been processed yet
// therefore the archived cp has not been generated, so
// we should just display the contents of the original
// bundle
// 2. User has uploaded normal file(s)
return original;
}
// For all other cases, we display the archived content package
return archived;
}
/**
* Utility method which gets the ARCHIVED_CONTENT_PACKAGE_BUNDLE of related
* items, given the relatedCP of the wrapper item
*
* @param context
* @param relatedCP
* @return
* @throws SQLException
*/
public static Bundle[] getRelatedCPBundles(Context context, Bundle[] relatedCP) throws SQLException {
Bundle[] bundles = new Bundle[relatedCP[0].getBitstreams().length];
int count = 0;
for (Bundle b : relatedCP) {
Bitstream[] bitstreams = b.getBitstreams();
// Cycle through the related items
// for each, get the ArchivedCP bundle
for (Bitstream bit : bitstreams) {
Item relatedItem = (Item) HandleManager.resolveToObject(context, bit.getName());
bundles[count] = relatedItem.getBundles(Constants.ARCHIVED_CONTENT_PACKAGE_BUNDLE)[0];
count++;
}
}
return bundles;
}
/**
* @return the default language qualifier for metadata
*/
public static String getDefaultLanguageQualifier()
{
String language = "";
language = ConfigurationManager.getProperty("default.language");
if (language == null || language == "")
{
language = "en";
}
return language;
}
public static boolean itemInCollection(Collection c, Item item){
boolean result = false;
try{
Collection itemCollections[] = item.getCollections();
for (Collection itemCol: itemCollections){
if (c.equals(itemCol)){
result = true;
break;
}
}
} catch (SQLException e){
ExceptionLogger.logException(logger, e);
}
return result;
}
// START GWaller 02/02/09 IssueID #175 Added methods to deal with licence manipulation inside packages
/**
* Renames all bundles matching the name supplied. The new name will be of the form:
* <prefix>bundle name<suffix>_<unique integer> e.g.
* "pre-ARCHIVED_CP_dd-MM-yyyy_HH:mm:ss_0" and "pre-ARCHIVED_CP_dd-MM-yyyy_HH:mm:ss_1" and "pre-ARCHIVED_CP_dd-MM-yyyy_HH:mm:ss_2" etc
* @param item the item containing the bundle
* @param bundleName the name of the bundle(s) to rename
* @param prefix prefix to use for the new name of the bundle(s)
* @param suffix suffix to use for the new name of the bundle(s)
* @return a Bundle pointer to the first renamed bundle
* @throws AuthorizeException
* @throws SQLException
*/
public static Bundle renameBundle(Item item, String bundleName, String prefix, String suffix) throws AuthorizeException, SQLException{
Bundle firstRenamedBundle = null;
Bundle[] bundles = item.getBundles(bundleName);
int count = 0;
for (Bundle b:bundles){
String name;
do{
name = prefix + b.getName() + suffix + "_" + count++;
}
while (BundleUtils.hasBundle(item, name));
b.setName(name);
b.update();
if (firstRenamedBundle == null){
firstRenamedBundle = b;
}
}
return firstRenamedBundle;
}
/**
* Renames all bundles matching the name supplied. The new name will be of the form:
* <new name>_<unique integer> e.g.
* "newName_0"
* @param item the item containing the bundle
* @param bundleName the name of the bundle(s) to rename
* @param newName the new name to use
* @return a Bundle pointer to the first renamed bundle
* @throws AuthorizeException
* @throws SQLException
*/
public static Bundle renameBundle(Item item, String bundleName, String newName) throws AuthorizeException, SQLException{
Bundle firstRenamedBundle = null;
Bundle[] bundles = item.getBundles(bundleName);
int count = 0;
for (Bundle b:bundles){
String name;
do{
name = newName + "_" + count++;
}
while (BundleUtils.hasBundle(item, name));
b.setName(name);
b.update();
if (firstRenamedBundle == null){
firstRenamedBundle = b;
}
}
return firstRenamedBundle;
}
/**
* Simply returns a pointer to the first bitstream in a bundle or null if no bitstreams exist
* @param bundle the bundle to examine
* @return pointer to the first bitstream or null if none found
*/
public static Bitstream getFirstBitStream(Bundle bundle){
Bitstream streams[] = bundle.getBitstreams();
Bitstream result = null;
if (streams.length > 0){
result = streams[0];
}
return result;
}
// END GWaller 02/02/09 IssueID #175 Added methods to deal with licence manipulation inside packages
}