Skip to content
Merged
129 changes: 129 additions & 0 deletions dspace-api/src/main/java/org/dspace/health/LicenseCheck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/**
* 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.health;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

import com.amazonaws.util.CollectionUtils;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.Item;
import org.dspace.content.clarin.ClarinLicenseLabel;
import org.dspace.content.clarin.ClarinLicenseResourceMapping;
import org.dspace.content.factory.ClarinServiceFactory;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
import org.dspace.content.service.clarin.ClarinLicenseResourceMappingService;
import org.dspace.core.Constants;
import org.dspace.core.Context;

/**
* This check provides information about the number of items categorized by clarin license type (PUB/RES/ACA),
* as well as details about items that are missing bundles, bitstreams, or license mappings.
* @author Matus Kasak (dspace at dataquest.sk)
*/
public class LicenseCheck extends Check {
private ClarinLicenseResourceMappingService clarinLicenseResourceMappingService =
ClarinServiceFactory.getInstance().getClarinLicenseResourceMappingService();

private Map<String, Integer> licensesCount = new HashMap<>();
private Map<String, List<UUID>> problemItems = new HashMap<>();

@Override
protected String run(ReportInfo ri) {
Context context = new Context();
StringBuilder sb = new StringBuilder();

Iterator<Item> items;
ItemService itemService = ContentServiceFactory.getInstance().getItemService();
try {
items = itemService.findAll(context);
} catch (SQLException e) {
throw new RuntimeException("Error while fetching items. ", e);
}

for (Iterator<Item> it = items; it.hasNext(); ) {
Item item = it.next();

List<Bundle> bundles = item.getBundles(Constants.DEFAULT_BUNDLE_NAME);
if (bundles.isEmpty()) {
licensesCount.put("no bundle", licensesCount.getOrDefault("no bundle", 0) + 1);
continue;
}

if (item.getBundles(Constants.LICENSE_BUNDLE_NAME).isEmpty()) {
problemItems.computeIfAbsent(
"UUIDs of items without license bundle", k -> new ArrayList<>()).add(item.getID());
}

List<Bitstream> bitstreams = bundles.get(0).getBitstreams();
if (bitstreams.isEmpty()) {
problemItems.computeIfAbsent(
"UUIDs of items without bitstreams", k -> new ArrayList<>()).add(item.getID());
continue;
}

// one bitstream is enough as there is only one license for all bitstreams in item
Bitstream firstBitstream = bitstreams.get(0);
UUID uuid = firstBitstream.getID();
try {
List<ClarinLicenseResourceMapping> clarinLicenseResourceMappingList =
clarinLicenseResourceMappingService.findByBitstreamUUID(context, uuid);

if (CollectionUtils.isNullOrEmpty(clarinLicenseResourceMappingList)) {
log.error("No license mapping found for bitstream with uuid {}", uuid);
problemItems.computeIfAbsent(
"UUIDs of bitstreams without license mappings", k -> new ArrayList<>()).add(uuid);
continue;
}

// Every resource mapping between license and the bitstream has only one record,
// because the bitstream has unique UUID, so get the first record from the List
ClarinLicenseResourceMapping clarinLicenseResourceMapping = clarinLicenseResourceMappingList.get(0);

ClarinLicenseLabel nonExtendedLabel =
clarinLicenseResourceMapping.getLicense().getNonExtendedClarinLicenseLabel();

if (Objects.isNull(nonExtendedLabel)) {
log.error("Item {} with id {} does not have non extended license label.",
item.getName(), item.getID());
} else {
licensesCount.put(nonExtendedLabel.getLabel(),
licensesCount.getOrDefault(nonExtendedLabel.getLabel(), 0) + 1);
}
} catch (SQLException e) {
throw new RuntimeException("Error while fetching ClarinLicenseResourceMapping by Bitstream UUID: " +
uuid, e);
}
}

for (Map.Entry<String, Integer> result : licensesCount.entrySet()) {
sb.append(String.format("%-20s: %d\n", result.getKey(), result.getValue()));
}

if (!problemItems.isEmpty()) {
for (Map.Entry<String, List<UUID>> problemItems : problemItems.entrySet()) {
List<UUID> uuids = problemItems.getValue();
sb.append(String.format("\n%s: %d\n", problemItems.getKey(), uuids.size()));
for (UUID uuid : uuids) {
sb.append(String.format(" %s\n", uuid));
}
}
}

context.close();
return sb.toString();
}
}
101 changes: 100 additions & 1 deletion dspace-api/src/test/java/org/dspace/scripts/HealthReportIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,48 @@
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.dspace.AbstractIntegrationTestWithDatabase;
import org.dspace.app.launcher.ScriptLauncher;
import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.content.clarin.ClarinLicense;
import org.dspace.content.clarin.ClarinLicenseLabel;
import org.dspace.content.clarin.ClarinLicenseResourceMapping;
import org.dspace.content.factory.ClarinServiceFactory;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.BitstreamService;
import org.dspace.content.service.BundleService;
import org.dspace.content.service.clarin.ClarinLicenseLabelService;
import org.dspace.content.service.clarin.ClarinLicenseResourceMappingService;
import org.dspace.content.service.clarin.ClarinLicenseService;
import org.dspace.core.Constants;
import org.junit.Test;

