Skip to content

Commit 8ddf0af

Browse files
UFAL/Refbox upgrade (#1015)
* Created integration test * Created an endpoint for complete ref box information like in the v5 * Added integration tests for formatting authors * Removed double semicolon * Fetch the metadata value following the current locale * Updated firstMetadataValue because it did return empty string instead of null * Use DEFAULT_LANGUAGE instead of current locale
1 parent 0bbdc00 commit 8ddf0af

7 files changed

Lines changed: 713 additions & 10 deletions

File tree

dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinRefBoxController.java

Lines changed: 209 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.UUID;
2626
import java.util.regex.Matcher;
2727
import java.util.regex.Pattern;
28+
import java.util.stream.Collectors;
2829
import javax.servlet.ServletException;
2930
import javax.servlet.http.HttpServletRequest;
3031
import javax.servlet.http.HttpServletResponse;
@@ -47,15 +48,22 @@
4748
import org.apache.commons.lang3.StringUtils;
4849
import org.apache.logging.log4j.Logger;
4950
import org.dspace.app.rest.converter.ConverterService;
51+
import org.dspace.app.rest.exception.UnprocessableEntityException;
5052
import org.dspace.app.rest.model.ClarinFeaturedServiceRest;
53+
import org.dspace.app.rest.model.refbox.ExportFormatDTO;
54+
import org.dspace.app.rest.model.refbox.FeaturedServiceDTO;
55+
import org.dspace.app.rest.model.refbox.FeaturedServiceLinkDTO;
56+
import org.dspace.app.rest.model.refbox.RefBoxDTO;
5157
import org.dspace.app.rest.utils.ContextUtil;
5258
import org.dspace.app.rest.utils.Utils;
59+
import org.dspace.content.DSpaceObject;
5360
import org.dspace.content.Item;
5461
import org.dspace.content.MetadataValue;
5562
import org.dspace.content.clarin.ClarinFeaturedService;
5663
import org.dspace.content.clarin.ClarinFeaturedServiceLink;
5764
import org.dspace.content.service.ItemService;
5865
import org.dspace.core.Context;
66+
import org.dspace.handle.service.HandleService;
5967
import org.dspace.services.ConfigurationService;
6068
import org.dspace.xoai.services.api.config.XOAIManagerResolver;
6169
import org.dspace.xoai.services.api.config.XOAIManagerResolverException;
@@ -90,6 +98,13 @@ public class ClarinRefBoxController {
9098

9199
private final static String BIBTEX_TYPE = "bibtex";
92100

101+
/**
102+
* Default language for the RefBox metadata values
103+
* This will be changed in the future to support multiple languages, probably fetching the language from the
104+
* request, but for now there is a mess in the metadata value languages, so we will use the default.
105+
*/
106+
private final static String DEFAULT_LANGUAGE = "*";
107+
93108
private final Logger log = org.apache.logging.log4j.LogManager.getLogger(ClarinRefBoxController.class);
94109

95110
@Autowired
@@ -119,6 +134,9 @@ public class ClarinRefBoxController {
119134
@Autowired
120135
private ItemRepositoryResolver itemRepositoryResolver;
121136

137+
@Autowired
138+
private HandleService handleService;
139+
122140
private final DSpaceResumptionTokenFormatter resumptionTokenFormat = new DSpaceResumptionTokenFormatter();
123141

124142
/**
@@ -163,7 +181,7 @@ public Page<ClarinFeaturedServiceRest> getServices(@RequestParam(name = "id") UU
163181
// Check if the item has the metadata for this featured service, if it doesn't have - do NOT return the
164182
// featured service.
165183
List<MetadataValue> itemMetadata = itemService.getMetadata(item, "local", "featuredService",
166-
featuredServiceName, Item.ANY, false);
184+
featuredServiceName, DEFAULT_LANGUAGE);
167185
if (CollectionUtils.isEmpty(itemMetadata)) {
168186
continue;
169187
}
@@ -286,6 +304,190 @@ public ResponseEntity getCitationText(@RequestParam(name = "type") String type,
286304
return new ResponseEntity<>(oaiMetadataWrapper, HttpStatus.valueOf(SC_OK));
287305
}
288306

307+
/**
308+
* Get the RefBox information based on the handle.
309+
* It returns the display text, export formats and featured services.
310+
*/
311+
@RequestMapping(method = RequestMethod.GET, produces = "application/json")
312+
public ResponseEntity<RefBoxDTO> getRefboxInfo(
313+
@RequestParam(name = "handle") String handle,
314+
HttpServletRequest request) throws SQLException {
315+
316+
Context context = ContextUtil.obtainContext(request);
317+
if (context == null) {
318+
throw new RuntimeException("Cannot obtain the context from the request.");
319+
}
320+
321+
DSpaceObject dSpaceObject = handleService.resolveToObject(context, handle);
322+
if (!(dSpaceObject instanceof Item)) {
323+
throw new UnprocessableEntityException("The handle does not resolve to an Item.");
324+
}
325+
Item item = (Item) dSpaceObject;
326+
327+
String title = itemService.getMetadataFirstValue(item, "dc", "title", null, DEFAULT_LANGUAGE);
328+
String displayText = buildDisplayText(context, item);
329+
330+
// Build exportFormats as a map with "exportFormat" key
331+
Map<String, List<ExportFormatDTO>> exportFormatsMap = new HashMap<>();
332+
exportFormatsMap.put("exportFormat", buildExportFormats(item));
333+
334+
// Build featuredServices as a map with "featuredService" key
335+
Map<String, List<FeaturedServiceDTO>> featuredServicesMap = new HashMap<>();
336+
featuredServicesMap.put("featuredService", buildFeaturedServices(context, item));
337+
338+
// Pass these maps to RefBoxDTO
339+
RefBoxDTO refBoxDTO = new RefBoxDTO(
340+
displayText,
341+
exportFormatsMap,
342+
featuredServicesMap,
343+
title != null ? title : ""
344+
);
345+
return ResponseEntity.ok(refBoxDTO);
346+
}
347+
348+
/**
349+
* Build the display text for the RefBox based on the Item Metadata.
350+
*/
351+
private String buildDisplayText(Context context, Item item) {
352+
// 1. Authors
353+
List<String> authors = itemService.getMetadata(item, "dc", "contributor", "author", DEFAULT_LANGUAGE)
354+
.stream().map(MetadataValue::getValue).collect(Collectors.toList());
355+
// If there are no authors, try to get the publisher metadata
356+
if (authors.isEmpty()) {
357+
authors = itemService.getMetadata(item, "dc", "publisher", null, DEFAULT_LANGUAGE)
358+
.stream().map(MetadataValue::getValue).collect(Collectors.toList());
359+
}
360+
String authorText = formatAuthors(authors);
361+
362+
// 2. Year
363+
String year = "";
364+
String issued = itemService.getMetadataFirstValue(item, "dc", "date", "issued", DEFAULT_LANGUAGE);
365+
if (issued != null && !issued.isEmpty()) {
366+
// The issued date is in the format YYYY-MM-DD, we take the year part
367+
year = issued.split("-")[0];
368+
}
369+
370+
// 3. Title
371+
String title = itemService.getMetadataFirstValue(item, "dc", "title", null, DEFAULT_LANGUAGE);
372+
373+
// 4. Repository name
374+
String repository = configurationService.getProperty("dspace.name");
375+
376+
// 5. Identifier URI (prefer DOI)
377+
String identifier = itemService.getMetadataFirstValue(item, "dc", "identifier", "doi", DEFAULT_LANGUAGE);
378+
if (identifier == null) {
379+
identifier = itemService.getMetadataFirstValue(item, "dc", "identifier", "uri", DEFAULT_LANGUAGE);
380+
}
381+
382+
// 6. Format
383+
// Using html tags to format the output because this display text will be rendered in the UI
384+
StringBuilder sb = new StringBuilder();
385+
if (authorText != null && !authorText.isEmpty()) {
386+
sb.append(authorText);
387+
}
388+
if (year != null && !year.isEmpty()) {
389+
if (sb.length() > 0) {
390+
sb.append(", ");
391+
}
392+
sb.append(year);
393+
}
394+
sb.append(", \n <i>").append(title != null ? title : "").append("</i>");
395+
if (repository != null && !repository.isEmpty()) {
396+
sb.append(", ").append(repository);
397+
}
398+
sb.append(", \n <a href=\"").append(identifier != null ? identifier : "").append("\">")
399+
.append(identifier != null ? identifier : "").append("</a>.");
400+
return sb.toString();
401+
}
402+
403+
/**
404+
* Format the authors for the display text.
405+
* If there is one author, it will return that author.
406+
* If there are 2-5 authors, it will join them with "; " and replace the last ";" with " and".
407+
* If there are more than 5 authors, it will return the first author and "et al.".
408+
*/
409+
private String formatAuthors(List<String> authors) {
410+
String authorText = "";
411+
if (authors.size() == 1) {
412+
authorText = authors.get(0);
413+
} else if (authors.size() <= 5) {
414+
authorText = String.join("; ", authors);
415+
authorText = authorText.replaceAll("; ([^;]*)$", " and $1");
416+
} else {
417+
authorText = authors.get(0) + "; et al.";
418+
}
419+
return authorText;
420+
}
421+
422+
/**
423+
* Build the export formats for the RefBox based on the Item handle.
424+
* It returns a list of ExportFormatDTO objects with the URL to the citation data.
425+
*/
426+
private List<ExportFormatDTO> buildExportFormats(Item item) {
427+
List<ExportFormatDTO> exportFormats = new ArrayList<>();
428+
String itemHandle = item.getHandle();
429+
if (itemHandle != null) {
430+
String baseUrl = configurationService.getProperty("dspace.server.url") +
431+
"/api/core/refbox/citations?handle=/" + Utils.getCanonicalHandleUrlNoProtocol(item);
432+
433+
String bibtexUrl = baseUrl + "&type=bibtex";
434+
String cmdiUrl = baseUrl + "&type=cmdi";
435+
436+
exportFormats.add(new ExportFormatDTO("bibtex", bibtexUrl, "json", ""));
437+
exportFormats.add(new ExportFormatDTO("cmdi", cmdiUrl, "json", ""));
438+
} else {
439+
log.error("Item with ID {} does not have a handle, export formats cannot be built.", item.getID());
440+
}
441+
return exportFormats;
442+
}
443+
444+
/**
445+
* Build the featured services for the RefBox based on the Item Metadata.
446+
* This method retrieves the metadata values for the featured services,
447+
* groups them by service name (qualifier),
448+
* and constructs a list of FeaturedServiceDTO objects
449+
* with the full name, URL, description, and links.
450+
*/
451+
private List<FeaturedServiceDTO> buildFeaturedServices(Context context, Item item) {
452+
List<MetadataValue> fsMeta = itemService.getMetadata(item, "local", "featuredService", "*", DEFAULT_LANGUAGE);
453+
Map<String, List<FeaturedServiceLinkDTO>> serviceLinksMap = new HashMap<>();
454+
455+
// Group links by service name (qualifier)
456+
for (MetadataValue mv : fsMeta) {
457+
String qualifier = mv.getMetadataField().getQualifier();
458+
if (qualifier == null) {
459+
continue;
460+
}
461+
String[] parts = mv.getValue().split("\\|");
462+
if (parts.length == 2) {
463+
serviceLinksMap
464+
.computeIfAbsent(qualifier, k -> new ArrayList<>())
465+
.add(new FeaturedServiceLinkDTO(parts[0], parts[1]));
466+
} else {
467+
log.error("Invalid metadata value format for featured service: {}. " +
468+
"Expected format is '<KEY>|<VALUE>'.", mv.getValue());
469+
}
470+
}
471+
472+
List<FeaturedServiceDTO> featuredServiceList = new ArrayList<>();
473+
// Iterate over the grouped service links and create FeaturedServiceDTO objects
474+
for (Map.Entry<String, List<FeaturedServiceLinkDTO>> entry : serviceLinksMap.entrySet()) {
475+
String name = entry.getKey();
476+
String fullname = configurationService.getProperty("featured.service." + name + ".fullname");
477+
String url = configurationService.getProperty("featured.service." + name + ".url");
478+
String description = configurationService.getProperty("featured.service." + name + ".description");
479+
Map<String, List<FeaturedServiceLinkDTO>> linksMap = new HashMap<>();
480+
linksMap.put("entry", entry.getValue());
481+
featuredServiceList.add(new FeaturedServiceDTO(
482+
fullname != null ? fullname : name,
483+
url != null ? url : "",
484+
description != null ? description : "",
485+
linksMap
486+
));
487+
}
488+
return featuredServiceList;
489+
}
490+
289491
private void closeContext(Context context) {
290492
if (Objects.nonNull(context) && context.isValid()) {
291493
context.abort();
@@ -422,20 +624,17 @@ public String toString() {
422624
* For better response parsing wrap the OAI data to the object.
423625
*/
424626
class OaiMetadataWrapper {
425-
private String metadata;
426-
427-
public OaiMetadataWrapper() {
428-
}
627+
private String value;
429628

430-
public OaiMetadataWrapper(String metadata) {
431-
this.metadata = metadata;
629+
public OaiMetadataWrapper(String value) {
630+
this.value = value;
432631
}
433632

434633
public String getMetadata() {
435-
return metadata;
634+
return value;
436635
}
437636

438-
public void setMetadata(String metadata) {
439-
this.metadata = metadata;
637+
public void setMetadata(String value) {
638+
this.value = value;
440639
}
441640
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* The contents of this file are subject to the license and copyright
3+
* detailed in the LICENSE and NOTICE files at the root of the source
4+
* tree and available online at
5+
*
6+
* http://www.dspace.org/license/
7+
*/
8+
package org.dspace.app.rest.model.refbox;
9+
10+
import java.io.Serializable;
11+
12+
/**
13+
* DTO for export formats in the reference box.
14+
* This class represents the export format details including its name, URL, data type, and extraction.
15+
* @author Milan Majchrak (dspace at dataquest.sk)
16+
*/
17+
public class ExportFormatDTO implements Serializable {
18+
private String name;
19+
private String url;
20+
private String dataType;
21+
private String extract;
22+
23+
public ExportFormatDTO(String name, String url, String dataType, String extract) {
24+
this.name = name;
25+
this.url = url;
26+
this.dataType = dataType;
27+
this.extract = extract;
28+
}
29+
30+
public String getName() {
31+
return name;
32+
}
33+
public void setName(String name) {
34+
this.name = name;
35+
}
36+
37+
public String getUrl() {
38+
return url;
39+
}
40+
public void setUrl(String url) {
41+
this.url = url;
42+
}
43+
44+
public String getDataType() {
45+
return dataType;
46+
}
47+
public void setDataType(String dataType) {
48+
this.dataType = dataType;
49+
}
50+
51+
public String getExtract() {
52+
return extract;
53+
}
54+
public void setExtract(String extract) {
55+
this.extract = extract;
56+
}
57+
}

0 commit comments

Comments
 (0)