[TLC-249] Refactor identifiers create endpoint

This commit is contained in:
Kim Shepherd
2023-02-01 16:26:03 +13:00
parent 0f77f1d3b1
commit f9ffceb172
8 changed files with 279 additions and 170 deletions

View File

@@ -44,7 +44,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/" + IdentifierRestController.CATEGORY)
public class IdentifierRestController implements InitializingBean {
public static final String CATEGORY = "pid";
public static final String CATEGORY = "pid_SDFSDFSDFSDF";
public static final String ACTION = "find";

View File

@@ -1,166 +0,0 @@
/**
* 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;
import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID;
import java.sql.SQLException;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.dspace.app.rest.converter.ConverterService;
import org.dspace.app.rest.converter.MetadataConverter;
import org.dspace.app.rest.model.IdentifiersRest;
import org.dspace.app.rest.model.ItemRest;
import org.dspace.app.rest.model.hateoas.ItemResource;
import org.dspace.app.rest.repository.ItemRestRepository;
import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.app.rest.utils.Utils;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Item;
import org.dspace.content.logic.TrueFilter;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.identifier.DOI;
import org.dspace.identifier.DOIIdentifierProvider;
import org.dspace.identifier.IdentifierException;
import org.dspace.identifier.IdentifierNotFoundException;
import org.dspace.identifier.service.DOIService;
import org.dspace.identifier.service.IdentifierService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.ControllerUtils;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* Controller to register a DOI for an item, if it has no DOI already, or a DOI in a state where it can be
* advanced to queue for reservation or registration.
*
* @author Kim Shepherd
*/
@RestController
@RequestMapping("/api/" + ItemRest.CATEGORY + "/" + ItemRest.PLURAL_NAME + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID
+ "/" + IdentifiersRest.NAME)
public class ItemIdentifierController {
@Autowired
ConverterService converter;
@Autowired
ItemService itemService;
@Autowired
ItemRestRepository itemRestRepository;
@Autowired
MetadataConverter metadataConverter;
@Autowired
IdentifierService identifierService;
@Autowired
DOIService doiService;
@Autowired
Utils utils;
/**
* Request that an identifier of a given type is 'created' for an item. Depending on the identifier type
* this could mean minting, registration, reservation, queuing for registration later, etc.
*
* @return 201 CREATED on success, or 302 FOUND if already created, or an error
*/
@RequestMapping(method = RequestMethod.POST)
@PreAuthorize("hasPermission(#uuid, 'ITEM', 'ADMIN')")
public ResponseEntity<RepresentationModel<?>> registerIdentifierForItem(@PathVariable UUID uuid,
HttpServletRequest request,
HttpServletResponse response,
@RequestParam(name = "type") String type)
throws SQLException, AuthorizeException {
Context context = ContextUtil.obtainContext(request);
Item item = itemService.find(context, uuid);
if (item == null) {
throw new ResourceNotFoundException("Could not find item with id " + uuid);
}
// Check for a valid identifier type and register the appropriate type of identifier
if ("doi".equals(type)) {
return registerDOI(context, item);
} else {
throw new IllegalArgumentException("Valid identifier type (eg. 'doi') is required, parameter name 'type'");
}
}
/**
* Queue a new, pending or minted DOI for registration for a given item. Requires administrative privilege.
* This request is sent from the Register DOI button (configurable) on the item status page.
*
* @return 302 FOUND if the DOI is already registered or reserved, 201 CREATED if queued for registration
*/
private ResponseEntity<RepresentationModel<?>> registerDOI(Context context, Item item)
throws SQLException, AuthorizeException {
String identifier = null;
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
ItemRest itemRest;
try {
DOIIdentifierProvider doiIdentifierProvider = DSpaceServicesFactory.getInstance().getServiceManager()
.getServiceByName("org.dspace.identifier.DOIIdentifierProvider", DOIIdentifierProvider.class);
if (doiIdentifierProvider != null) {
DOI doi = doiService.findDOIByDSpaceObject(context, item);
boolean exists = false;
boolean pending = false;
if (null != doi) {
exists = true;
Integer doiStatus = doiService.findDOIByDSpaceObject(context, item).getStatus();
// Check if this DOI has a status which makes it eligible for registration
if (null == doiStatus || DOIIdentifierProvider.MINTED.equals(doiStatus)
|| DOIIdentifierProvider.PENDING.equals(doiStatus)) {
pending = true;
}
}
if (!exists || pending) {
// Mint identifier and return 201 CREATED
doiIdentifierProvider.register(context, item, new TrueFilter());
httpStatus = HttpStatus.CREATED;
} else {
// This DOI exists and isn't in a state where it can be queued for registration
// We'll return 302 FOUND to indicate it's here and not an error, but no creation was performed
httpStatus = HttpStatus.FOUND;
}
} else {
throw new IllegalStateException("No DOI provider is configured");
}
} catch (IdentifierNotFoundException e) {
httpStatus = HttpStatus.NOT_FOUND;
} catch (IdentifierException e) {
throw new IllegalStateException("Failed to register identifier: " + identifier);
}
// We didn't exactly change the item, but we did queue an identifier which is closely associated with it
// so we should update the last modified date here
itemService.updateLastModified(context, item);
itemRest = converter.toRest(item, utils.obtainProjection());
context.complete();
ItemResource itemResource = converter.toResource(itemRest);
// Return the status and item resource
return ControllerUtils.toResponseEntity(httpStatus, new HttpHeaders(), itemResource);
}
}

View File

@@ -125,6 +125,7 @@ public class RestResourceController implements InitializingBean {
// this doesn't work as we don't have an active http request
// see https://github.com/spring-projects/spring-hateoas/issues/408
// Link l = linkTo(this.getClass(), r).withRel(r);
log.error(r);
String[] split = r.split("\\.", 2);
String plural = English.plural(split[1]);
Link l = Link.of("/api/" + split[0] + "/" + plural, plural);

View File

@@ -8,6 +8,7 @@
package org.dspace.app.rest.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.dspace.app.rest.RestResourceController;
/**
* Implementation of IdentifierRest REST resource, representing some DSpace identifier
@@ -15,7 +16,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
*
* @author Kim Shepherd <kim@shepherd.nz>
*/
public class IdentifierRest implements RestModel {
public class IdentifierRest extends BaseObjectRest<String> implements RestModel {
// Set names used in component wiring
public static final String NAME = "identifier";
@@ -51,6 +52,11 @@ public class IdentifierRest implements RestModel {
return NAME;
}
@Override
public String getTypePlural() {
return PLURAL_NAME;
}
/**
* Get the identifier value eg full DOI URL
* @return identifier value eg. https://doi.org/123/234
@@ -98,4 +104,14 @@ public class IdentifierRest implements RestModel {
public void setIdentifierStatus(String identifierStatus) {
this.identifierStatus = identifierStatus;
}
@Override
public String getCategory() {
return "ppid";
}
@Override
public Class getController() {
return RestResourceController.class;
}
}

View File

@@ -18,7 +18,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
*
* @author Kim Shepherd <kim@shepherd.nz>
*/
public class IdentifiersRest implements RestModel {
public class IdentifiersRest extends BaseObjectRest<String> {
// Set names used in component wiring
public static final String NAME = "identifiers";
@@ -44,4 +44,14 @@ public class IdentifiersRest implements RestModel {
public void setIdentifiers(List<IdentifierRest> identifiers) {
this.identifiers = identifiers;
}
@Override
public String getCategory() {
return null;
}
@Override
public Class getController() {
return null;
}
}

View File

@@ -0,0 +1,25 @@
/**
* 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.model.hateoas;
import org.dspace.app.rest.model.IdentifierRest;
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
import org.dspace.app.rest.utils.Utils;
/**
*
* Simple HAL wrapper for IdentifierRest model
*
* @author Kim Shepherd
*/
@RelNameDSpaceResource(IdentifierRest.NAME)
public class IdentifierResource extends DSpaceResource<IdentifierRest> {
public IdentifierResource(IdentifierRest model, Utils utils) {
super(model, utils);
}
}

View File

@@ -0,0 +1,223 @@
/**
* 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.repository;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.function.Function;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.NotSupportedException;
import org.dspace.app.rest.DiscoverableEndpointsService;
import org.dspace.app.rest.IdentifierRestController;
import org.dspace.app.rest.Parameter;
import org.dspace.app.rest.SearchRestMethod;
import org.dspace.app.rest.exception.LinkNotFoundException;
import org.dspace.app.rest.exception.RESTAuthorizationException;
import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException;
import org.dspace.app.rest.exception.UnprocessableEntityException;
import org.dspace.app.rest.model.IdentifierRest;
import org.dspace.app.rest.repository.handler.service.UriListHandlerService;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.logic.TrueFilter;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.handle.service.HandleService;
import org.dspace.identifier.DOI;
import org.dspace.identifier.DOIIdentifierProvider;
import org.dspace.identifier.IdentifierException;
import org.dspace.identifier.service.DOIService;
import org.dspace.identifier.service.IdentifierService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Controller for exposition of vocabularies entry details for the submission
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*/
@Component("pid.identifier")
public class IdentifierRestRepository extends DSpaceRestRepository<IdentifierRest, String> {
@Autowired
private DiscoverableEndpointsService discoverableEndpointsService;
@Autowired
private UriListHandlerService uriListHandlerService;
@Autowired
private DOIService doiService;
@Autowired
private HandleService handleService;
@Autowired
private ItemService itemService;
@Autowired
private IdentifierService identifierService;
@PreAuthorize("permitAll()")
@Override
public Page<IdentifierRest> findAll(Context context, Pageable pageable) {
List<IdentifierRest> results = new ArrayList<>();
//return converter.toRestPage(results, pageable, utils.obtainProjection());
return new PageImpl<>(results, pageable, 0);
}
@PreAuthorize("permitAll()")
@Override
public IdentifierRest findOne(Context context, String identifier) {
DSpaceObject dso;
IdentifierRest identifierRest = new IdentifierRest();
try {
// Resolve to an object first - if that fails then this is not a valid identifier anyway.
dso = identifierService.resolve(context, identifier);
if (dso != null) {
// DSpace has no concept of a higher-level Identifier object, so in order to detect the type
// and return sufficient information, we have to try the identifier types we know are currently
// supported.
// First, try to resolve to a handle.
dso = handleService.resolveToObject(context, identifier);
if (dso == null) {
// No object found for a handle, try DOI
DOI doi = doiService.findByDoi(context, identifier);
if (doi != null) {
String doiUrl = doiService.DOIToExternalForm(doi.getDoi());
identifierRest.setIdentifierType("doi");
identifierRest.setIdentifierStatus(DOIIdentifierProvider.statusText[doi.getStatus()]);
identifierRest.setValue(doiUrl);
}
} else {
// Handle found
identifierRest.setIdentifierType("handle");
identifierRest.setIdentifierStatus(null);
identifierRest.setValue(handleService.getCanonicalForm(dso.getHandle()));
}
} else {
throw new LinkNotFoundException(IdentifierRestController.CATEGORY, IdentifierRest.NAME, identifier);
}
} catch (SQLException | IdentifierException e) {
throw new LinkNotFoundException(IdentifierRestController.CATEGORY, IdentifierRest.NAME, identifier);
}
return identifierRest;
}
@SearchRestMethod(name = "findByItem")
@PreAuthorize("permitAll()")
public Page<IdentifierRest> findByItem(@Parameter(value = "uuid", required = true)
String uuid, Pageable pageable) {
Context context = obtainContext();
List<IdentifierRest> results = new ArrayList<>();
try {
DSpaceObject dso = itemService.find(context, UUID.fromString(uuid));
String handle = dso.getHandle();
DOI doi = doiService.findDOIByDSpaceObject(context, dso);
if (doi != null) {
String doiUrl = doiService.DOIToExternalForm(doi.getDoi());
results.add(new IdentifierRest(doiUrl, "doi", DOIIdentifierProvider.statusText[doi.getStatus()]));
}
if (handle != null) {
String handleUrl = handleService.getCanonicalForm(handle);
results.add(new IdentifierRest(handleUrl, "handle", null));
}
} catch (SQLException | IdentifierException e) {
throw new LinkNotFoundException(IdentifierRestController.CATEGORY, IdentifierRest.NAME, uuid);
}
// Return list of identifiers for this DSpaceObject
return new PageImpl<>(results, pageable, results.size());
}
/**
* Right now, the only supported identifier type is DOI
* @param context
* the dspace context
* @param list
* The list of Strings that will be used as data for the object that's to be created
* This list is retrieved from the uri-list body
* @return
* @throws AuthorizeException
* @throws SQLException
* @throws RepositoryMethodNotImplementedException
*/
@Override
protected IdentifierRest createAndReturn(Context context, List<String> list)
throws AuthorizeException, SQLException, RepositoryMethodNotImplementedException {
HttpServletRequest request = getRequestService().getCurrentRequest().getHttpServletRequest();
// Extract 'type' from request
String type = request.getParameter("type");
if (!"doi".equals(type)) {
throw new NotSupportedException("Only identifiers of type 'doi' are supported");
}
IdentifierRest identifierRest = new IdentifierRest();
try {
Item item = uriListHandlerService.handle(context, request, list, Item.class);
if (item == null) {
throw new UnprocessableEntityException(
"No DSpace Item found, the uri-list does not contain a valid resource");
}
// Does this item have a DOI already? If the DOI doesn't exist or has a null, MINTED or PENDING status
// then we proceed with a typical create operation and return 201 success with the object
DOI doi = doiService.findDOIByDSpaceObject(context, item);
if (doi == null || null == doi.getStatus() || DOIIdentifierProvider.MINTED.equals(doi.getStatus())
|| DOIIdentifierProvider.PENDING.equals(doi.getStatus())) {
// Proceed with creation
// Register things
identifierRest = registerDOI(context, item);
} else {
// Nothing to do here, return existing DOI
identifierRest = new IdentifierRest(doiService.DOIToExternalForm(doi.getDoi()),
"doi", DOIIdentifierProvider.statusText[doi.getStatus()]);
}
} catch (AuthorizeException e) {
throw new RESTAuthorizationException(e);
} catch (IdentifierException e) {
throw new UnprocessableEntityException(e.getMessage());
}
return identifierRest;
}
private IdentifierRest registerDOI(Context context, Item item)
throws SQLException, AuthorizeException {
String identifier = null;
IdentifierRest identifierRest = new IdentifierRest();
identifierRest.setIdentifierType("doi");
try {
DOIIdentifierProvider doiIdentifierProvider = DSpaceServicesFactory.getInstance().getServiceManager()
.getServiceByName("org.dspace.identifier.DOIIdentifierProvider", DOIIdentifierProvider.class);
if (doiIdentifierProvider != null) {
String doiValue = doiIdentifierProvider.register(context, item, new TrueFilter());
identifierRest.setValue(doiValue);
// Get new status
DOI doi = doiService.findByDoi(context, doiValue);
if (doi != null) {
identifierRest.setIdentifierStatus(DOIIdentifierProvider.statusText[doi.getStatus()]);
}
} else {
throw new IllegalStateException("No DOI provider is configured");
}
} catch (IdentifierException e) {
throw new IllegalStateException("Failed to register identifier: " + identifier);
}
// We didn't exactly change the item, but we did queue an identifier which is closely associated with it,
// so we should update the last modified date here
itemService.updateLastModified(context, item);
context.complete();
return identifierRest;
}
@Override
public Class<IdentifierRest> getDomainClass() {
return IdentifierRest.class;
}
}

View File

@@ -41,7 +41,7 @@
# Show Register DOI button in item status page?
# Default: false
#identifiers.item-status.registerDOI = true
#identifiers.item-status.register-doi = true
# Which identifier types to show in submission step?
# Default: handle, doi (currently the only supported identifier 'types')