/**
* Integration test for the HealthReport script
* @author Milan Majchrak (milan.majchrak at dataquest.sk)
* @author Matus Kasak (dspace at dataquest.sk)
*/
public class HealthReportIT extends AbstractIntegrationTestWithDatabase {
private static final String PUB_LABEL = "PUB";
private static final String PUB_LICENSE_NAME = "Public Domain Mark (PUB)";
private static final String PUB_LICENSE_URL = "https://creativecommons.org/publicdomain/mark/1.0/";
private static final String LICENSE_TEXT = "This is a PUB License.";

@Test
public void testDefaultHealthcheckRun() throws Exception {

Expand All @@ -40,4 +70,73 @@ public void testDefaultHealthcheckRun() throws Exception {
assertThat(messages, hasSize(1));
assertThat(messages, hasItem(containsString("HEALTH REPORT:")));
}
}

@Test
public void testLicenseCheck() throws Exception {
context.turnOffAuthorisationSystem();

Community community = CommunityBuilder.createCommunity(context)
.withName("Community")
.build();

Collection collection = CollectionBuilder.createCollection(context, community)
.withName("Collection")
.withSubmitterGroup(eperson)
.build();

Item itemPUB = ItemBuilder.createItem(context, collection)
.withTitle("Test item with Bitstream")
.build();

ItemBuilder.createItem(context, collection)
.withTitle("Test item without Bitstream")
.build();

BundleService bundleService = ContentServiceFactory.getInstance().getBundleService();
BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService();
ClarinLicenseService clarinLicenseService = ClarinServiceFactory.getInstance().getClarinLicenseService();
ClarinLicenseLabelService clarinLicenseLabelService =
ClarinServiceFactory.getInstance().getClarinLicenseLabelService();
ClarinLicenseResourceMappingService clarinLicenseResourceMappingService =
ClarinServiceFactory.getInstance().getClarinLicenseResourceMappingService();

Bundle bundle = bundleService.create(context, itemPUB, Constants.DEFAULT_BUNDLE_NAME);
InputStream inputStream = new ByteArrayInputStream(LICENSE_TEXT.getBytes(StandardCharsets.UTF_8));

Bitstream bitstream = bitstreamService.create(context, bundle, inputStream);

ClarinLicenseLabel clarinLicenseLabel = clarinLicenseLabelService.create(context);
clarinLicenseLabel.setLabel(PUB_LABEL);
clarinLicenseLabelService.update(context, clarinLicenseLabel);

ClarinLicense clarinLicense = clarinLicenseService.create(context);
clarinLicense.setName(PUB_LICENSE_NAME);
clarinLicense.setDefinition(PUB_LICENSE_URL);

Set<ClarinLicenseLabel> licenseLabels = new HashSet<>();
licenseLabels.add(clarinLicenseLabel);
clarinLicense.setLicenseLabels(licenseLabels);
clarinLicenseService.update(context, clarinLicense);

ClarinLicenseResourceMapping mapping = clarinLicenseResourceMappingService.create(context);
mapping.setBitstream(bitstream);
mapping.setLicense(clarinLicense);

clarinLicenseResourceMappingService.update(context, mapping);
bitstreamService.update(context, bitstream);
bundleService.update(context, bundle);
context.commit();

TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler();
// -c 3 run only third check, in this case License check
String[] args = new String[] { "health-report", "-c", "3" };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl);

assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty());
List<String> messages = testDSpaceRunnableHandler.getInfoMessages();
assertThat(messages, hasSize(1));
assertThat(messages, hasItem(containsString("no bundle")));
assertThat(messages, hasItem(containsString("UUIDs of items without license bundle:")));
assertThat(messages, hasItem(containsString("PUB")));
}
}
6 changes: 4 additions & 2 deletions dspace/config/modules/healthcheck.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@
# add 'Embargo items (Pre-3.0),' to the following list.
healthcheck.checks = General Information,\
Item summary,\
User summary
User summary,\
License summary

plugin.named.org.dspace.health.Check = \
org.dspace.health.InfoCheck = General Information,\
org.dspace.health.ChecksumCheck = Checksum,\
org.dspace.health.EmbargoCheck = Embargo items (Pre-3.0),\
org.dspace.health.ItemCheck = Item summary,\
org.dspace.health.UserCheck = User summary,\
org.dspace.health.LogAnalyserCheck = Log Analyser Check
org.dspace.health.LogAnalyserCheck = Log Analyser Check,\
org.dspace.health.LicenseCheck = License summary

# default value of the report from the last N days (where dates are applicable)
healthcheck.last_n_days = 7