mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-17 23:13:10 +00:00
Merge pull request #8357 from 4Science/main-duracom-70
Add compatibility for DSpace 7 with the Remote Handle Resolver
This commit is contained in:
@@ -405,7 +405,7 @@ public class HandleServiceImpl implements HandleService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check additional prefixes supported in the config file
|
// Check additional prefixes supported in the config file
|
||||||
String[] additionalPrefixes = configurationService.getArrayProperty("handle.additional.prefixes");
|
String[] additionalPrefixes = getAdditionalPrefixes();
|
||||||
for (String additionalPrefix : additionalPrefixes) {
|
for (String additionalPrefix : additionalPrefixes) {
|
||||||
if (identifier.startsWith(additionalPrefix + "/")) {
|
if (identifier.startsWith(additionalPrefix + "/")) {
|
||||||
// prefix is the equivalent of 123456789 in 123456789/???; don't strip
|
// prefix is the equivalent of 123456789 in 123456789/???; don't strip
|
||||||
@@ -415,4 +415,9 @@ public class HandleServiceImpl implements HandleService {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getAdditionalPrefixes() {
|
||||||
|
return configurationService.getArrayProperty("handle.additional.prefixes");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* 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.hdlresolver;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
|
import org.dspace.core.Constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the URL of the request to an handle identifier
|
||||||
|
*
|
||||||
|
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.it)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class HdlResolverDTO {
|
||||||
|
|
||||||
|
private final String[] splittedString;
|
||||||
|
private final String handle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a given URL
|
||||||
|
* @param url URL
|
||||||
|
* @return decoded URL
|
||||||
|
*/
|
||||||
|
private static String decode(String url) {
|
||||||
|
try {
|
||||||
|
return URLDecoder.decode(url, Constants.DEFAULT_ENCODING);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default Constructor
|
||||||
|
*
|
||||||
|
* @param requestURL is the complete Request URL
|
||||||
|
* @param resolverSubPath is the rest service Sub-path
|
||||||
|
*/
|
||||||
|
public HdlResolverDTO(final String requestURL, final String resolverSubPath) {
|
||||||
|
Validate.notBlank(requestURL, "RequestURI not specified");
|
||||||
|
Validate.notBlank(resolverSubPath, "fullPath not specified");
|
||||||
|
this.splittedString = requestURL.split(resolverSubPath);
|
||||||
|
if (Objects.nonNull(splittedString) && splittedString.length > 1) {
|
||||||
|
// Decodes the URL-encoded characters of the String
|
||||||
|
this.handle = decode(splittedString[1]);
|
||||||
|
} else {
|
||||||
|
this.handle = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the splitted String of the resource-path
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public final String[] getSplittedString() {
|
||||||
|
return this.splittedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the handle identifier
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public final String getHandle() {
|
||||||
|
return this.handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the handle identifier is valid.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isValid() {
|
||||||
|
return Objects.nonNull(this.handle) &&
|
||||||
|
!"null".equalsIgnoreCase(this.handle) &&
|
||||||
|
!this.handle.trim().isEmpty();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* 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.hdlresolver;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.dspace.core.Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service used to for utilities involving {@code HdlResolverDTO} and its
|
||||||
|
* resolution to handle URI and vice-versa.
|
||||||
|
*
|
||||||
|
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.it)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface HdlResolverService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that creates an <code>HdlResovlerDTO</code> using the requestURI (full
|
||||||
|
* requested handle URI) and the path (REST handler URI)
|
||||||
|
*
|
||||||
|
* @param requestURI
|
||||||
|
* @param path
|
||||||
|
* @return <code>HdlResolverDTO</code>
|
||||||
|
*/
|
||||||
|
HdlResolverDTO resolveBy(String requestURI, String path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the hdlResovler into URL fetching it from repository using the DSpace context
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* @param hdlResolver
|
||||||
|
* @return URL found or null
|
||||||
|
*/
|
||||||
|
String resolveToURL(Context context, HdlResolverDTO hdlResolver);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all available prefixes for this installation
|
||||||
|
*
|
||||||
|
* @return `List<String>` of Handle prefixes
|
||||||
|
*/
|
||||||
|
List<String> listPrefixes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all available handles with `prefix`
|
||||||
|
*
|
||||||
|
* @param context DSpace context
|
||||||
|
* @param prefix prefix to search
|
||||||
|
* @return `List<String>` of handles
|
||||||
|
*/
|
||||||
|
List<String> listHandles(Context context, String prefix);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies status of handle controller
|
||||||
|
*
|
||||||
|
* @return `true` if enabled, `false` otherwise
|
||||||
|
*/
|
||||||
|
boolean isListhandlesEnabled();
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* 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.hdlresolver;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.dspace.core.Context;
|
||||||
|
import org.dspace.handle.service.HandleService;
|
||||||
|
import org.dspace.services.ConfigurationService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Handle Resolver that uses an <code>HandleService</code> to retrieve the right
|
||||||
|
* URL of a target Handle.
|
||||||
|
*
|
||||||
|
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.it)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class HdlResolverServiceImpl implements HdlResolverService {
|
||||||
|
|
||||||
|
public static final String LISTHANDLES_HIDE_PROP = "handle.hide.listhandles";
|
||||||
|
|
||||||
|
private static final Logger log = LogManager.getLogger();
|
||||||
|
|
||||||
|
@Autowired(required = true)
|
||||||
|
private HandleService handleService;
|
||||||
|
|
||||||
|
@Autowired(required = true)
|
||||||
|
private ConfigurationService configurationService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HdlResolverDTO resolveBy(String requestURI, String path) {
|
||||||
|
return new HdlResolverDTO(requestURI, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String resolveToURL(Context context, HdlResolverDTO hdlResolver) {
|
||||||
|
try {
|
||||||
|
return this.handleService.resolveToURL(context, hdlResolver.getHandle());
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("Error while resolving Handle: " + hdlResolver.getHandle(), e);
|
||||||
|
throw new RuntimeException("Error while resolving Handle: " + hdlResolver.getHandle(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> listPrefixes() {
|
||||||
|
return Stream.concat(
|
||||||
|
Stream.of(this.handleService.getAdditionalPrefixes()),
|
||||||
|
Stream.of(this.handleService.getPrefix())
|
||||||
|
)
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> listHandles(Context context, String prefix) {
|
||||||
|
List<String> handlesForPrefix = List.of();
|
||||||
|
try {
|
||||||
|
handlesForPrefix = this.handleService.getHandlesForPrefix(context, prefix);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("Error while listing handles for prefix: " + prefix, e);
|
||||||
|
throw new RuntimeException("Error while listing handles for prefix: " + prefix, e);
|
||||||
|
}
|
||||||
|
return handlesForPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isListhandlesEnabled() {
|
||||||
|
return !this.configurationService.getBooleanProperty(LISTHANDLES_HIDE_PROP);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -101,16 +101,18 @@ public interface HandleService {
|
|||||||
throws SQLException, IllegalStateException;
|
throws SQLException, IllegalStateException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a handle entry, but with a handle supplied by the caller (new
|
* Creates a handle entry, but with a handle supplied by the caller (new Handle
|
||||||
* Handle not generated)
|
* not generated)
|
||||||
*
|
*
|
||||||
* @param context DSpace context
|
* @param context DSpace context
|
||||||
* @param dso DSpaceObject
|
* @param dso DSpaceObject
|
||||||
* @param suppliedHandle existing handle value
|
* @param suppliedHandle existing handle value
|
||||||
* @param force FIXME: currently unused
|
* @param force FIXME: currently unused
|
||||||
* @return the Handle
|
* @return the Handle
|
||||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
* @throws SQLException An exception that provides information on a
|
||||||
* @throws IllegalStateException if specified handle is already in use by another object
|
* database access error or other errors.
|
||||||
|
* @throws IllegalStateException if specified handle is already in use by
|
||||||
|
* another object
|
||||||
*/
|
*/
|
||||||
public String createHandle(Context context, DSpaceObject dso, String suppliedHandle, boolean force)
|
public String createHandle(Context context, DSpaceObject dso, String suppliedHandle, boolean force)
|
||||||
throws SQLException, IllegalStateException;
|
throws SQLException, IllegalStateException;
|
||||||
@@ -190,4 +192,12 @@ public interface HandleService {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
String parseHandle(String identifier);
|
String parseHandle(String identifier);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the additional prefixes used for handles,
|
||||||
|
* mapped in configuration file.
|
||||||
|
*
|
||||||
|
* @return `String[]` array of prefixes
|
||||||
|
*/
|
||||||
|
String[] getAdditionalPrefixes();
|
||||||
}
|
}
|
||||||
|
@@ -70,6 +70,17 @@ mail.server.disabled = true
|
|||||||
# (Defaults to a dummy/fake prefix of 123456789)
|
# (Defaults to a dummy/fake prefix of 123456789)
|
||||||
handle.prefix = 123456789
|
handle.prefix = 123456789
|
||||||
|
|
||||||
|
# Whether to enable the DSpace handle resolver endpoints necessary for
|
||||||
|
# https://github.com/DSpace/Remote-Handle-Resolver
|
||||||
|
# Defaults to "false" which means these handle resolver endpoints are not available.
|
||||||
|
handle.remote-resolver.enabled = true
|
||||||
|
|
||||||
|
# Whether to enable the DSpace listhandles resolver that lists all available
|
||||||
|
# handles for this DSpace installation.
|
||||||
|
# Defaults to "false" which means is possible to obtain the list of handles
|
||||||
|
# of this DSpace installation, whenever the `handle.remote-resolver.enabled = true`.
|
||||||
|
handle.hide.listhandles = false
|
||||||
|
|
||||||
#####################
|
#####################
|
||||||
# LOGLEVEL SETTINGS #
|
# LOGLEVEL SETTINGS #
|
||||||
#####################
|
#####################
|
||||||
|
@@ -39,10 +39,10 @@ import org.dspace.profile.OrcidSynchronizationMode;
|
|||||||
public class ItemBuilder extends AbstractDSpaceObjectBuilder<Item> {
|
public class ItemBuilder extends AbstractDSpaceObjectBuilder<Item> {
|
||||||
|
|
||||||
private boolean withdrawn = false;
|
private boolean withdrawn = false;
|
||||||
|
private String handle = null;
|
||||||
private WorkspaceItem workspaceItem;
|
private WorkspaceItem workspaceItem;
|
||||||
private Item item;
|
private Item item;
|
||||||
private Group readerGroup = null;
|
private Group readerGroup = null;
|
||||||
private String handle = null;
|
|
||||||
|
|
||||||
protected ItemBuilder(Context context) {
|
protected ItemBuilder(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@@ -371,7 +371,7 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder<Item> {
|
|||||||
@Override
|
@Override
|
||||||
public Item build() {
|
public Item build() {
|
||||||
try {
|
try {
|
||||||
installItemService.installItem(context, workspaceItem, handle);
|
installItemService.installItem(context, workspaceItem, this.handle);
|
||||||
itemService.update(context, item);
|
itemService.update(context, item);
|
||||||
|
|
||||||
//Check if we need to make this item private. This has to be done after item install.
|
//Check if we need to make this item private. This has to be done after item install.
|
||||||
|
@@ -0,0 +1,198 @@
|
|||||||
|
/**
|
||||||
|
* 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.app.rest.hdlresolver;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.dspace.app.rest.utils.ContextUtil;
|
||||||
|
import org.dspace.handle.hdlresolver.HdlResolverDTO;
|
||||||
|
import org.dspace.handle.hdlresolver.HdlResolverService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This controller is public and is useful for handle resolving,
|
||||||
|
* whether a target handle identifier will be resolved into the
|
||||||
|
* corresponding URL (if found), otherwise will respond a null string.
|
||||||
|
*
|
||||||
|
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.it)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@ConditionalOnProperty("handle.remote-resolver.enabled")
|
||||||
|
@RequestMapping(path = "/{hdlService:hdlresolver|resolve|listhandles|listprefixes}/")
|
||||||
|
public class HdlResolverRestController {
|
||||||
|
static final String HDL_RESOLVER = "/hdlresolver/";
|
||||||
|
static final String RESOLVE = "/resolve/";
|
||||||
|
static final String LISTHANDLES = "/listhandles/";
|
||||||
|
static final String LISTPREFIXES = "/listprefixes/";
|
||||||
|
|
||||||
|
private static final Logger log = LogManager.getLogger();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private HdlResolverService hdlResolverService;
|
||||||
|
|
||||||
|
@GetMapping(
|
||||||
|
value = "**",
|
||||||
|
produces = "application/json;charset=UTF-8"
|
||||||
|
)
|
||||||
|
public ResponseEntity<String> handleController(HttpServletRequest request, @PathVariable String hdlService) {
|
||||||
|
if (HDL_RESOLVER.contains(hdlService) || RESOLVE.contains(hdlService)) {
|
||||||
|
return resolveHandle(request, hdlService);
|
||||||
|
} else if (LISTHANDLES.contains(hdlService)) {
|
||||||
|
return this.listHandles(
|
||||||
|
request,
|
||||||
|
Optional.ofNullable(request.getRequestURI().split(LISTHANDLES))
|
||||||
|
.filter(split -> split.length > 1)
|
||||||
|
.map(splitted -> splitted[1])
|
||||||
|
.orElse(null)
|
||||||
|
);
|
||||||
|
} else if (LISTPREFIXES.contains(hdlService)) {
|
||||||
|
return this.listPrefixes(request);
|
||||||
|
} else {
|
||||||
|
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST GET Method used to find and retrieve the URL of a target Handle. It
|
||||||
|
* should return only one item, if found, else a null body value.
|
||||||
|
* </br>
|
||||||
|
* Generate an <code>HttpStatus.BAD_REQUEST</code> 400 Error if the handle used
|
||||||
|
* in path isn't valid.
|
||||||
|
* </br>
|
||||||
|
* The response type will be (<code>application/json;charset=UTF-8</code>)
|
||||||
|
* a string representing an array-like list of handles:
|
||||||
|
* </br>
|
||||||
|
* Example:
|
||||||
|
* <ul>
|
||||||
|
* <li>Request: GET - http://{dspace.url}/hdlresolver/handleIdExample/1</li>
|
||||||
|
* <li>Response: 200 - ["http://localhost/handle/hanldeIdExample1"]
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param request {@code HttpServletRequest}
|
||||||
|
* @return One element List or <code>null</code> with status 200 - <code>HttpStatus.OK</code>
|
||||||
|
* or 400 - <code>HttpStatus.BAD_REQUEST</code> error
|
||||||
|
*/
|
||||||
|
public ResponseEntity<String> resolveHandle(HttpServletRequest request, String hdlService) {
|
||||||
|
HdlResolverDTO handleResolver =
|
||||||
|
this.hdlResolverService.resolveBy(
|
||||||
|
request.getRequestURI(),
|
||||||
|
MessageFormat.format("{0}/{1}/", request.getContextPath(), hdlService)
|
||||||
|
);
|
||||||
|
if (!handleResolver.isValid()) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
|
} else {
|
||||||
|
return new ResponseEntity<>(this.resolveToURL(request, handleResolver), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST GET Method used to list all available prefixes for handles. It
|
||||||
|
* should return a list of prefixes, at least the default one.
|
||||||
|
* </br>
|
||||||
|
* The response type will be (<code>application/json;charset=UTF-8</code>)
|
||||||
|
* a string representing an array-like list of handles:
|
||||||
|
* </br>
|
||||||
|
* Example:
|
||||||
|
* <ul>
|
||||||
|
* <li>Request: GET - http://{dspace.url}/listprefixes</li>
|
||||||
|
* <li>Response: 200 - ["123456789","prefix1","prefix2"]
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param request {@code HttpServletRequest}
|
||||||
|
* @return List of valid prefixes with status 200 <code>HttpStatus.OK</code>
|
||||||
|
*/
|
||||||
|
public ResponseEntity<String> listPrefixes(HttpServletRequest request) {
|
||||||
|
return new ResponseEntity<>(this.mapAsJson(this.hdlResolverService.listPrefixes()), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST GET Method used to list all available handles starting with target prefix.
|
||||||
|
* It should return a list of handles.
|
||||||
|
* </br>
|
||||||
|
* If the controller is disabled `handle.hide.listhandles = true`,
|
||||||
|
* then 400 - <code>HttpStatus.NOT_FOUND</code> is returned.
|
||||||
|
* </br>
|
||||||
|
* If the requested prefix is blank,
|
||||||
|
* then 404 - <code>HttpStatus.BAD_REQUEST</code> is returned.
|
||||||
|
* </br>
|
||||||
|
* The response type will be (<code>application/json;charset=UTF-8</code>)
|
||||||
|
* a string representing an array-like list of handles:
|
||||||
|
* </br>
|
||||||
|
* Example:
|
||||||
|
* <ul>
|
||||||
|
* <li>Request: GET - http://{dspace.url}/listhandles/prefix</li>
|
||||||
|
* <li>Response: 200 - ["prefix/zero","prefix1/one","prefix2/two"]
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param request {@code HttpServletRequest}
|
||||||
|
* @param prefix {@code String} representing the prefix that will be searched
|
||||||
|
* @return List of valid prefixes with status 200 <code>HttpStatus.OK</code>
|
||||||
|
* or status 400 <code>HttpStatus.NOT_FOUND</code> if disabled by properties
|
||||||
|
* or status 404 <code>HttpStatus.BAD_REQUEST</code> if blank prefix is requested.
|
||||||
|
*/
|
||||||
|
public ResponseEntity<String> listHandles(HttpServletRequest request, @PathVariable String prefix) {
|
||||||
|
if (!this.hdlResolverService.isListhandlesEnabled()) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(prefix)) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
return new ResponseEntity<>(
|
||||||
|
this.mapAsJson(this.hdlResolverService.listHandles(ContextUtil.obtainContext(request), prefix)),
|
||||||
|
HttpStatus.OK
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the handle to a correct response.
|
||||||
|
*
|
||||||
|
* @param request HttpServletRequest
|
||||||
|
* @param handleResolver HdlResolverDTO - Handle resolver
|
||||||
|
* @return One element list using String if found, else null String.
|
||||||
|
*/
|
||||||
|
private String resolveToURL(HttpServletRequest request, HdlResolverDTO handleResolver) {
|
||||||
|
return mapAsJson(this.hdlResolverService.resolveToURL(ContextUtil.obtainContext(request), handleResolver));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String mapAsJson(final String resolvedUrl) {
|
||||||
|
String json = "null";
|
||||||
|
if (StringUtils.isNotEmpty(resolvedUrl)) {
|
||||||
|
json = mapAsJson(List.of(resolvedUrl));
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String mapAsJson(final List<String> jsonList) {
|
||||||
|
String json = "null";
|
||||||
|
if (jsonList != null && !jsonList.isEmpty()) {
|
||||||
|
try {
|
||||||
|
json = new ObjectMapper().writeValueAsString(jsonList);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Error during conversion of response!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,327 @@
|
|||||||
|
/**
|
||||||
|
* 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.app.rest.hdlresolver;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.allOf;
|
||||||
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
|
||||||
|
import org.dspace.builder.CollectionBuilder;
|
||||||
|
import org.dspace.builder.CommunityBuilder;
|
||||||
|
import org.dspace.builder.ItemBuilder;
|
||||||
|
import org.dspace.content.Collection;
|
||||||
|
import org.dspace.content.Item;
|
||||||
|
import org.dspace.handle.hdlresolver.HdlResolverServiceImpl;
|
||||||
|
import org.dspace.services.ConfigurationService;
|
||||||
|
import org.hamcrest.core.StringContains;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.test.web.servlet.ResultMatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca@4science.com)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class HdlResolverRestControllerIT extends AbstractControllerIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConfigurationService configurationService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that any mapped <code>hdlIdentifier</code> returns the
|
||||||
|
* corresponding <code>handle URL</code>
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void givenMappedIdentifierWhenCallHdlresolverThenReturnsMappedURL() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
// ** START GIVEN **
|
||||||
|
|
||||||
|
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
|
||||||
|
|
||||||
|
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1")
|
||||||
|
.withLogo("TestingContentForLogo").build();
|
||||||
|
|
||||||
|
Item publicItem1 = ItemBuilder.createItem(context, col1).withTitle("Public item 1").withIssueDate("2017-10-17")
|
||||||
|
.withAuthor("Smith, Donald").withAuthor("Doe, John").withSubject("ExtraEntry")
|
||||||
|
.withHandle("123456789/testHdlResolver").build();
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
// ** END GIVEN **
|
||||||
|
|
||||||
|
ResultMatcher matchHandleResponse = jsonPath("$[0]",
|
||||||
|
StringContains.containsString("123456789/testHdlResolver"));
|
||||||
|
getClient()
|
||||||
|
.perform(get(HdlResolverRestController.LISTHANDLES + publicItem1.getHandle()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(matchHandleResponse);
|
||||||
|
getClient()
|
||||||
|
.perform(get(HdlResolverRestController.RESOLVE + publicItem1.getHandle()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(matchHandleResponse);
|
||||||
|
getClient()
|
||||||
|
.perform(get(HdlResolverRestController.HDL_RESOLVER + publicItem1.getHandle()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(matchHandleResponse);
|
||||||
|
getClient()
|
||||||
|
.perform(get("/wrongController/" + publicItem1.getHandle()))
|
||||||
|
.andExpect(status().isNotFound());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenAnyHandlesWhenDisabledListhandleThenReturnsNotFoundResp()
|
||||||
|
throws Exception {
|
||||||
|
this.configurationService.setProperty(HdlResolverServiceImpl.LISTHANDLES_HIDE_PROP, true);
|
||||||
|
try {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
|
||||||
|
|
||||||
|
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1")
|
||||||
|
.withLogo("TestingContentForLogo").build();
|
||||||
|
|
||||||
|
String handlePrefix = "123456789/PREFIX";
|
||||||
|
ItemBuilder.createItem(context, col1)
|
||||||
|
.withTitle("Public item 1")
|
||||||
|
.withIssueDate("2017-10-17")
|
||||||
|
.withAuthor("Smith, Donald")
|
||||||
|
.withAuthor("Doe, John")
|
||||||
|
.withSubject("ExtraEntry")
|
||||||
|
.withHandle(handlePrefix)
|
||||||
|
.build();
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
getClient()
|
||||||
|
.perform(get(HdlResolverRestController.LISTHANDLES))
|
||||||
|
.andExpect(status().isNotFound());
|
||||||
|
|
||||||
|
getClient()
|
||||||
|
.perform(get(HdlResolverRestController.LISTHANDLES + handlePrefix))
|
||||||
|
.andExpect(status().isNotFound());
|
||||||
|
|
||||||
|
getClient()
|
||||||
|
.perform(get(HdlResolverRestController.LISTHANDLES + "anyHandlePrefixHere"))
|
||||||
|
.andExpect(status().isNotFound());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
this.configurationService.setProperty(HdlResolverServiceImpl.LISTHANDLES_HIDE_PROP, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenMappedHandlesWhenCalledListHandlesWithoutPrefixThenReturnsBadRequestResp()
|
||||||
|
throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
|
||||||
|
|
||||||
|
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1")
|
||||||
|
.withLogo("TestingContentForLogo").build();
|
||||||
|
|
||||||
|
String handlePrefix = "123456789/PREFIX";
|
||||||
|
ItemBuilder.createItem(context, col1)
|
||||||
|
.withTitle("Public item 1")
|
||||||
|
.withIssueDate("2017-10-17")
|
||||||
|
.withAuthor("Smith, Donald")
|
||||||
|
.withAuthor("Doe, John")
|
||||||
|
.withSubject("ExtraEntry")
|
||||||
|
.withHandle(handlePrefix)
|
||||||
|
.build();
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
getClient()
|
||||||
|
.perform(get(HdlResolverRestController.LISTHANDLES))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenMappedHandlesWhenCalledListHandlesWithPrefixThenReturnsAllHandlesWithThatPrefix()
|
||||||
|
throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
// ** START GIVEN **
|
||||||
|
|
||||||
|
parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build();
|
||||||
|
|
||||||
|
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1")
|
||||||
|
.withLogo("TestingContentForLogo").build();
|
||||||
|
|
||||||
|
String handlePrefix = "123456789/PREFIX";
|
||||||
|
ItemBuilder.createItem(context, col1)
|
||||||
|
.withTitle("Public item 1")
|
||||||
|
.withIssueDate("2017-10-17")
|
||||||
|
.withAuthor("Smith, Donald")
|
||||||
|
.withAuthor("Doe, John")
|
||||||
|
.withSubject("ExtraEntry")
|
||||||
|
.withHandle(handlePrefix)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String handlePrefix1 = "123456789/PREFIX1";
|
||||||
|
ItemBuilder.createItem(context, col1)
|
||||||
|
.withTitle("Public item 1")
|
||||||
|
.withIssueDate("2017-10-17")
|
||||||
|
.withAuthor("Smith, Donald")
|
||||||
|
.withAuthor("Doe, John")
|
||||||
|
.withSubject("ExtraEntry")
|
||||||
|
.withHandle(handlePrefix1)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String noHandle = "123456789/NOPREFIX";
|
||||||
|
ItemBuilder.createItem(context, col1)
|
||||||
|
.withTitle("Public item 1")
|
||||||
|
.withIssueDate("2017-10-17")
|
||||||
|
.withAuthor("Smith, Donald")
|
||||||
|
.withAuthor("Doe, John")
|
||||||
|
.withSubject("ExtraEntry")
|
||||||
|
.withHandle(noHandle)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String testHandle = "123456789/TEST";
|
||||||
|
ItemBuilder.createItem(context, col1)
|
||||||
|
.withTitle("Public item 1")
|
||||||
|
.withIssueDate("2017-10-17")
|
||||||
|
.withAuthor("Smith, Donald")
|
||||||
|
.withAuthor("Doe, John")
|
||||||
|
.withSubject("ExtraEntry")
|
||||||
|
.withHandle(testHandle)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
// ** END GIVEN **
|
||||||
|
|
||||||
|
ResultMatcher matchHandleResponse =
|
||||||
|
jsonPath("$[*]",
|
||||||
|
allOf(
|
||||||
|
containsInAnyOrder(handlePrefix, handlePrefix1),
|
||||||
|
not(containsInAnyOrder(noHandle, testHandle))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
getClient()
|
||||||
|
.perform(get(HdlResolverRestController.LISTHANDLES + handlePrefix))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(matchHandleResponse);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that a null hdlIdentifier returns a
|
||||||
|
*
|
||||||
|
* <code>HttpStatus.BAD_REQUEST</code>
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
* @throws SQLException
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void givenNullHdlIdentifierWhenCallHdlresolverThenReturnsBadRequest() throws Exception {
|
||||||
|
|
||||||
|
getClient().perform(
|
||||||
|
get(HdlResolverRestController.HDL_RESOLVER + "null"))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
getClient().perform(
|
||||||
|
get(HdlResolverRestController.RESOLVE + "null"))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that an empty hdlIdentifier returns a
|
||||||
|
* <code>HttpStatus.BAD_REQUEST</code>
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
* @throws SQLException
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void givenEmptyHdlIdentifierWhenCallHdlresolverThenReturnsNull() throws Exception {
|
||||||
|
|
||||||
|
getClient()
|
||||||
|
.perform(get(HdlResolverRestController.HDL_RESOLVER + " "))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
getClient()
|
||||||
|
.perform(get(HdlResolverRestController.RESOLVE + " "))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that any unmapped hdlIdentifier returns a null response
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void givenIdentifierNotMappedWhenCallHdlresolverThenReturnsNull() throws Exception {
|
||||||
|
getClient()
|
||||||
|
.perform(get(HdlResolverRestController.HDL_RESOLVER + "testHdlResolver/2"))
|
||||||
|
.andExpect(status().isOk()).andExpect(content().string("null"));
|
||||||
|
getClient()
|
||||||
|
.perform(get(HdlResolverRestController.RESOLVE + "testHdlResolver/2"))
|
||||||
|
.andExpect(status().isOk()).andExpect(content().string("null"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenMappedPrefixWhenNoAdditionalPrefixesConfThenReturnsHandlePrefix() throws Exception {
|
||||||
|
String handlePrefix = this.configurationService.getProperty("handle.prefix");
|
||||||
|
ResultMatcher matchHandleResponse =
|
||||||
|
jsonPath("$[*]",
|
||||||
|
allOf(
|
||||||
|
containsInAnyOrder(handlePrefix)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
getClient()
|
||||||
|
.perform(get(HdlResolverRestController.LISTPREFIXES))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(matchHandleResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenMappedPrefixWhenAdditionalPrefixesConfThenReturnsAllOfThem() throws Exception {
|
||||||
|
String handlePrefix = this.configurationService.getProperty("handle.prefix");
|
||||||
|
String[] defaultValue = this.configurationService.getArrayProperty("handle.additional.prefixes");
|
||||||
|
try {
|
||||||
|
String additionalPrefixes = "additional1,additional2";
|
||||||
|
this.configurationService.setProperty("handle.additional.prefixes", additionalPrefixes);
|
||||||
|
|
||||||
|
List<String> validPrefixes = Arrays.asList(additionalPrefixes.split(","));
|
||||||
|
validPrefixes.add(handlePrefix);
|
||||||
|
ResultMatcher matchHandleResponse =
|
||||||
|
jsonPath("$[*]",
|
||||||
|
allOf(
|
||||||
|
containsInAnyOrder(validPrefixes)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
getClient()
|
||||||
|
.perform(get(HdlResolverRestController.LISTPREFIXES))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(matchHandleResponse);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
this.configurationService.setProperty("handle.additional.prefixse", defaultValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -330,8 +330,15 @@ handle.dir = ${dspace.dir}/handle-server
|
|||||||
# that repository)
|
# that repository)
|
||||||
# handle.additional.prefixes = prefix1[, prefix2]
|
# handle.additional.prefixes = prefix1[, prefix2]
|
||||||
|
|
||||||
# By default we hide the list handles method in the JSON endpoint as it could
|
# Whether to enable the DSpace handle resolver endpoints necessary for
|
||||||
# produce heavy load for large repository
|
# https://github.com/DSpace/Remote-Handle-Resolver
|
||||||
|
# Defaults to "false" which means these handle resolver endpoints are not available.
|
||||||
|
# handle.remote-resolver.enabled = false
|
||||||
|
|
||||||
|
# Whether to enable the DSpace listhandles resolver that lists all available
|
||||||
|
# handles for this DSpace installation.
|
||||||
|
# Defaults to "false" which means is possible to obtain the list of handles
|
||||||
|
# of this DSpace installation, whenever the `handle.remote-resolver.enabled = true`.
|
||||||
# handle.hide.listhandles = false
|
# handle.hide.listhandles = false
|
||||||
|
|
||||||
##### Authorization system configuration - Delegate ADMIN #####
|
##### Authorization system configuration - Delegate ADMIN #####
|
||||||
|
@@ -67,6 +67,8 @@
|
|||||||
|
|
||||||
<bean class="org.dspace.profile.OrcidMetadataCopyingAction"/>
|
<bean class="org.dspace.profile.OrcidMetadataCopyingAction"/>
|
||||||
|
|
||||||
|
<bean class="org.dspace.handle.hdlresolver.HdlResolverServiceImpl"/>
|
||||||
|
|
||||||
<bean class='org.dspace.service.impl.HttpConnectionPoolService'
|
<bean class='org.dspace.service.impl.HttpConnectionPoolService'
|
||||||
id='solrHttpConnectionPoolService'
|
id='solrHttpConnectionPoolService'
|
||||||
scope='singleton'
|
scope='singleton'
|
||||||
|
Reference in New Issue
Block a user