Skip to content

Commit 5c75c86

Browse files
authored
Merge pull request DSpace#10554 from atmire/feature-bitstream-linking-9.0
Bitstream tracking for replacements and copies
2 parents 1d4c6e1 + 6ce8048 commit 5c75c86

34 files changed

Lines changed: 3335 additions & 1070 deletions

dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.stream.Collectors;
2121

2222
import org.apache.commons.lang3.Strings;
23+
import org.apache.commons.lang3.tuple.Pair;
2324
import org.dspace.app.mediafilter.service.MediaFilterService;
2425
import org.dspace.authorize.AuthorizeException;
2526
import org.dspace.authorize.service.AuthorizeService;
@@ -329,23 +330,9 @@ public boolean processBitstream(Context context, Item item, Bitstream source, Fo
329330
String newName = formatFilter.getFilteredName(source.getName());
330331

331332
// check if destination bitstream exists
332-
Bundle existingBundle = null;
333-
List<Bitstream> existingBitstreams = new ArrayList<>();
334-
List<Bundle> bundles = itemService.getBundles(item, formatFilter.getBundleName());
335-
336-
if (!bundles.isEmpty()) {
337-
// only finds the last matching bundle and all matching bitstreams in the proper bundle(s)
338-
for (Bundle bundle : bundles) {
339-
List<Bitstream> bitstreams = bundle.getBitstreams();
340-
341-
for (Bitstream bitstream : bitstreams) {
342-
if (bitstream.getName().trim().equals(newName.trim())) {
343-
existingBundle = bundle;
344-
existingBitstreams.add(bitstream);
345-
}
346-
}
347-
}
348-
}
333+
Pair<List<Bitstream>, Bundle> bitstreamsAndBundle = getBitstreamsDerivedFromFilter(item, source, formatFilter);
334+
List<Bitstream> existingBitstreams = bitstreamsAndBundle.getLeft();
335+
Bundle existingBundle = bitstreamsAndBundle.getRight();
349336

350337
// if exists and overwrite = false, exit
351338
if (!overWrite && (!existingBitstreams.isEmpty())) {
@@ -380,6 +367,7 @@ public boolean processBitstream(Context context, Item item, Bitstream source, Fo
380367
return false;
381368
}
382369

370+
List<Bundle> bundles = itemService.getBundles(item, formatFilter.getBundleName());
383371
Bundle targetBundle; // bundle we're modifying
384372
if (bundles.isEmpty()) {
385373
// create new bundle if needed
@@ -426,6 +414,28 @@ public boolean processBitstream(Context context, Item item, Bitstream source, Fo
426414
return true;
427415
}
428416

417+
@Override
418+
public Pair<List<Bitstream>, Bundle> getBitstreamsDerivedFromFilter(Item item, Bitstream sourceBitstream,
419+
FormatFilter formatFilter) throws Exception {
420+
Bundle lastBundle = null;
421+
String filteredName = formatFilter.getFilteredName(sourceBitstream.getName());
422+
List<Bitstream> derivedBitstreams = new ArrayList<>();
423+
List<Bundle> bundleList = itemService.getBundles(item, formatFilter.getBundleName());
424+
if (!bundleList.isEmpty()) {
425+
// only finds the last matching bundle and all matching bitstreams in the proper bundle(s)
426+
for (Bundle bundle : bundleList) {
427+
List<Bitstream> bitstreamList = bundle.getBitstreams();
428+
for (Bitstream bitstream : bitstreamList) {
429+
if (bitstream.getName().trim().equals(filteredName.trim())) {
430+
lastBundle = bundle;
431+
derivedBitstreams.add(bitstream);
432+
}
433+
}
434+
}
435+
}
436+
return Pair.of(derivedBitstreams, lastBundle);
437+
}
438+
429439
@Override
430440
public void updatePoliciesOfDerivativeBitstreams(Context context, Item item, Bitstream source)
431441
throws SQLException, AuthorizeException {

dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
import java.util.List;
1313
import java.util.Map;
1414

15+
import org.apache.commons.lang3.tuple.Pair;
1516
import org.dspace.app.mediafilter.FormatFilter;
1617
import org.dspace.authorize.AuthorizeException;
1718
import org.dspace.content.Bitstream;
19+
import org.dspace.content.Bundle;
1820
import org.dspace.content.Collection;
1921
import org.dspace.content.Community;
2022
import org.dspace.content.Item;
@@ -94,6 +96,19 @@ public void applyFiltersCollection(Context context, Collection collection)
9496
public boolean processBitstream(Context context, Item item, Bitstream source, FormatFilter formatFilter)
9597
throws Exception;
9698

99+
/**
100+
* Returns all {@code item}'s bitstreams that are derived from {@code formatFilter}.
101+
*
102+
* @param item
103+
* @param sourceBitstream
104+
* @param formatFilter
105+
* @return
106+
* @throws Exception
107+
*/
108+
Pair<List<Bitstream>, Bundle> getBitstreamsDerivedFromFilter(Item item, Bitstream sourceBitstream,
109+
FormatFilter formatFilter)
110+
throws Exception;
111+
97112
/**
98113
* update resource polices of derivative bitstreams
99114
* related to source bitstream.
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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.content;
9+
10+
import java.sql.SQLException;
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
import java.util.Objects;
14+
import java.util.UUID;
15+
16+
import org.apache.logging.log4j.Logger;
17+
import org.dspace.authorize.AuthorizeException;
18+
import org.dspace.content.authority.Choices;
19+
import org.dspace.content.service.BitstreamLinkingService;
20+
import org.dspace.content.service.BitstreamService;
21+
import org.dspace.core.Context;
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
24+
/**
25+
* Service implementation for the BitstreamLinkingService
26+
* This class is responsible for providing metadata to bitstreams that are cloned in order to track which bitstreams
27+
* are copies, original, replacement, or replaced by.
28+
*
29+
* @author Nathan Buckingham at atmire.com
30+
*/
31+
public class BitstreamLinkingServiceImpl implements BitstreamLinkingService {
32+
33+
@Autowired
34+
BitstreamService bitstreamService;
35+
36+
public static final String DSPACE = "dspace";
37+
public static final String BITSTREAM = "bitstream";
38+
public static final String HAS_COPIES = "hasCopies";
39+
public static final String IS_COPY_OF = "isCopyOf";
40+
public static final String IS_REPLACED_BY = "isReplacedBy";
41+
public static final String IS_REPLACEMENT_OF = "isReplacementOf";
42+
43+
/**
44+
* log4j logger
45+
*/
46+
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger();
47+
48+
@Override
49+
public void cloneMetadata(Context context, Bitstream bitstream, Bitstream clone) throws SQLException,
50+
AuthorizeException {
51+
skipBitstreamMetadataThenAdd(context, bitstream, clone);
52+
registerBitstreams(context, bitstream, clone);
53+
}
54+
55+
@Override
56+
public void replaceMetadata(
57+
Context context, Bitstream bitstream, Bitstream replacedBy, String newName
58+
) throws SQLException, AuthorizeException {
59+
skipBitstreamMetadataThenAdd(context, bitstream, replacedBy);
60+
replacedBy.setName(context, newName);
61+
registerReplacementBitstream(context, bitstream, replacedBy);
62+
}
63+
64+
private String getName(Bitstream bitstream) {
65+
String name = bitstreamService.getName(bitstream);
66+
// we need a text_value, otherwise the metadata value won't be added
67+
return Objects.requireNonNullElse(name, "<unnamed>");
68+
}
69+
70+
@Override
71+
public void registerBitstreams(Context context, Bitstream oldCopy,
72+
Bitstream newCopy) throws SQLException, AuthorizeException {
73+
bitstreamService.addMetadata(context, oldCopy, DSPACE, BITSTREAM,
74+
HAS_COPIES, null, getName(newCopy), newCopy.getID().toString(), Choices.CF_ACCEPTED);
75+
bitstreamService.addMetadata(context, newCopy, DSPACE, BITSTREAM,
76+
IS_COPY_OF, null, getName(oldCopy), oldCopy.getID().toString(), Choices.CF_ACCEPTED);
77+
bitstreamService.update(context, oldCopy);
78+
}
79+
80+
@Override
81+
public void registerReplacementBitstream(Context context, Bitstream oldCopy,
82+
Bitstream replacementCopy) throws SQLException {
83+
bitstreamService.addMetadata(context, oldCopy, DSPACE, BITSTREAM, IS_REPLACED_BY, null,
84+
getName(replacementCopy), replacementCopy.getID().toString(), Choices.CF_ACCEPTED);
85+
bitstreamService.addMetadata(context, replacementCopy, DSPACE, BITSTREAM, IS_REPLACEMENT_OF,
86+
null, getName(oldCopy), oldCopy.getID().toString(), Choices.CF_ACCEPTED);
87+
}
88+
89+
@Override
90+
public List<Bitstream> getCopies(Context context, Bitstream bitstream) throws SQLException {
91+
return getRelatedBitstreams(context, bitstream, HAS_COPIES);
92+
}
93+
94+
@Override
95+
public Bitstream getOriginal(Context context, Bitstream bitstream) throws SQLException {
96+
return getRelatedBitstream(context, bitstream, IS_COPY_OF);
97+
}
98+
99+
@Override
100+
public List<Bitstream> getReplacements(Context context, Bitstream bitstream) throws SQLException {
101+
return getRelatedBitstreams(context, bitstream, IS_REPLACED_BY);
102+
}
103+
104+
@Override
105+
public Bitstream getOriginalReplacement(Context context, Bitstream bitstream) throws SQLException {
106+
return getRelatedBitstream(context, bitstream, IS_REPLACEMENT_OF);
107+
}
108+
109+
/**
110+
* Gets the array of related bitstreams to the metadata given by qualifier.
111+
*
112+
* @param context Context
113+
* @param bitstream The bitstream to search from
114+
* @param qualifier The qualifier of the metadata key to search upon
115+
* @return The array of related bitstreams
116+
* @throws SQLException If bitstreamService.fine() fails to access the database
117+
*/
118+
private List<Bitstream> getRelatedBitstreamArray(Context context, Bitstream bitstream, String qualifier)
119+
throws SQLException {
120+
List<Bitstream> bitstreams = new ArrayList<>();
121+
List<MetadataValue> metadataValues = bitstreamService
122+
.getMetadata(bitstream, DSPACE, BITSTREAM, qualifier, null);
123+
for (MetadataValue metadataValue : metadataValues) {
124+
String authority = metadataValue.getAuthority();
125+
if (authority != null && !authority.isBlank()) {
126+
bitstreams.add(bitstreamService.find(context, UUID.fromString(authority)));
127+
}
128+
}
129+
return bitstreams;
130+
}
131+
132+
/**
133+
* Inner function that is used to get all related bitstreams according to a specific metadataField
134+
*
135+
* @param context Context
136+
* @param bitstream The bitstream to search from
137+
* @param qualifier The qualifier of the metadata key to search upon
138+
*
139+
* @return List<Bitstream> of bitstreams that were found using the uuids found in the given metadatafield.
140+
* @throws SQLException If bitstreamService.find() fails to access the database
141+
*/
142+
protected List<Bitstream> getRelatedBitstreams(Context context, Bitstream bitstream,
143+
String qualifier) throws SQLException {
144+
return getRelatedBitstreamArray(context, bitstream, qualifier);
145+
}
146+
147+
/**
148+
* Inner function that is used to get one related bitstreams according to a specific metadataField
149+
*
150+
* @param context Context
151+
* @param bitstream The bitstream to search from
152+
* @param qualifier The qualifier of the metadata key to search upon
153+
*
154+
* @return The bitstream that was found using the uuids found in the given metadatafield.
155+
* @throws SQLException If bitstreamService.find() fails to access the database
156+
*/
157+
protected Bitstream getRelatedBitstream(Context context, Bitstream bitstream,
158+
String qualifier) throws SQLException {
159+
List<Bitstream> bitstreams = getRelatedBitstreamArray(context, bitstream, qualifier);
160+
if (bitstreams.size() > 1) {
161+
log.warn("Related bitstream for: " + qualifier + " should only contain one bitstream, database " +
162+
"errors may be present if this is the case");
163+
}
164+
return !bitstreams.isEmpty() ? bitstreams.get(0) : null;
165+
}
166+
167+
/**
168+
* Clones the metadata to the new bitstream without taking any bitstream linking data with it
169+
*
170+
* @param context Dspace Context
171+
* @param bitstream DSpace original Bitstream
172+
* @param clone Dspace
173+
* @throws SQLException
174+
*/
175+
private void skipBitstreamMetadataThenAdd(Context context, Bitstream bitstream, Bitstream clone)
176+
throws SQLException {
177+
List<MetadataValue> metadataValues = bitstreamService.getMetadata(bitstream, Item.ANY, Item.ANY, Item.ANY,
178+
Item.ANY);
179+
180+
for (MetadataValue metadataValue : metadataValues) {
181+
if (metadataValue.getMetadataField().toString().equals(DSPACE + "_" + BITSTREAM + "_" + HAS_COPIES) ||
182+
metadataValue.getMetadataField().toString().equals(DSPACE + "_" + BITSTREAM + "_" + IS_COPY_OF) ||
183+
metadataValue.getMetadataField().toString().equals(DSPACE + "_" + BITSTREAM + "_" + IS_REPLACED_BY) ||
184+
metadataValue.getMetadataField().toString().equals(DSPACE + "_" + BITSTREAM + "_" + IS_REPLACEMENT_OF)
185+
) {
186+
continue;
187+
}
188+
bitstreamService.addMetadata(context, clone, metadataValue.getMetadataField(),
189+
metadataValue.getLanguage(), metadataValue.getValue(), metadataValue.getAuthority(),
190+
metadataValue.getConfidence());
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)