/** * 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.content; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.dspace.authorize.AuthorizeConfiguration; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.dao.BundleDAO; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.BundleService; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogManager; import org.dspace.event.Event; import org.springframework.beans.factory.annotation.Autowired; import java.io.IOException; import java.sql.SQLException; import java.util.*; /** * Service implementation for the Bundle object. * This class is responsible for all business logic calls for the Bundle object and is autowired by spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com */ public class BundleServiceImpl extends DSpaceObjectServiceImpl<Bundle> implements BundleService { /** log4j logger */ private static Logger log = Logger.getLogger(Bundle.class); @Autowired(required = true) protected BundleDAO bundleDAO; @Autowired(required = true) protected BitstreamService bitstreamService; @Autowired(required = true) protected ItemService itemService; @Autowired(required = true) protected AuthorizeService authorizeService; @Autowired(required = true) protected ResourcePolicyService resourcePolicyService; protected BundleServiceImpl() { super(); } @Override public Bundle find(Context context, UUID id) throws SQLException { // First check the cache Bundle bundle = bundleDAO.findByID(context, Bundle.class, id); if (bundle == null) { if (log.isDebugEnabled()) { log.debug(LogManager.getHeader(context, "find_bundle", "not_found,bundle_id=" + id)); } return null; } else { if (log.isDebugEnabled()) { log.debug(LogManager.getHeader(context, "find_bundle", "bundle_id=" + id)); } return bundle; } } @Override public Bundle create(Context context, Item item, String name) throws SQLException, AuthorizeException { if (StringUtils.isBlank(name)) { throw new SQLException("Bundle must be created with non-null name"); } authorizeService.authorizeAction(context, item, Constants.ADD); // Create a table row Bundle bundle = bundleDAO.create(context, new Bundle()); bundle.setName(context, name); itemService.addBundle(context, item, bundle); if(!bundle.getItems().contains(item)) { bundle.addItem(item); } log.info(LogManager.getHeader(context, "create_bundle", "bundle_id=" + bundle.getID())); // if we ever use the identifier service for bundles, we should // create the bundle before we create the Event and should add all // identifiers to it. context.addEvent(new Event(Event.CREATE, Constants.BUNDLE, bundle.getID(), null)); return bundle; } @Override public Bitstream getBitstreamByName(Bundle bundle, String name) { Bitstream target = null; for (Bitstream bitstream : bundle.getBitstreams()) { if (name.equals(bitstream.getName())) { target = bitstream; break; } } return target; } @Override public void addBitstream(Context context, Bundle bundle, Bitstream bitstream) throws SQLException, AuthorizeException { // Check authorisation authorizeService.authorizeAction(context, bundle, Constants.ADD); log.info(LogManager.getHeader(context, "add_bitstream", "bundle_id=" + bundle.getID() + ",bitstream_id=" + bitstream.getID())); // First check that the bitstream isn't already in the list List<Bitstream> bitstreams = bundle.getBitstreams(); int topOrder = 0; // First check that the bitstream isn't already in the list for (Bitstream bs : bitstreams) { if (bitstream.getID().equals(bs.getID())) { // Bitstream is already there; no change return; } } bundle.addBitstream(bitstream); bitstream.getBundles().add(bundle); context.addEvent(new Event(Event.ADD, Constants.BUNDLE, bundle.getID(), Constants.BITSTREAM, bitstream.getID(), String.valueOf(bitstream.getSequenceID()), getIdentifiers(context, bundle))); // copy authorization policies from bundle to bitstream // FIXME: multiple inclusion is affected by this... authorizeService.inheritPolicies(context, bundle, bitstream); bitstreamService.update(context, bitstream); } @Override public void removeBitstream(Context context, Bundle bundle, Bitstream bitstream) throws AuthorizeException, SQLException, IOException { // Check authorisation authorizeService.authorizeAction(context, bundle, Constants.REMOVE); log.info(LogManager.getHeader(context, "remove_bitstream", "bundle_id=" + bundle.getID() + ",bitstream_id=" + bitstream.getID())); context.addEvent(new Event(Event.REMOVE, Constants.BUNDLE, bundle.getID(), Constants.BITSTREAM, bitstream.getID(), String.valueOf(bitstream.getSequenceID()), getIdentifiers(context, bundle))); //Ensure that the last modified from the item is triggered ! Item owningItem = (Item) getParentObject(context, bundle); if(owningItem != null) { itemService.updateLastModified(context, owningItem); itemService.update(context, owningItem); } // In the event that the bitstream to remove is actually // the primary bitstream, be sure to unset the primary // bitstream. if (bitstream.equals(bundle.getPrimaryBitstream())) { bundle.unsetPrimaryBitstreamID(); } // Check if we our bitstream is part of a single bundle: // If so delete it, if not then remove the link between bundle & bitstream if(bitstream.getBundles().size() == 1) { // We don't need to remove the link between bundle & bitstream, this will be handled in the delete() method. bitstreamService.delete(context, bitstream); }else{ bundle.getBitstreams().remove(bitstream); bitstream.getBundles().remove(bundle); } } @Override public void inheritCollectionDefaultPolicies(Context context, Bundle bundle, Collection collection) throws SQLException, AuthorizeException { List<ResourcePolicy> policies = authorizeService.getPoliciesActionFilter(context, collection, Constants.DEFAULT_BITSTREAM_READ); // change the action to just READ // just don't call update on the resourcepolicies!!! Iterator<ResourcePolicy> i = policies.iterator(); if (!i.hasNext()) { throw new java.sql.SQLException("Collection " + collection.getID() + " has no default bitstream READ policies"); } List<ResourcePolicy> newPolicies = new ArrayList<ResourcePolicy>(); while (i.hasNext()) { ResourcePolicy rp = resourcePolicyService.clone(context, i.next()); rp.setAction(Constants.READ); newPolicies.add(rp); } replaceAllBitstreamPolicies(context, bundle, newPolicies); } @Override public void replaceAllBitstreamPolicies(Context context, Bundle bundle, List<ResourcePolicy> newpolicies) throws SQLException, AuthorizeException { List<Bitstream> bitstreams = bundle.getBitstreams(); if (CollectionUtils.isNotEmpty(bitstreams)) { for (Bitstream bs : bitstreams) { // change bitstream policies authorizeService.removeAllPolicies(context, bs); authorizeService.addPolicies(context, newpolicies, bs); } } // change bundle policies authorizeService.removeAllPolicies(context, bundle); authorizeService.addPolicies(context, newpolicies, bundle); } @Override public List<ResourcePolicy> getBitstreamPolicies(Context context, Bundle bundle) throws SQLException { List<ResourcePolicy> list = new ArrayList<ResourcePolicy>(); List<Bitstream> bitstreams = bundle.getBitstreams(); if (CollectionUtils.isNotEmpty(bitstreams)) { for (Bitstream bs : bitstreams) { list.addAll(authorizeService.getPolicies(context, bs)); } } return list; } @Override public List<ResourcePolicy> getBundlePolicies(Context context, Bundle bundle) throws SQLException { return authorizeService.getPolicies(context, bundle); } @Override public void setOrder(Context context, Bundle bundle, UUID[] bitstreamIds) throws AuthorizeException, SQLException { authorizeService.authorizeAction(context, bundle, Constants.WRITE); bundle.getBitstreams().clear(); for (int i = 0; i < bitstreamIds.length; i++) { UUID bitstreamId = bitstreamIds[i]; Bitstream bitstream = bitstreamService.find(context, bitstreamId); if(bitstream == null){ //This should never occur but just in case log.warn(LogManager.getHeader(context, "Invalid bitstream id while changing bitstream order", "Bundle: " + bundle.getID() + ", bitstream id: " + bitstreamId)); continue; } bitstream.getBundles().remove(bundle); bundle.getBitstreams().add(bitstream); bitstream.getBundles().add(bundle); bitstreamService.update(context, bitstream); } //The order of the bitstreams has changed, ensure that we update the last modified of our item Item owningItem = (Item) getParentObject(context, bundle); if(owningItem != null) { itemService.updateLastModified(context, owningItem); itemService.update(context, owningItem); } } @Override public DSpaceObject getAdminObject(Context context, Bundle bundle, int action) throws SQLException { DSpaceObject adminObject = null; Item item = (Item) getParentObject(context, bundle); Collection collection = null; Community community = null; if (item != null) { collection = item.getOwningCollection(); if (collection != null) { community = collection.getCommunities().get(0); } } switch (action) { case Constants.REMOVE: if (AuthorizeConfiguration.canItemAdminPerformBitstreamDeletion()) { adminObject = item; } else if (AuthorizeConfiguration.canCollectionAdminPerformBitstreamDeletion()) { adminObject = collection; } else if (AuthorizeConfiguration .canCommunityAdminPerformBitstreamDeletion()) { adminObject = community; } break; case Constants.ADD: if (AuthorizeConfiguration.canItemAdminPerformBitstreamCreation()) { adminObject = item; } else if (AuthorizeConfiguration .canCollectionAdminPerformBitstreamCreation()) { adminObject = collection; } else if (AuthorizeConfiguration .canCommunityAdminPerformBitstreamCreation()) { adminObject = community; } break; default: adminObject = bundle; break; } return adminObject; } @Override public DSpaceObject getParentObject(Context context, Bundle bundle) throws SQLException { List<Item> items = bundle.getItems(); if(CollectionUtils.isNotEmpty(items)) { return items.iterator().next(); }else{ return null; } } @Override public void updateLastModified(Context context, Bundle dso) { //No implemented for bundle } @Override public void update(Context context, Bundle bundle) throws SQLException, AuthorizeException { // Check authorisation //AuthorizeManager.authorizeAction(ourContext, this, Constants.WRITE); log.info(LogManager.getHeader(context, "update_bundle", "bundle_id=" + bundle.getID())); super.update(context, bundle); bundleDAO.save(context, bundle); if (bundle.isModified() || bundle.isMetadataModified()) { if(bundle.isMetadataModified()){ context.addEvent(new Event(Event.MODIFY_METADATA, bundle.getType(), bundle.getID(), bundle.getDetails(), getIdentifiers(context, bundle))); } context.addEvent(new Event(Event.MODIFY, Constants.BUNDLE, bundle.getID(), null, getIdentifiers(context, bundle))); bundle.clearModified(); bundle.clearDetails(); } } @Override public void delete(Context context, Bundle bundle) throws SQLException, AuthorizeException, IOException { log.info(LogManager.getHeader(context, "delete_bundle", "bundle_id=" + bundle.getID())); authorizeService.authorizeAction(context, bundle, Constants.DELETE); context.addEvent(new Event(Event.DELETE, Constants.BUNDLE, bundle.getID(), bundle.getName(), getIdentifiers(context, bundle))); // Remove bitstreams Iterator<Bitstream> bitstreams = bundle.getBitstreams().iterator(); while (bitstreams.hasNext()) { Bitstream bitstream = bitstreams.next(); bitstreams.remove(); removeBitstream(context, bundle, bitstream); } Iterator<Item> items = bundle.getItems().iterator(); while (items.hasNext()) { Item item = items.next(); item.removeBundle(bundle); } // Remove ourself bundleDAO.delete(context, bundle); } @Override public int getSupportsTypeConstant() { return Constants.BUNDLE; } @Override public Bundle findByIdOrLegacyId(Context context, String id) throws SQLException { if(StringUtils.isNumeric(id)) { return findByLegacyId(context, Integer.parseInt(id)); } else { return find(context, UUID.fromString(id)); } } @Override public Bundle findByLegacyId(Context context, int id) throws SQLException { return bundleDAO.findByLegacyId(context, id, Bundle.class); } @Override public int countTotal(Context context) throws SQLException { return bundleDAO.countRows(context); } }