diff --git a/dspace-api/src/main/java/org/dspace/health/LicenseCheck.java b/dspace-api/src/main/java/org/dspace/health/LicenseCheck.java new file mode 100644 index 000000000000..f668194afd13 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/health/LicenseCheck.java @@ -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 licensesCount = new HashMap<>(); + private Map> problemItems = new HashMap<>(); + + @Override + protected String run(ReportInfo ri) { + Context context = new Context(); + StringBuilder sb = new StringBuilder(); + + Iterator items; + ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + try { + items = itemService.findAll(context); + } catch (SQLException e) { + throw new RuntimeException("Error while fetching items. ", e); + } + + for (Iterator it = items; it.hasNext(); ) { + Item item = it.next(); + + List 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 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 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 result : licensesCount.entrySet()) { + sb.append(String.format("%-20s: %d\n", result.getKey(), result.getValue())); + } + + if (!problemItems.isEmpty()) { + for (Map.Entry> problemItems : problemItems.entrySet()) { + List 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(); + } +} diff --git a/dspace-api/src/test/java/org/dspace/scripts/HealthReportIT.java b/dspace-api/src/test/java/org/dspace/scripts/HealthReportIT.java index c4e732d49990..e77a907ef731 100644 --- a/dspace-api/src/test/java/org/dspace/scripts/HealthReportIT.java +++ b/dspace-api/src/test/java/org/dspace/scripts/HealthReportIT.java @@ -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 { @@ -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 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 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"))); + } +} \ No newline at end of file diff --git a/dspace/config/modules/healthcheck.cfg b/dspace/config/modules/healthcheck.cfg index 69300b2f0029..0352e88c5781 100644 --- a/dspace/config/modules/healthcheck.cfg +++ b/dspace/config/modules/healthcheck.cfg @@ -6,7 +6,8 @@ # 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,\ @@ -14,7 +15,8 @@ plugin.named.org.dspace.health.Check = \ 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