diff --git a/dspace-api/src/main/java/org/dspace/handle/Handle.java b/dspace-api/src/main/java/org/dspace/handle/Handle.java index 0c17894293..274fc48e67 100644 --- a/dspace-api/src/main/java/org/dspace/handle/Handle.java +++ b/dspace-api/src/main/java/org/dspace/handle/Handle.java @@ -26,8 +26,8 @@ public class Handle implements ReloadableEntity { @Id @Column(name="handle_id") - @GeneratedValue(strategy = GenerationType.SEQUENCE ,generator="handle_seq") - @SequenceGenerator(name="handle_seq", sequenceName="handle_seq", allocationSize = 1) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="handle_id_seq") + @SequenceGenerator(name="handle_id_seq", sequenceName="handle_id_seq", allocationSize = 1) private Integer id; @Column(name = "handle", unique = true) @@ -54,6 +54,7 @@ public class Handle implements ReloadableEntity { } + @Override public Integer getID() { return id; } @@ -82,6 +83,7 @@ public class Handle implements ReloadableEntity { return resourceTypeId; } + @Override public boolean equals(final Object o) { if (this == o) return true; @@ -96,6 +98,7 @@ public class Handle implements ReloadableEntity { .isEquals(); } + @Override public int hashCode() { return new HashCodeBuilder(17, 37) .append(id) diff --git a/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java b/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java index 3cab15ebd2..8d2b0cb101 100644 --- a/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java @@ -8,20 +8,22 @@ 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.ConfigurationManager; 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 CNRI Handle * System . @@ -46,6 +48,8 @@ public class HandleServiceImpl implements HandleService @Autowired(required = true) protected HandleDAO handleDAO; + @Autowired(required = true) + protected ConfigurationService configurationService; @Autowired protected SiteService siteService; @@ -66,7 +70,7 @@ public class HandleServiceImpl implements HandleService return null; } - String url = ConfigurationManager.getProperty("dspace.url") + String url = configurationService.getProperty("dspace.url") + "/handle/" + handle; if (log.isDebugEnabled()) @@ -81,9 +85,9 @@ public class HandleServiceImpl implements HandleService public String resolveUrlToHandle(Context context, String url) throws SQLException { - String dspaceUrl = ConfigurationManager.getProperty("dspace.url") + String dspaceUrl = configurationService.getProperty("dspace.url") + "/handle/"; - String handleResolver = ConfigurationManager.getProperty("handle.canonical.prefix"); + String handleResolver = configurationService.getProperty("handle.canonical.prefix"); String handle = null; @@ -119,8 +123,8 @@ public class HandleServiceImpl implements HandleService // 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 = ConfigurationManager.getProperty("handle.canonical.prefix"); - if (handlePrefix == null || handlePrefix.length() == 0) + String handlePrefix = configurationService.getProperty("handle.canonical.prefix"); + if (StringUtils.isBlank(handlePrefix)) { handlePrefix = "http://hdl.handle.net/"; } @@ -133,7 +137,7 @@ public class HandleServiceImpl implements HandleService throws SQLException { Handle handle = handleDAO.create(context, new Handle()); - String handleId = createId(handle.getID()); + String handleId = createId(context); handle.setHandle(handleId); handle.setDSpaceObject(dso); @@ -302,8 +306,8 @@ public class HandleServiceImpl implements HandleService @Override public String getPrefix() { - String prefix = ConfigurationManager.getProperty("handle.prefix"); - if (null == prefix) + 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); @@ -386,18 +390,22 @@ public class HandleServiceImpl implements HandleService } /** - * Create a new handle id. The implementation uses the PK of the RDBMS - * Handle table. + * Create/mint a new handle id. * + * @param context DSpace Context * @return A new handle id * @exception SQLException * If a database error occurs */ - protected String createId(int id) throws SQLException + protected String createId(Context context) throws SQLException { + // Get configured prefix String handlePrefix = getPrefix(); - return handlePrefix + (handlePrefix.endsWith("/") ? "" : "/") + id; + // 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 diff --git a/dspace-api/src/main/java/org/dspace/handle/dao/HandleDAO.java b/dspace-api/src/main/java/org/dspace/handle/dao/HandleDAO.java index fcc465c873..ebfd01b133 100644 --- a/dspace-api/src/main/java/org/dspace/handle/dao/HandleDAO.java +++ b/dspace-api/src/main/java/org/dspace/handle/dao/HandleDAO.java @@ -24,6 +24,8 @@ import java.util.List; */ public interface HandleDAO extends GenericDAO { + public Long getNextHandleSuffix(Context context) throws SQLException; + public List getHandlesByDSpaceObject(Context context, DSpaceObject dso) throws SQLException; public Handle findByHandle(Context context, String handle)throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/handle/dao/impl/HandleDAOImpl.java b/dspace-api/src/main/java/org/dspace/handle/dao/impl/HandleDAOImpl.java index a378f5a384..7c43cf9209 100644 --- a/dspace-api/src/main/java/org/dspace/handle/dao/impl/HandleDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/handle/dao/impl/HandleDAOImpl.java @@ -7,6 +7,9 @@ */ package org.dspace.handle.dao.impl; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.core.AbstractHibernateDAO; @@ -15,6 +18,10 @@ import org.dspace.handle.dao.HandleDAO; import org.hibernate.Criteria; import org.hibernate.Query; import org.hibernate.criterion.Restrictions; +import org.hibernate.dialect.Dialect; +import org.hibernate.jdbc.ReturningWork; +import org.hibernate.service.jdbc.dialect.internal.StandardDialectResolver; +import org.hibernate.service.jdbc.dialect.spi.DialectResolver; import java.sql.SQLException; import java.util.Collections; @@ -29,6 +36,9 @@ import java.util.List; */ public class HandleDAOImpl extends AbstractHibernateDAO implements HandleDAO { + // The name of the sequence used to determine next available handle + private static final String HANDLE_SEQUENCE = "handle_seq"; + protected HandleDAOImpl() { super(); @@ -94,4 +104,45 @@ public class HandleDAOImpl extends AbstractHibernateDAO implements Handl public int countRows(Context context) throws SQLException { return count(createQuery(context, "SELECT count(*) FROM Handle")); } + + /** + * Return next available value of Handle suffix (based on DB sequence). + * @param context Current DSpace Context + * @return next available Handle suffix (as a Long) + * @throws SQLException if database error or sequence doesn't exist + */ + @Override + public Long getNextHandleSuffix(Context context) throws SQLException + { + // Create a new Hibernate ReturningWork, which will return the + // result of the next value in the Handle Sequence. + ReturningWork nextValReturningWork = new ReturningWork() { + @Override + public Long execute(Connection connection) throws SQLException { + Long nextVal = 0L; + + // Determine what dialect we are using for this DB + DialectResolver dialectResolver = new StandardDialectResolver(); + Dialect dialect = dialectResolver.resolveDialect(connection.getMetaData()); + + // Find the next value in our sequence (based on DB dialect) + try (PreparedStatement preparedStatement = connection.prepareStatement(dialect.getSequenceNextValString(HANDLE_SEQUENCE))) + { + // Execute query and return results + try(ResultSet resultSet = preparedStatement.executeQuery()) + { + if(resultSet.next()) + { + // Return result of query (from first column) + nextVal = resultSet.getLong(1); + } + } + } + return nextVal; + } + }; + + // Run our work, returning the next value in the sequence (see 'nextValReturningWork' above) + return getHibernateSession(context).doReturningWork(nextValReturningWork); + } } diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql new file mode 100644 index 0000000000..624b9394ed --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql @@ -0,0 +1,15 @@ +-- +-- 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/ +-- + +---------------------------------------------------------------------------------- +-- DS-3277 : 'handle_id' column needs its own separate sequence, so that Handles +-- can be minted from 'handle_seq' +---------------------------------------------------------------------------------- +-- Create a new sequence for 'handle_id' column. +-- The role of this sequence is to simply provide a unique internal ID to the database. +CREATE SEQUENCE handle_id_seq; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql new file mode 100644 index 0000000000..96f125f78b --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql @@ -0,0 +1,44 @@ +-- +-- 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/ +-- + +---------------------------------------------------------------------------------- +-- DS-3277 : 'handle_id' column needs its own separate sequence, so that Handles +-- can be minted from 'handle_seq' +---------------------------------------------------------------------------------- +-- Create a new sequence for 'handle_id' column. +-- The role of this sequence is to simply provide a unique internal ID to the database. +CREATE SEQUENCE handle_id_seq; +-- Initialize new 'handle_id_seq' to the maximum value of 'handle_id' +DECLARE + curr NUMBER := 0; +BEGIN + SELECT max(handle_id) INTO curr FROM handle; + + curr := curr + 1; + + EXECUTE IMMEDIATE 'DROP SEQUENCE handle_id_seq'; + + EXECUTE IMMEDIATE 'CREATE SEQUENCE handle_id_seq START WITH ' || NVL(curr,1); +END; +/ + +-- Ensure the 'handle_seq' is updated to the maximum *suffix* in 'handle' column, +-- as this sequence is used to mint new Handles. +-- Code borrowed from update-sequences.sql and updateseq.sql +DECLARE + curr NUMBER := 0; +BEGIN + SELECT max(to_number(regexp_replace(handle, '.*/', ''), '999999999999')) INTO curr FROM handle WHERE REGEXP_LIKE(handle, '^.*/[0123456789]*$'); + + curr := curr + 1; + + EXECUTE IMMEDIATE 'DROP SEQUENCE handle_seq'; + + EXECUTE IMMEDIATE 'CREATE SEQUENCE handle_seq START WITH ' || NVL(curr,1); +END; +/ \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql new file mode 100644 index 0000000000..4331e432ef --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql @@ -0,0 +1,30 @@ +-- +-- 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/ +-- + +---------------------------------------------------------------------------------- +-- DS-3277 : 'handle_id' column needs its own separate sequence, so that Handles +-- can be minted from 'handle_seq' +---------------------------------------------------------------------------------- +-- Create a new sequence for 'handle_id' column. +-- The role of this sequence is to simply provide a unique internal ID to the database. +CREATE SEQUENCE handle_id_seq; +-- Initialize new 'handle_id_seq' to the maximum value of 'handle_id' +SELECT setval('handle_id_seq', max(handle_id)) FROM handle; + +-- Ensure the 'handle_seq' is updated to the maximum *suffix* in 'handle' column, +-- as this sequence is used to mint new Handles. +-- Code borrowed from update-sequences.sql +SELECT setval('handle_seq', + CAST ( + max( + to_number(regexp_replace(handle, '.*/', ''), '999999999999') + ) + AS BIGINT) + ) + FROM handle + WHERE handle SIMILAR TO '%/[0123456789]*'; \ No newline at end of file diff --git a/dspace/etc/oracle/update-sequences.sql b/dspace/etc/oracle/update-sequences.sql index c3382a5816..d40f7665a7 100644 --- a/dspace/etc/oracle/update-sequences.sql +++ b/dspace/etc/oracle/update-sequences.sql @@ -63,6 +63,7 @@ @updateseq.sql harvested_item_seq harvested_item id "" @updateseq.sql webapp_seq webapp webapp_id "" @updateseq.sql requestitem_seq requestitem requestitem_id "" +@updateseq.sql handle_id_seq handle handle_id "" -- Handle Sequence is a special case. Since Handles minted by DSpace use the 'handle_seq', -- we need to ensure the next assigned handle will *always* be unique. So, 'handle_seq' diff --git a/dspace/etc/postgres/update-sequences.sql b/dspace/etc/postgres/update-sequences.sql index fd06aa3f49..9928dbf319 100644 --- a/dspace/etc/postgres/update-sequences.sql +++ b/dspace/etc/postgres/update-sequences.sql @@ -60,6 +60,7 @@ SELECT setval('harvested_collection_seq', max(id)) FROM harvested_collection; SELECT setval('harvested_item_seq', max(id)) FROM harvested_item; SELECT setval('webapp_seq', max(webapp_id)) FROM webapp; SELECT setval('requestitem_seq', max(requestitem_id)) FROM requestitem; +SELECT setval('handle_id_seq', max(handle_id)) FROM handle; -- Handle Sequence is a special case. Since Handles minted by DSpace use the 'handle_seq', -- we need to ensure the next assigned handle will *always* be unique. So, 'handle_seq'