Skip to content

Commit

Permalink
refs #107 - added support for repeatable to bag profile conformance c…
Browse files Browse the repository at this point in the history
…hecker
  • Loading branch information
johnscancella committed Feb 6, 2018
1 parent 304d666 commit 39654ff
Show file tree
Hide file tree
Showing 15 changed files with 131 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import gov.loc.repository.bagit.exceptions.conformance.BagitVersionIsNotAcceptableException;
import gov.loc.repository.bagit.exceptions.conformance.FetchFileNotAllowedException;
import gov.loc.repository.bagit.exceptions.conformance.MetatdataValueIsNotAcceptableException;
import gov.loc.repository.bagit.exceptions.conformance.MetatdataValueIsNotRepeatableException;
import gov.loc.repository.bagit.exceptions.conformance.RequiredManifestNotPresentException;
import gov.loc.repository.bagit.exceptions.conformance.RequiredMetadataFieldNotPresentException;
import gov.loc.repository.bagit.exceptions.conformance.RequiredTagFileNotPresentException;
Expand Down Expand Up @@ -62,14 +63,15 @@ private BagLinter(){
*
* @throws FetchFileNotAllowedException if there is a fetch file when the profile prohibits it
* @throws MetatdataValueIsNotAcceptableException if a metadata value is not in the list of acceptable values
* @throws MetatdataValueIsNotRepeatableException if a metadata value shows up more than once when not repeatable
* @throws RequiredMetadataFieldNotPresentException if a metadata field is not present but it should be
* @throws RequiredManifestNotPresentException if a payload or tag manifest type is not present but should be
* @throws BagitVersionIsNotAcceptableException if the version of the bag is not in the list of acceptable versions
* @throws RequiredTagFileNotPresentException if a tag file is not present but should be
*/
public static void checkAgainstProfile(final InputStream jsonProfile, final Bag bag) throws JsonParseException, JsonMappingException,
IOException, FetchFileNotAllowedException, RequiredMetadataFieldNotPresentException, MetatdataValueIsNotAcceptableException, RequiredManifestNotPresentException,
BagitVersionIsNotAcceptableException, RequiredTagFileNotPresentException{
BagitVersionIsNotAcceptableException, RequiredTagFileNotPresentException, MetatdataValueIsNotRepeatableException{
BagProfileChecker.bagConformsToProfile(jsonProfile, bag);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import gov.loc.repository.bagit.exceptions.conformance.BagitVersionIsNotAcceptableException;
import gov.loc.repository.bagit.exceptions.conformance.FetchFileNotAllowedException;
import gov.loc.repository.bagit.exceptions.conformance.MetatdataValueIsNotAcceptableException;
import gov.loc.repository.bagit.exceptions.conformance.MetatdataValueIsNotRepeatableException;
import gov.loc.repository.bagit.exceptions.conformance.RequiredManifestNotPresentException;
import gov.loc.repository.bagit.exceptions.conformance.RequiredMetadataFieldNotPresentException;
import gov.loc.repository.bagit.exceptions.conformance.RequiredTagFileNotPresentException;
Expand Down Expand Up @@ -59,14 +60,15 @@ private BagProfileChecker(){
*
* @throws FetchFileNotAllowedException if there is a fetch file when the profile prohibits it
* @throws MetatdataValueIsNotAcceptableException if a metadata value is not in the list of acceptable values
* @throws MetatdataValueIsNotRepeatableException if a metadata value shows up more than once when not repeatable
* @throws RequiredMetadataFieldNotPresentException if a metadata field is not present but it should be
* @throws RequiredManifestNotPresentException if a payload or tag manifest type is not present but should be
* @throws BagitVersionIsNotAcceptableException if the version of the bag is not in the list of acceptable versions
* @throws RequiredTagFileNotPresentException if a tag file is not present but should be
*/
public static void bagConformsToProfile(final InputStream jsonProfile, final Bag bag) throws JsonParseException, JsonMappingException,
IOException, FetchFileNotAllowedException, RequiredMetadataFieldNotPresentException, MetatdataValueIsNotAcceptableException,
RequiredManifestNotPresentException, BagitVersionIsNotAcceptableException, RequiredTagFileNotPresentException{
RequiredManifestNotPresentException, BagitVersionIsNotAcceptableException, RequiredTagFileNotPresentException, MetatdataValueIsNotRepeatableException{

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

private static void checkMetadata(final Metadata bagMetadata, final Map<String, BagInfoRequirement> bagInfoEntryRequirements)
throws RequiredMetadataFieldNotPresentException, MetatdataValueIsNotAcceptableException{
throws RequiredMetadataFieldNotPresentException, MetatdataValueIsNotAcceptableException, MetatdataValueIsNotRepeatableException{

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

logger.debug(messages.getString("checking_metadata_entry_required"), bagInfoEntryRequirement.getKey());
//is it required and not there?
if(bagInfoEntryRequirement.getValue().isRequired() && !metadataContainsKey){
throw new RequiredMetadataFieldNotPresentException(messages.getString("required_metadata_field_not_present_error"), bagInfoEntryRequirement.getKey());
}
checkIfMetadataEntryIsRequired(bagInfoEntryRequirement, metadataContainsKey);

checkForAcceptableValues(bagMetadata, bagInfoEntryRequirement);

//a size of zero implies that all values are acceptable
if(!bagInfoEntryRequirement.getValue().getAcceptableValues().isEmpty()){
logger.debug(messages.getString("check_values_acceptable"), bagInfoEntryRequirement.getKey());
for(final String metadataValue : bagMetadata.get(bagInfoEntryRequirement.getKey())){
if(!bagInfoEntryRequirement.getValue().getAcceptableValues().contains(metadataValue)){
throw new MetatdataValueIsNotAcceptableException(messages.getString("metadata_value_not_acceptable_error"),
bagInfoEntryRequirement.getKey(), bagInfoEntryRequirement.getValue().getAcceptableValues(), metadataValue);
}
checkForNoneRepeatableMetadata(bagMetadata, bagInfoEntryRequirement, metadataContainsKey);
}
}

private static void checkIfMetadataEntryIsRequired(final Entry<String, BagInfoRequirement> bagInfoEntryRequirement, final boolean metadataContainsKey) throws RequiredMetadataFieldNotPresentException{
logger.debug(messages.getString("checking_metadata_entry_required"), bagInfoEntryRequirement.getKey());
//is it required and not there?
if(bagInfoEntryRequirement.getValue().isRequired() && !metadataContainsKey){
throw new RequiredMetadataFieldNotPresentException(messages.getString("required_metadata_field_not_present_error"), bagInfoEntryRequirement.getKey());
}
}

private static void checkForAcceptableValues(final Metadata bagMetadata, final Entry<String, BagInfoRequirement> bagInfoEntryRequirement) throws MetatdataValueIsNotAcceptableException{
//a size of zero implies that all values are acceptable
if(!bagInfoEntryRequirement.getValue().getAcceptableValues().isEmpty()){
logger.debug(messages.getString("check_values_acceptable"), bagInfoEntryRequirement.getKey());
for(final String metadataValue : bagMetadata.get(bagInfoEntryRequirement.getKey())){
if(!bagInfoEntryRequirement.getValue().getAcceptableValues().contains(metadataValue)){
throw new MetatdataValueIsNotAcceptableException(messages.getString("metadata_value_not_acceptable_error"),
bagInfoEntryRequirement.getKey(), bagInfoEntryRequirement.getValue().getAcceptableValues(), metadataValue);
}
}
}
}

private static void checkForNoneRepeatableMetadata(final Metadata bagMetadata, final Entry<String, BagInfoRequirement> bagInfoEntryRequirement, final boolean metadataContainsKey) throws MetatdataValueIsNotRepeatableException{
//if it is none repeatable, but shows up multiple times
if(!bagInfoEntryRequirement.getValue().isRepeatable() && metadataContainsKey
&& bagMetadata.get(bagInfoEntryRequirement.getKey()).size() > 1){
throw new MetatdataValueIsNotRepeatableException(messages.getString("metadata_value_not_repeatable_error"), bagInfoEntryRequirement.getKey());
}
}

@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
private static void requiredManifestsExist(final Set<Manifest> manifests, final List<String> requiredManifestTypes, final boolean isPayloadManifest) throws RequiredManifestNotPresentException{
final Set<String> manifestTypesPresent = new HashSet<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
public class BagInfoRequirement {
private boolean required;
private List<String> acceptableValues = new ArrayList<>();
private boolean repeatable;

@Override
public boolean equals(final Object other) {
Expand All @@ -18,12 +19,13 @@ public boolean equals(final Object other) {
}
final BagInfoRequirement castOther = (BagInfoRequirement) other;
return Objects.equals(required, castOther.required)
&& Objects.equals(acceptableValues, castOther.acceptableValues);
&& Objects.equals(acceptableValues, castOther.acceptableValues)
&& Objects.equals(repeatable, castOther.repeatable);
}

@Override
public int hashCode() {
return Objects.hash(required, acceptableValues);
return Objects.hash(required, acceptableValues, repeatable);
}

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

@Override
public String toString() {
return "[required=" + required + ", acceptableValues=" + acceptableValues + "]";
return "[required=" + required + ", acceptableValues=" + acceptableValues + ", repeatable=" + repeatable + "]";
}

public boolean isRequired() {
Expand All @@ -52,4 +54,10 @@ public List<String> getAcceptableValues() {
public void setAcceptableValues(final List<String> acceptableValues) {
this.acceptableValues = acceptableValues;
}
public boolean isRepeatable() {
return repeatable;
}
public void setRepeatable(final boolean repeatable) {
this.repeatable = repeatable;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package gov.loc.repository.bagit.exceptions.conformance;

import org.slf4j.helpers.MessageFormatter;

/**
* Class to represent when a metadata's value is not to be repeated
*/
public class MetatdataValueIsNotRepeatableException extends Exception {
private static final long serialVersionUID = 1L;

public MetatdataValueIsNotRepeatableException(final String message, final String metadataKey) {
super(MessageFormatter.format(message, metadataKey).getMessage());
}
}
11 changes: 7 additions & 4 deletions src/main/resources/MessageBundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,16 @@ bagit_version_not_acceptable_error=Version [{}] is not in the acceptable list of
required_metadata_field_not_present_error=Profile specifies metadata field [{}] is required but was not found!

#for FetchFileNotAllowedException.java
fetch_file_not_allowed_error=Fetch File was found in bag [{}]
fetch_file_not_allowed_error=Fetch File was found in bag [{}]!

#for MetadataBalueIsNotAcceptableException.java
metadata_value_not_acceptable_error=Profile specifies that acceptable values for [{}] are {} but found [{}]
#for MetadataValueIsNotAcceptableException.java
metadata_value_not_acceptable_error=Profile specifies that acceptable values for [{}] are {} but found [{}]!

#for MetadataValueIsNotRepeatableException.java
metadata_value_not_repeatable_error=Profile specifies that value [{}] is not repeatable, but was listed multiple times!

#for RequiredTagFileNotPresentException.java
required_tag_file_not_found_error=Required tag file [{}] was not found
required_tag_file_not_found_error=Required tag file [{}] was not found!

#for EncodingChecker.java
tag_files_not_encoded_with_utf8_warning=Tag files are encoded with [{}]. We recommend always using UTF-8 instead.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import gov.loc.repository.bagit.exceptions.conformance.BagitVersionIsNotAcceptableException;
import gov.loc.repository.bagit.exceptions.conformance.FetchFileNotAllowedException;
import gov.loc.repository.bagit.exceptions.conformance.MetatdataValueIsNotAcceptableException;
import gov.loc.repository.bagit.exceptions.conformance.MetatdataValueIsNotRepeatableException;
import gov.loc.repository.bagit.exceptions.conformance.RequiredManifestNotPresentException;
import gov.loc.repository.bagit.exceptions.conformance.RequiredMetadataFieldNotPresentException;
import gov.loc.repository.bagit.exceptions.conformance.RequiredTagFileNotPresentException;
Expand Down Expand Up @@ -68,6 +69,16 @@ public void testMetatdataValueIsNotAcceptableException() throws Exception{
}
}

@Test(expected=MetatdataValueIsNotRepeatableException.class)
public void testMetadataValueIsNotRepeatableException() throws Exception{
Path bagRootPath = new File("src/test/resources/bagitProfileTestBags/repeatedMetadataBag").toPath();
Bag bag = reader.read(bagRootPath);

try(InputStream inputStream = Files.newInputStream(profileJson, StandardOpenOption.READ)){
BagProfileChecker.bagConformsToProfile(inputStream, bag);
}
}

@Test(expected=RequiredManifestNotPresentException.class)
public void testRequiredPayloadManifestNotPresentException() throws Exception{
Path bagRootPath = new File("src/test/resources/bagitProfileTestBags/missingRequiredPayloadManifestBag").toPath();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,36 @@ public class BagitProfileTest extends AbstractBagitProfileTest{
@Test
public void testToString() throws Exception{
String expectedOutput = "BagitProfile [bagitProfileIdentifier=http://canadiana.org/standards/bagit/tdr_ingest.json, "
+ "sourceOrganization=Candiana.org, externalDescription=BagIt profile for ingesting content into the C.O. TDR "
+ "loading dock., contactName=William Wueppelmann, [email protected], version=1.2, "
+ "bagInfoRequirements={Payload-Oxum=[required=true, acceptableValues=[]], Bag-Size=[required=true, "
+ "acceptableValues=[]], Bagging-Date=[required=true, acceptableValues=[]], Source-Organization=[required=true, "
+ "acceptableValues=[Simon Fraser University, York University]], Bag-Count=[required=true, acceptableValues=[]], "
+ "Organization-Address=[required=true, acceptableValues=[8888 University Drive Burnaby, B.C. V5A 1S6 Canada, "
+ "4700 Keele Street Toronto, Ontario M3J 1P3 Canada]], Bag-Group-Identifier=[required=false, "
+ "acceptableValues=[]], External-Identifier=[required=false, acceptableValues=[]], "
+ "Internal-Sender-Identifier=[required=false, acceptableValues=[]], Contact-Email=[required=true, "
+ "acceptableValues=[]], Contact-Phone=[required=false, acceptableValues=[]], "
+ "Internal-Sender-Description=[required=false, acceptableValues=[]], External-Description=[required=true, "
+ "acceptableValues=[]], Contact-Name=[required=true, acceptableValues=[Mark Jordan, Nick Ruest]]}, "
+ "manifestTypesRequired=[md5], fetchFileAllowed=false, serialization=forbidden, "
+ "acceptableMIMESerializationTypes=[application/zip], acceptableBagitVersions=[0.96], "
+ "tagManifestTypesRequired=[md5], tagFilesRequired=[DPN/dpnFirstNode.txt, DPN/dpnRegistry]]";
+ "sourceOrganization=Candiana.org, "
+ "externalDescription=BagIt profile for ingesting content into the C.O. TDR loading dock., "
+ "contactName=William Wueppelmann, "
+ "[email protected], "
+ "version=1.2, "
+ "bagInfoRequirements={"
+ "Payload-Oxum=[required=true, acceptableValues=[], repeatable=false], "
+ "Bag-Size=[required=true, acceptableValues=[], repeatable=false], "
+ "Bagging-Date=[required=true, acceptableValues=[], repeatable=false], "
+ "Source-Organization=[required=true, acceptableValues=[Simon Fraser University, York University], repeatable=false], "
+ "Bag-Count=[required=true, acceptableValues=[], repeatable=false], "
+ "Organization-Address=[required=true, acceptableValues=[8888 University Drive Burnaby, B.C. V5A 1S6 Canada, 4700 Keele Street Toronto, Ontario M3J 1P3 Canada], repeatable=false], "
+ "Bag-Group-Identifier=[required=false, acceptableValues=[], repeatable=false], "
+ "External-Identifier=[required=false, acceptableValues=[], repeatable=false], "
+ "Internal-Sender-Identifier=[required=false, acceptableValues=[], repeatable=false], "
+ "Contact-Email=[required=true, acceptableValues=[], repeatable=false], "
+ "Contact-Phone=[required=false, acceptableValues=[], repeatable=false], "
+ "Internal-Sender-Description=[required=false, acceptableValues=[], repeatable=false], "
+ "External-Description=[required=true, acceptableValues=[], repeatable=false], "
+ "Contact-Name=[required=true, acceptableValues=[Mark Jordan, Nick Ruest], repeatable=false]}, "
+ "manifestTypesRequired=[md5], "
+ "fetchFileAllowed=false, "
+ "serialization=forbidden, "
+ "acceptableMIMESerializationTypes=[application/zip], "
+ "acceptableBagitVersions=[0.96], "
+ "tagManifestTypesRequired=[md5], "
+ "tagFilesRequired=[DPN/dpnFirstNode.txt, DPN/dpnRegistry]]";

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bar
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Bag-Software-Agent: bagit.py v1.5.4 <http://github.com/libraryofcongress/bagit-python>
Bagging-Date: 2017-01-30
Bagging-Date: 2017-01-30
Payload-Oxum: 6.1
Source-Organization: York University
Organization-Address: 4700 Keele Street Toronto, Ontario M3J 1P3 Canada
Contact-Name: Nick Ruest
Contact-Email: [email protected]
External-Description: description here
Bag-Size: 6kb
Bag-Count: 1 of 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
BagIt-Version: 0.96
Tag-File-Character-Encoding: UTF-8
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
b1946ac92492d2347c6235b4d2611184 data/hello.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
55760904ab1799e2c61d438a6c44a013 bag-info.txt
ace0ef9419c8edbe164a888d4e4ab7ee bagit.txt
4cf73cc2586a3e8c3c118e1de8970675 manifest-md5.txt
3 changes: 2 additions & 1 deletion src/test/resources/bagitProfiles/exampleProfile.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
"required":false
},
"Bagging-Date":{
"required":true
"required":true,
"repeatable": false
},
"Payload-Oxum":{
"required":true
Expand Down

0 comments on commit 39654ff

Please sign in to comment.