Skip to content

Commit 39654ff

Browse files
committed
refs #107 - added support for repeatable to bag profile conformance checker
1 parent 304d666 commit 39654ff

File tree

15 files changed

+131
-39
lines changed

15 files changed

+131
-39
lines changed

src/main/java/gov/loc/repository/bagit/conformance/BagLinter.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import gov.loc.repository.bagit.exceptions.conformance.BagitVersionIsNotAcceptableException;
3030
import gov.loc.repository.bagit.exceptions.conformance.FetchFileNotAllowedException;
3131
import gov.loc.repository.bagit.exceptions.conformance.MetatdataValueIsNotAcceptableException;
32+
import gov.loc.repository.bagit.exceptions.conformance.MetatdataValueIsNotRepeatableException;
3233
import gov.loc.repository.bagit.exceptions.conformance.RequiredManifestNotPresentException;
3334
import gov.loc.repository.bagit.exceptions.conformance.RequiredMetadataFieldNotPresentException;
3435
import gov.loc.repository.bagit.exceptions.conformance.RequiredTagFileNotPresentException;
@@ -62,14 +63,15 @@ private BagLinter(){
6263
*
6364
* @throws FetchFileNotAllowedException if there is a fetch file when the profile prohibits it
6465
* @throws MetatdataValueIsNotAcceptableException if a metadata value is not in the list of acceptable values
66+
* @throws MetatdataValueIsNotRepeatableException if a metadata value shows up more than once when not repeatable
6567
* @throws RequiredMetadataFieldNotPresentException if a metadata field is not present but it should be
6668
* @throws RequiredManifestNotPresentException if a payload or tag manifest type is not present but should be
6769
* @throws BagitVersionIsNotAcceptableException if the version of the bag is not in the list of acceptable versions
6870
* @throws RequiredTagFileNotPresentException if a tag file is not present but should be
6971
*/
7072
public static void checkAgainstProfile(final InputStream jsonProfile, final Bag bag) throws JsonParseException, JsonMappingException,
7173
IOException, FetchFileNotAllowedException, RequiredMetadataFieldNotPresentException, MetatdataValueIsNotAcceptableException, RequiredManifestNotPresentException,
72-
BagitVersionIsNotAcceptableException, RequiredTagFileNotPresentException{
74+
BagitVersionIsNotAcceptableException, RequiredTagFileNotPresentException, MetatdataValueIsNotRepeatableException{
7375
BagProfileChecker.bagConformsToProfile(jsonProfile, bag);
7476
}
7577

src/main/java/gov/loc/repository/bagit/conformance/BagProfileChecker.java

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import gov.loc.repository.bagit.exceptions.conformance.BagitVersionIsNotAcceptableException;
3131
import gov.loc.repository.bagit.exceptions.conformance.FetchFileNotAllowedException;
3232
import gov.loc.repository.bagit.exceptions.conformance.MetatdataValueIsNotAcceptableException;
33+
import gov.loc.repository.bagit.exceptions.conformance.MetatdataValueIsNotRepeatableException;
3334
import gov.loc.repository.bagit.exceptions.conformance.RequiredManifestNotPresentException;
3435
import gov.loc.repository.bagit.exceptions.conformance.RequiredMetadataFieldNotPresentException;
3536
import gov.loc.repository.bagit.exceptions.conformance.RequiredTagFileNotPresentException;
@@ -59,14 +60,15 @@ private BagProfileChecker(){
5960
*
6061
* @throws FetchFileNotAllowedException if there is a fetch file when the profile prohibits it
6162
* @throws MetatdataValueIsNotAcceptableException if a metadata value is not in the list of acceptable values
63+
* @throws MetatdataValueIsNotRepeatableException if a metadata value shows up more than once when not repeatable
6264
* @throws RequiredMetadataFieldNotPresentException if a metadata field is not present but it should be
6365
* @throws RequiredManifestNotPresentException if a payload or tag manifest type is not present but should be
6466
* @throws BagitVersionIsNotAcceptableException if the version of the bag is not in the list of acceptable versions
6567
* @throws RequiredTagFileNotPresentException if a tag file is not present but should be
6668
*/
6769
public static void bagConformsToProfile(final InputStream jsonProfile, final Bag bag) throws JsonParseException, JsonMappingException,
6870
IOException, FetchFileNotAllowedException, RequiredMetadataFieldNotPresentException, MetatdataValueIsNotAcceptableException,
69-
RequiredManifestNotPresentException, BagitVersionIsNotAcceptableException, RequiredTagFileNotPresentException{
71+
RequiredManifestNotPresentException, BagitVersionIsNotAcceptableException, RequiredTagFileNotPresentException, MetatdataValueIsNotRepeatableException{
7072

7173
final BagitProfile profile = parseBagitProfile(jsonProfile);
7274
checkFetch(bag.getRootDir(), profile.isFetchFileAllowed(), bag.getItemsToFetch());
@@ -101,30 +103,48 @@ private static void checkFetch(final Path rootDir, final boolean allowFetchFile,
101103
}
102104

103105
private static void checkMetadata(final Metadata bagMetadata, final Map<String, BagInfoRequirement> bagInfoEntryRequirements)
104-
throws RequiredMetadataFieldNotPresentException, MetatdataValueIsNotAcceptableException{
106+
throws RequiredMetadataFieldNotPresentException, MetatdataValueIsNotAcceptableException, MetatdataValueIsNotRepeatableException{
105107

106108
for(final Entry<String, BagInfoRequirement> bagInfoEntryRequirement : bagInfoEntryRequirements.entrySet()){
107109
final boolean metadataContainsKey = bagMetadata.contains(bagInfoEntryRequirement.getKey());
108110

109-
logger.debug(messages.getString("checking_metadata_entry_required"), bagInfoEntryRequirement.getKey());
110-
//is it required and not there?
111-
if(bagInfoEntryRequirement.getValue().isRequired() && !metadataContainsKey){
112-
throw new RequiredMetadataFieldNotPresentException(messages.getString("required_metadata_field_not_present_error"), bagInfoEntryRequirement.getKey());
113-
}
111+
checkIfMetadataEntryIsRequired(bagInfoEntryRequirement, metadataContainsKey);
112+
113+
checkForAcceptableValues(bagMetadata, bagInfoEntryRequirement);
114114

115-
//a size of zero implies that all values are acceptable
116-
if(!bagInfoEntryRequirement.getValue().getAcceptableValues().isEmpty()){
117-
logger.debug(messages.getString("check_values_acceptable"), bagInfoEntryRequirement.getKey());
118-
for(final String metadataValue : bagMetadata.get(bagInfoEntryRequirement.getKey())){
119-
if(!bagInfoEntryRequirement.getValue().getAcceptableValues().contains(metadataValue)){
120-
throw new MetatdataValueIsNotAcceptableException(messages.getString("metadata_value_not_acceptable_error"),
121-
bagInfoEntryRequirement.getKey(), bagInfoEntryRequirement.getValue().getAcceptableValues(), metadataValue);
122-
}
115+
checkForNoneRepeatableMetadata(bagMetadata, bagInfoEntryRequirement, metadataContainsKey);
116+
}
117+
}
118+
119+
private static void checkIfMetadataEntryIsRequired(final Entry<String, BagInfoRequirement> bagInfoEntryRequirement, final boolean metadataContainsKey) throws RequiredMetadataFieldNotPresentException{
120+
logger.debug(messages.getString("checking_metadata_entry_required"), bagInfoEntryRequirement.getKey());
121+
//is it required and not there?
122+
if(bagInfoEntryRequirement.getValue().isRequired() && !metadataContainsKey){
123+
throw new RequiredMetadataFieldNotPresentException(messages.getString("required_metadata_field_not_present_error"), bagInfoEntryRequirement.getKey());
124+
}
125+
}
126+
127+
private static void checkForAcceptableValues(final Metadata bagMetadata, final Entry<String, BagInfoRequirement> bagInfoEntryRequirement) throws MetatdataValueIsNotAcceptableException{
128+
//a size of zero implies that all values are acceptable
129+
if(!bagInfoEntryRequirement.getValue().getAcceptableValues().isEmpty()){
130+
logger.debug(messages.getString("check_values_acceptable"), bagInfoEntryRequirement.getKey());
131+
for(final String metadataValue : bagMetadata.get(bagInfoEntryRequirement.getKey())){
132+
if(!bagInfoEntryRequirement.getValue().getAcceptableValues().contains(metadataValue)){
133+
throw new MetatdataValueIsNotAcceptableException(messages.getString("metadata_value_not_acceptable_error"),
134+
bagInfoEntryRequirement.getKey(), bagInfoEntryRequirement.getValue().getAcceptableValues(), metadataValue);
123135
}
124136
}
125137
}
126138
}
127139

140+
private static void checkForNoneRepeatableMetadata(final Metadata bagMetadata, final Entry<String, BagInfoRequirement> bagInfoEntryRequirement, final boolean metadataContainsKey) throws MetatdataValueIsNotRepeatableException{
141+
//if it is none repeatable, but shows up multiple times
142+
if(!bagInfoEntryRequirement.getValue().isRepeatable() && metadataContainsKey
143+
&& bagMetadata.get(bagInfoEntryRequirement.getKey()).size() > 1){
144+
throw new MetatdataValueIsNotRepeatableException(messages.getString("metadata_value_not_repeatable_error"), bagInfoEntryRequirement.getKey());
145+
}
146+
}
147+
128148
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
129149
private static void requiredManifestsExist(final Set<Manifest> manifests, final List<String> requiredManifestTypes, final boolean isPayloadManifest) throws RequiredManifestNotPresentException{
130150
final Set<String> manifestTypesPresent = new HashSet<>();

src/main/java/gov/loc/repository/bagit/conformance/profile/BagInfoRequirement.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
public class BagInfoRequirement {
1111
private boolean required;
1212
private List<String> acceptableValues = new ArrayList<>();
13+
private boolean repeatable;
1314

1415
@Override
1516
public boolean equals(final Object other) {
@@ -18,12 +19,13 @@ public boolean equals(final Object other) {
1819
}
1920
final BagInfoRequirement castOther = (BagInfoRequirement) other;
2021
return Objects.equals(required, castOther.required)
21-
&& Objects.equals(acceptableValues, castOther.acceptableValues);
22+
&& Objects.equals(acceptableValues, castOther.acceptableValues)
23+
&& Objects.equals(repeatable, castOther.repeatable);
2224
}
2325

2426
@Override
2527
public int hashCode() {
26-
return Objects.hash(required, acceptableValues);
28+
return Objects.hash(required, acceptableValues, repeatable);
2729
}
2830

2931
public BagInfoRequirement(){
@@ -37,7 +39,7 @@ public BagInfoRequirement(final boolean required, final List<String> acceptableV
3739

3840
@Override
3941
public String toString() {
40-
return "[required=" + required + ", acceptableValues=" + acceptableValues + "]";
42+
return "[required=" + required + ", acceptableValues=" + acceptableValues + ", repeatable=" + repeatable + "]";
4143
}
4244

4345
public boolean isRequired() {
@@ -52,4 +54,10 @@ public List<String> getAcceptableValues() {
5254
public void setAcceptableValues(final List<String> acceptableValues) {
5355
this.acceptableValues = acceptableValues;
5456
}
57+
public boolean isRepeatable() {
58+
return repeatable;
59+
}
60+
public void setRepeatable(final boolean repeatable) {
61+
this.repeatable = repeatable;
62+
}
5563
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package gov.loc.repository.bagit.exceptions.conformance;
2+
3+
import org.slf4j.helpers.MessageFormatter;
4+
5+
/**
6+
* Class to represent when a metadata's value is not to be repeated
7+
*/
8+
public class MetatdataValueIsNotRepeatableException extends Exception {
9+
private static final long serialVersionUID = 1L;
10+
11+
public MetatdataValueIsNotRepeatableException(final String message, final String metadataKey) {
12+
super(MessageFormatter.format(message, metadataKey).getMessage());
13+
}
14+
}

src/main/resources/MessageBundle.properties

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,16 @@ bagit_version_not_acceptable_error=Version [{}] is not in the acceptable list of
5757
required_metadata_field_not_present_error=Profile specifies metadata field [{}] is required but was not found!
5858

5959
#for FetchFileNotAllowedException.java
60-
fetch_file_not_allowed_error=Fetch File was found in bag [{}]
60+
fetch_file_not_allowed_error=Fetch File was found in bag [{}]!
6161

62-
#for MetadataBalueIsNotAcceptableException.java
63-
metadata_value_not_acceptable_error=Profile specifies that acceptable values for [{}] are {} but found [{}]
62+
#for MetadataValueIsNotAcceptableException.java
63+
metadata_value_not_acceptable_error=Profile specifies that acceptable values for [{}] are {} but found [{}]!
64+
65+
#for MetadataValueIsNotRepeatableException.java
66+
metadata_value_not_repeatable_error=Profile specifies that value [{}] is not repeatable, but was listed multiple times!
6467

6568
#for RequiredTagFileNotPresentException.java
66-
required_tag_file_not_found_error=Required tag file [{}] was not found
69+
required_tag_file_not_found_error=Required tag file [{}] was not found!
6770

6871
#for EncodingChecker.java
6972
tag_files_not_encoded_with_utf8_warning=Tag files are encoded with [{}]. We recommend always using UTF-8 instead.

src/test/java/gov/loc/repository/bagit/conformance/BagProfileCheckerTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import gov.loc.repository.bagit.exceptions.conformance.BagitVersionIsNotAcceptableException;
1515
import gov.loc.repository.bagit.exceptions.conformance.FetchFileNotAllowedException;
1616
import gov.loc.repository.bagit.exceptions.conformance.MetatdataValueIsNotAcceptableException;
17+
import gov.loc.repository.bagit.exceptions.conformance.MetatdataValueIsNotRepeatableException;
1718
import gov.loc.repository.bagit.exceptions.conformance.RequiredManifestNotPresentException;
1819
import gov.loc.repository.bagit.exceptions.conformance.RequiredMetadataFieldNotPresentException;
1920
import gov.loc.repository.bagit.exceptions.conformance.RequiredTagFileNotPresentException;
@@ -68,6 +69,16 @@ public void testMetatdataValueIsNotAcceptableException() throws Exception{
6869
}
6970
}
7071

72+
@Test(expected=MetatdataValueIsNotRepeatableException.class)
73+
public void testMetadataValueIsNotRepeatableException() throws Exception{
74+
Path bagRootPath = new File("src/test/resources/bagitProfileTestBags/repeatedMetadataBag").toPath();
75+
Bag bag = reader.read(bagRootPath);
76+
77+
try(InputStream inputStream = Files.newInputStream(profileJson, StandardOpenOption.READ)){
78+
BagProfileChecker.bagConformsToProfile(inputStream, bag);
79+
}
80+
}
81+
7182
@Test(expected=RequiredManifestNotPresentException.class)
7283
public void testRequiredPayloadManifestNotPresentException() throws Exception{
7384
Path bagRootPath = new File("src/test/resources/bagitProfileTestBags/missingRequiredPayloadManifestBag").toPath();

src/test/java/gov/loc/repository/bagit/conformance/profile/BagitProfileTest.java

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,36 @@ public class BagitProfileTest extends AbstractBagitProfileTest{
1111
@Test
1212
public void testToString() throws Exception{
1313
String expectedOutput = "BagitProfile [bagitProfileIdentifier=http://canadiana.org/standards/bagit/tdr_ingest.json, "
14-
+ "sourceOrganization=Candiana.org, externalDescription=BagIt profile for ingesting content into the C.O. TDR "
15-
+ "loading dock., contactName=William Wueppelmann, contactEmail=tdr@canadiana.com, version=1.2, "
16-
+ "bagInfoRequirements={Payload-Oxum=[required=true, acceptableValues=[]], Bag-Size=[required=true, "
17-
+ "acceptableValues=[]], Bagging-Date=[required=true, acceptableValues=[]], Source-Organization=[required=true, "
18-
+ "acceptableValues=[Simon Fraser University, York University]], Bag-Count=[required=true, acceptableValues=[]], "
19-
+ "Organization-Address=[required=true, acceptableValues=[8888 University Drive Burnaby, B.C. V5A 1S6 Canada, "
20-
+ "4700 Keele Street Toronto, Ontario M3J 1P3 Canada]], Bag-Group-Identifier=[required=false, "
21-
+ "acceptableValues=[]], External-Identifier=[required=false, acceptableValues=[]], "
22-
+ "Internal-Sender-Identifier=[required=false, acceptableValues=[]], Contact-Email=[required=true, "
23-
+ "acceptableValues=[]], Contact-Phone=[required=false, acceptableValues=[]], "
24-
+ "Internal-Sender-Description=[required=false, acceptableValues=[]], External-Description=[required=true, "
25-
+ "acceptableValues=[]], Contact-Name=[required=true, acceptableValues=[Mark Jordan, Nick Ruest]]}, "
26-
+ "manifestTypesRequired=[md5], fetchFileAllowed=false, serialization=forbidden, "
27-
+ "acceptableMIMESerializationTypes=[application/zip], acceptableBagitVersions=[0.96], "
28-
+ "tagManifestTypesRequired=[md5], tagFilesRequired=[DPN/dpnFirstNode.txt, DPN/dpnRegistry]]";
14+
+ "sourceOrganization=Candiana.org, "
15+
+ "externalDescription=BagIt profile for ingesting content into the C.O. TDR loading dock., "
16+
+ "contactName=William Wueppelmann, "
17+
+ "contactEmail=tdr@canadiana.com, "
18+
+ "version=1.2, "
19+
+ "bagInfoRequirements={"
20+
+ "Payload-Oxum=[required=true, acceptableValues=[], repeatable=false], "
21+
+ "Bag-Size=[required=true, acceptableValues=[], repeatable=false], "
22+
+ "Bagging-Date=[required=true, acceptableValues=[], repeatable=false], "
23+
+ "Source-Organization=[required=true, acceptableValues=[Simon Fraser University, York University], repeatable=false], "
24+
+ "Bag-Count=[required=true, acceptableValues=[], repeatable=false], "
25+
+ "Organization-Address=[required=true, acceptableValues=[8888 University Drive Burnaby, B.C. V5A 1S6 Canada, 4700 Keele Street Toronto, Ontario M3J 1P3 Canada], repeatable=false], "
26+
+ "Bag-Group-Identifier=[required=false, acceptableValues=[], repeatable=false], "
27+
+ "External-Identifier=[required=false, acceptableValues=[], repeatable=false], "
28+
+ "Internal-Sender-Identifier=[required=false, acceptableValues=[], repeatable=false], "
29+
+ "Contact-Email=[required=true, acceptableValues=[], repeatable=false], "
30+
+ "Contact-Phone=[required=false, acceptableValues=[], repeatable=false], "
31+
+ "Internal-Sender-Description=[required=false, acceptableValues=[], repeatable=false], "
32+
+ "External-Description=[required=true, acceptableValues=[], repeatable=false], "
33+
+ "Contact-Name=[required=true, acceptableValues=[Mark Jordan, Nick Ruest], repeatable=false]}, "
34+
+ "manifestTypesRequired=[md5], "
35+
+ "fetchFileAllowed=false, "
36+
+ "serialization=forbidden, "
37+
+ "acceptableMIMESerializationTypes=[application/zip], "
38+
+ "acceptableBagitVersions=[0.96], "
39+
+ "tagManifestTypesRequired=[md5], "
40+
+ "tagFilesRequired=[DPN/dpnFirstNode.txt, DPN/dpnRegistry]]";
2941

3042
BagitProfile profile = mapper.readValue(new File("src/test/resources/bagitProfiles/exampleProfile.json"), BagitProfile.class);
43+
System.err.println(profile.toString());
3144
assertEquals(expectedOutput, profile.toString());
3245
}
3346

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
foo
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bar
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Bag-Software-Agent: bagit.py v1.5.4 <http://github.com/libraryofcongress/bagit-python>
2+
Bagging-Date: 2017-01-30
3+
Bagging-Date: 2017-01-30
4+
Payload-Oxum: 6.1
5+
Source-Organization: York University
6+
Organization-Address: 4700 Keele Street Toronto, Ontario M3J 1P3 Canada
7+
Contact-Name: Nick Ruest
8+
Contact-Email: foo@foo.com
9+
External-Description: description here
10+
Bag-Size: 6kb
11+
Bag-Count: 1 of 1

0 commit comments

Comments
 (0)