/** * 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.handle; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.dspace.content.DSpaceObject; import org.dspace.content.service.SiteService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.handle.dao.HandleDAO; import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; /** * Interface to the <a href="http://www.handle.net" target=_new>CNRI Handle * System </a>. * * <p> * Currently, this class simply maps handles to local facilities; handles which * are owned by other sites (including other DSpaces) are treated as * non-existent. * </p> * * @author Peter Breton * @version $Revision$ */ public class HandleServiceImpl implements HandleService { /** log4j category */ private static Logger log = Logger.getLogger(HandleServiceImpl.class); /** Prefix registered to no one */ static final String EXAMPLE_PREFIX = "123456789"; @Autowired(required = true) protected HandleDAO handleDAO; @Autowired(required = true) protected ConfigurationService configurationService; @Autowired protected SiteService siteService; /** Public Constructor */ protected HandleServiceImpl() { } @Override public String resolveToURL(Context context, String handle) throws SQLException { Handle dbhandle = findHandleInternal(context, handle); if (dbhandle == null) { return null; } String url = configurationService.getProperty("dspace.url") + "/handle/" + handle; if (log.isDebugEnabled()) { log.debug("Resolved " + handle + " to " + url); } return url; } @Override public String resolveUrlToHandle(Context context, String url) throws SQLException { String dspaceUrl = configurationService.getProperty("dspace.url") + "/handle/"; String handleResolver = configurationService.getProperty("handle.canonical.prefix"); String handle = null; if (url.startsWith(dspaceUrl)) { handle = url.substring(dspaceUrl.length()); } if (url.startsWith(handleResolver)) { handle = url.substring(handleResolver.length()); } if (null == handle) { return null; } // remove trailing slashes while (handle.startsWith("/")) { handle = handle.substring(1); } Handle dbhandle = findHandleInternal(context, handle); return (null == dbhandle) ? null : handle; } @Override public String getCanonicalForm(String handle) { // Let the admin define a new prefix, if not then we'll use the // CNRI default. This allows the admin to use "hdl:" if they want to or // use a locally branded prefix handle.myuni.edu. String handlePrefix = configurationService.getProperty("handle.canonical.prefix"); if (StringUtils.isBlank(handlePrefix)) { handlePrefix = "http://hdl.handle.net/"; } return handlePrefix + handle; } @Override public String createHandle(Context context, DSpaceObject dso) throws SQLException { Handle handle = handleDAO.create(context, new Handle()); String handleId = createId(context); handle.setHandle(handleId); handle.setDSpaceObject(dso); dso.addHandle(handle); handle.setResourceTypeId(dso.getType()); handleDAO.save(context, handle); if (log.isDebugEnabled()) { log.debug("Created new handle for " + Constants.typeText[dso.getType()] + " (ID=" + dso.getID() + ") " + handleId ); } return handleId; } @Override public String createHandle(Context context, DSpaceObject dso, String suppliedHandle) throws SQLException, IllegalStateException { return createHandle(context, dso, suppliedHandle, false); } @Override public String createHandle(Context context, DSpaceObject dso, String suppliedHandle, boolean force) throws SQLException, IllegalStateException { //Check if the supplied handle is already in use -- cannot use the same handle twice Handle handle = findHandleInternal(context, suppliedHandle); if (handle != null && handle.getDSpaceObject() != null) { //Check if this handle is already linked up to this specified DSpace Object if (handle.getDSpaceObject().getID().equals(dso.getID())) { //This handle already links to this DSpace Object -- so, there's nothing else we need to do return suppliedHandle; } else { //handle found in DB table & already in use by another existing resource throw new IllegalStateException("Attempted to create a handle which is already in use: " + suppliedHandle); } } else if (handle!=null && handle.getResourceTypeId() != null) { //If there is a 'resource_type_id' (but 'resource_id' is empty), then the object using // this handle was previously unbound (see unbindHandle() method) -- likely because object was deleted int previousType = handle.getResourceTypeId(); //Since we are restoring an object to a pre-existing handle, double check we are restoring the same *type* of object // (e.g. we will not allow an Item to be restored to a handle previously used by a Collection) if (previousType != dso.getType()) { throw new IllegalStateException("Attempted to reuse a handle previously used by a " + Constants.typeText[previousType] + " for a new " + Constants.typeText[dso.getType()]); } } else if (handle==null) //if handle not found, create it { //handle not found in DB table -- create a new table entry handle = handleDAO.create(context, new Handle()); handle.setHandle(suppliedHandle); } handle.setResourceTypeId(dso.getType()); handle.setDSpaceObject(dso); dso.addHandle(handle); handleDAO.save(context, handle); if (log.isDebugEnabled()) { log.debug("Created new handle for " + Constants.typeText[dso.getType()] + " (ID=" + dso.getID() + ") " + suppliedHandle ); } return suppliedHandle; } @Override public void unbindHandle(Context context, DSpaceObject dso) throws SQLException { List<Handle> handles = getInternalHandles(context, dso); if (CollectionUtils.isNotEmpty(handles)) { for (Handle handle: handles) { //Only set the "resouce_id" column to null when unbinding a handle. // We want to keep around the "resource_type_id" value, so that we // can verify during a restore whether the same *type* of resource // is reusing this handle! handle.setDSpaceObject(null); handleDAO.save(context, handle); if (log.isDebugEnabled()) { log.debug("Unbound Handle " + handle.getHandle() + " from object " + Constants.typeText[dso.getType()] + " id=" + dso.getID()); } } } else { log.warn("Cannot find Handle entry to unbind for object " + Constants.typeText[dso.getType()] + " id=" + dso.getID()); } } @Override public DSpaceObject resolveToObject(Context context, String handle) throws IllegalStateException, SQLException { Handle dbhandle = findHandleInternal(context, handle); // check if handle was allocated previously, but is currently not // associated with a DSpaceObject // (this may occur when 'unbindHandle()' is called for an obj that was removed) if (dbhandle == null || (dbhandle.getDSpaceObject() == null) || (dbhandle.getResourceTypeId() == null)) { //if handle has been unbound, just return null (as this will result in a PageNotFound) return null; } return dbhandle.getDSpaceObject(); } @Override public String findHandle(Context context, DSpaceObject dso) throws SQLException { List<Handle> handles = getInternalHandles(context, dso); if (CollectionUtils.isEmpty(handles)) { return null; } else { //TODO: Move this code away from the HandleService & into the Identifier provider //Attempt to retrieve a handle that does NOT look like {handle.part}/{handle.part}.{version} String result = handles.iterator().next().getHandle(); for (Handle handle: handles) { //Ensure that the handle doesn't look like this 12346/213.{version} //If we find a match that indicates that we have a proper handle if (!handle.getHandle().matches(".*/.*\\.\\d+")) { result = handle.getHandle(); } } return result; } } @Override public List<String> getHandlesForPrefix(Context context, String prefix) throws SQLException { List<Handle> handles = handleDAO.findByPrefix(context, prefix); List<String> handleStrings = new ArrayList<String>(handles.size()); for (Handle handle : handles) { handleStrings.add(handle.getHandle()); } return handleStrings; } @Override public String getPrefix() { String prefix = configurationService.getProperty("handle.prefix"); if (StringUtils.isBlank(prefix)) { prefix = EXAMPLE_PREFIX; // XXX no good way to exit cleanly log.error("handle.prefix is not configured; using " + prefix); } return prefix; } @Override public long countHandlesByPrefix(Context context, String prefix) throws SQLException { return handleDAO.countHandlesByPrefix(context, prefix); } @Override public int updateHandlesWithNewPrefix(Context context, String newPrefix, String oldPrefix) throws SQLException { return handleDAO.updateHandlesWithNewPrefix(context, newPrefix, oldPrefix); } @Override public void modifyHandleDSpaceObject(Context context, String handle, DSpaceObject newOwner) throws SQLException { Handle dbHandle = findHandleInternal(context, handle); if (dbHandle != null) { // Check if we have to remove the handle from the current handle list // or if object is alreday deleted. if (dbHandle.getDSpaceObject() != null) { // Remove the old handle from the current handle list dbHandle.getDSpaceObject().getHandles().remove(dbHandle); } // Transfer the current handle to the new object dbHandle.setDSpaceObject(newOwner); dbHandle.setResourceTypeId(newOwner.getType()); newOwner.getHandles().add(0, dbHandle); handleDAO.save(context, dbHandle); } } //////////////////////////////////////// // Internal methods //////////////////////////////////////// /** * Return the handle for an Object, or null if the Object has no handle. * * @param context * DSpace context * @param dso * DSpaceObject for which we require our handles * @return The handle for object, or null if the object has no handle. * @throws SQLException * If a database error occurs */ protected List<Handle> getInternalHandles(Context context, DSpaceObject dso) throws SQLException { return handleDAO.getHandlesByDSpaceObject(context, dso); } /** * Find the database row corresponding to handle. * * @param context * DSpace context * @param handle * The handle to resolve * @return The database row corresponding to the handle * @throws SQLException * If a database error occurs */ protected Handle findHandleInternal(Context context, String handle) throws SQLException { if (handle == null) { throw new IllegalArgumentException("Handle is null"); } return handleDAO.findByHandle(context, handle); } /** * Create/mint a new handle id. * * @param context DSpace Context * @return A new handle id * @throws SQLException * If a database error occurs */ protected String createId(Context context) throws SQLException { // Get configured prefix String handlePrefix = getPrefix(); // Get next available suffix (as a Long, since DSpace uses an incrementing sequence) Long handleSuffix = handleDAO.getNextHandleSuffix(context); return handlePrefix + (handlePrefix.endsWith("/") ? "" : "/") + handleSuffix.toString(); } @Override public int countTotal(Context context) throws SQLException { return handleDAO.countRows(context); } }