• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jscancella / bagging / 4d62d4f2-8169-4233-b46e-bb15ca33adef

pending completion
4d62d4f2-8169-4233-b46e-bb15ca33adef

Pull #74

circleci

John Scancella
feat: actually check new profile features
Pull Request #74: feat: update bagitprofile parsing for 1.4.0 version

32 of 32 new or added lines in 2 files covered. (100.0%)

744 of 799 relevant lines covered (93.12%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.06
/src/main/java/com/github/jscancella/conformance/internal/BagProfileChecker.java
1
package com.github.jscancella.conformance.internal;
2

3
import java.io.IOException;
4
import java.io.InputStream;
5
import java.nio.file.Files;
6
import java.nio.file.Path;
7
import java.util.HashSet;
8
import java.util.List;
9
import java.util.Map;
10
import java.util.Map.Entry;
11
import java.util.ResourceBundle;
12
import java.util.Set;
13

14
import org.slf4j.Logger;
15
import org.slf4j.LoggerFactory;
16
import org.slf4j.helpers.MessageFormatter;
17

18
import com.fasterxml.jackson.core.JsonParseException;
19
import com.fasterxml.jackson.databind.JsonMappingException;
20
import com.fasterxml.jackson.databind.ObjectMapper;
21
import com.fasterxml.jackson.databind.module.SimpleModule;
22
import com.github.jscancella.conformance.exceptions.BagitVersionIsNotAcceptableException;
23
import com.github.jscancella.conformance.exceptions.FetchFileNotAllowedException;
24
import com.github.jscancella.conformance.exceptions.MetatdataValueIsNotAcceptableException;
25
import com.github.jscancella.conformance.exceptions.MetatdataValueIsNotRepeatableException;
26
import com.github.jscancella.conformance.exceptions.RequiredManifestNotPresentException;
27
import com.github.jscancella.conformance.exceptions.RequiredMetadataFieldNotPresentException;
28
import com.github.jscancella.conformance.exceptions.RequiredTagFileNotPresentException;
29
import com.github.jscancella.conformance.profile.BagInfoRequirement;
30
import com.github.jscancella.conformance.profile.BagitProfile;
31
import com.github.jscancella.conformance.profile.BagitProfileDeserializer;
32
import com.github.jscancella.domain.Bag;
33
import com.github.jscancella.domain.FetchItem;
34
import com.github.jscancella.domain.Manifest;
35
import com.github.jscancella.domain.Metadata;
36
import com.github.jscancella.exceptions.DataDirectoryMustBeEmptyException;
37
import com.github.jscancella.exceptions.FetchFileDoesNotExistException;
38

39
/**
40
 * Part of the BagIt conformance suite. 
41
 * Responsible for checking a bag against a profile
42
 */
43
public enum BagProfileChecker {;//using enum to enforce singleton
1✔
44
  private static final Logger logger = LoggerFactory.getLogger(BagProfileChecker.class);
1✔
45
  private static final ResourceBundle messages = ResourceBundle.getBundle("MessageBundle");
1✔
46
  
47
  /**
48
   * Check a bag against a bagit-profile as described by 
49
   * <a href="https://github.com/ruebot/bagit-profiles">https://github.com/ruebot/bagit-profiles</a>
50
   * <br>Note: <b> This implementation does not check the Serialization part of the profile!</b>
51
   * 
52
   * @param jsonProfile the input stream to the json string describing the profile
53
   * @param bag the bag to check against the profile
54
   * 
55
   * @throws IOException if there is a problem reading the profile
56
   * @throws JsonMappingException if there is a problem mapping the profile to the {@link BagitProfile}
57
   * @throws JsonParseException if there is a problem parsing the json while mapping to java object
58
   * 
59
   * @throws FetchFileNotAllowedException if there is a fetch file when the profile prohibits it
60
   * @throws FetchFileDoesNotExistException if there is no fetch file in the bag when the profile requires it
61
   * @throws DataDirectoryMustBeEmptyException if the data directory is not empty when the profile prohibits it
62
   * @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
64
   * @throws RequiredMetadataFieldNotPresentException if a metadata field is not present but it should be
65
   * @throws RequiredManifestNotPresentException if a payload or tag manifest type is not present but should be
66
   * @throws BagitVersionIsNotAcceptableException if the version of the bag is not in the list of acceptable versions
67
   * @throws RequiredTagFileNotPresentException if a tag file is not present but should be
68
   */
69
  public static void bagConformsToProfile(final InputStream jsonProfile, final Bag bag) throws JsonParseException, JsonMappingException, 
70
  IOException, FetchFileNotAllowedException, RequiredMetadataFieldNotPresentException, MetatdataValueIsNotAcceptableException, 
71
  RequiredManifestNotPresentException, BagitVersionIsNotAcceptableException, RequiredTagFileNotPresentException, MetatdataValueIsNotRepeatableException, FetchFileDoesNotExistException, DataDirectoryMustBeEmptyException{
72
    
73
    final BagitProfile profile = parseBagitProfile(jsonProfile);
1✔
74
    checkFetchIsAllowed(bag.getRootDir(), profile.isFetchFileAllowed(), bag.getItemsToFetch());
1✔
75
    checkFetchIsRequired(bag.getRootDir(), profile.isFetchFileRequired(), bag.getItemsToFetch());
1✔
76
    
77
    checkMetadata(bag.getMetadata(), profile.getBagInfoRequirements());
1✔
78
    
79
    requiredManifestsExist(bag.getPayLoadManifests(), profile.getManifestTypesRequired(), true);
1✔
80
    dataDirectoryMustBeEmpty(bag.getDataDir(), profile.isDataDirMustBeEmpty());
1✔
81

82
    requiredManifestsExist(bag.getTagManifests(), profile.getTagManifestTypesRequired(), false);
1✔
83

84
    if(!profile.getAcceptableBagitVersions().contains(bag.getVersion().toString())){
1✔
85
      throw new BagitVersionIsNotAcceptableException(messages.getString("bagit_version_not_acceptable_error"), bag.getVersion(), profile.getAcceptableBagitVersions());
1✔
86
    }
87
    
88
    requiredTagFilesExist(bag.getRootDir(), profile.getTagFilesRequired());
1✔
89
  }
1✔
90
  
91
  private static BagitProfile parseBagitProfile(final InputStream jsonProfile) throws JsonParseException, JsonMappingException, IOException{
92
    final ObjectMapper mapper = new ObjectMapper();
1✔
93
    final SimpleModule module = new SimpleModule();
1✔
94
    module.addDeserializer(BagitProfile.class, new BagitProfileDeserializer());
1✔
95
    mapper.registerModule(module);
1✔
96

97
    return mapper.readValue(jsonProfile, BagitProfile.class);
1✔
98
  }
99
  
100
  private static void checkFetchIsAllowed(final Path rootDir, final boolean allowFetchFile, final List<FetchItem> itemsToFetch) throws FetchFileNotAllowedException{
101
    logger.debug(messages.getString("checking_fetch_file_allowed"), rootDir);
1✔
102
    if(!allowFetchFile && !itemsToFetch.isEmpty()){
1✔
103
      throw new FetchFileNotAllowedException(messages.getString("fetch_file_not_allowed_error"), rootDir);
1✔
104
    }
105
  }
1✔
106
  
107
  private static void checkFetchIsRequired(final Path rootDir, final boolean fetchFileRequired, final List<FetchItem> itemsToFetch) throws FetchFileDoesNotExistException{
108
    logger.debug(messages.getString("checking_fetch_file_required"), rootDir);
1✔
109
    if(fetchFileRequired && itemsToFetch.isEmpty()) {
1✔
110
      throw new FetchFileDoesNotExistException(messages.getString("fetch_file_required_error"), rootDir);
×
111
    }
112
  }
1✔
113
  
114
  private static void checkMetadata(final Metadata bagMetadata, final Map<String, BagInfoRequirement> bagInfoEntryRequirements) 
115
      throws RequiredMetadataFieldNotPresentException, MetatdataValueIsNotAcceptableException, MetatdataValueIsNotRepeatableException{
116
    
117
    for(final Entry<String, BagInfoRequirement> bagInfoEntryRequirement : bagInfoEntryRequirements.entrySet()){
1✔
118
      final boolean metadataContainsKey = bagMetadata.contains(bagInfoEntryRequirement.getKey());
1✔
119
      
120
      checkIfMetadataEntryIsRequired(bagInfoEntryRequirement, metadataContainsKey);
1✔
121
      
122
      checkForAcceptableValues(bagMetadata, bagInfoEntryRequirement);
1✔
123
      
124
      checkForNoneRepeatableMetadata(bagMetadata, bagInfoEntryRequirement, metadataContainsKey);
1✔
125
    }
1✔
126
  }
1✔
127
  
128
  private static void checkIfMetadataEntryIsRequired(final Entry<String, BagInfoRequirement> bagInfoEntryRequirement, final boolean metadataContainsKey) throws RequiredMetadataFieldNotPresentException{
129
    logger.debug(messages.getString("checking_metadata_entry_required"), bagInfoEntryRequirement.getKey());
1✔
130
    //is it required and not there?
131
    if(bagInfoEntryRequirement.getValue().isRequired() && !metadataContainsKey){
1✔
132
      throw new RequiredMetadataFieldNotPresentException(messages.getString("required_metadata_field_not_present_error"), bagInfoEntryRequirement.getKey());
1✔
133
    }
134
  }
1✔
135
  
136
  private static void checkForAcceptableValues(final Metadata bagMetadata, final Entry<String, BagInfoRequirement> bagInfoEntryRequirement) throws MetatdataValueIsNotAcceptableException{
137
    //a size of zero implies that all values are acceptable
138
    if(!bagInfoEntryRequirement.getValue().getAcceptableValues().isEmpty()){
1✔
139
      logger.debug(messages.getString("check_values_acceptable"), bagInfoEntryRequirement.getKey());
1✔
140
      for(final String metadataValue : bagMetadata.get(bagInfoEntryRequirement.getKey())){
1✔
141
        if(!bagInfoEntryRequirement.getValue().getAcceptableValues().contains(metadataValue)){
1✔
142
          throw new MetatdataValueIsNotAcceptableException(messages.getString("metadata_value_not_acceptable_error"), 
1✔
143
              bagInfoEntryRequirement.getKey(), bagInfoEntryRequirement.getValue().getAcceptableValues(), metadataValue);
1✔
144
        }
145
      }
1✔
146
    }
147
  }
1✔
148
  
149
  private static void checkForNoneRepeatableMetadata(final Metadata bagMetadata, final Entry<String, BagInfoRequirement> bagInfoEntryRequirement, final boolean metadataContainsKey) throws MetatdataValueIsNotRepeatableException{
150
    //if it is none repeatable, but shows up multiple times
151
    if(!bagInfoEntryRequirement.getValue().isRepeatable() && metadataContainsKey 
1✔
152
        && bagMetadata.get(bagInfoEntryRequirement.getKey()).size() > 1){
1✔
153
      throw new MetatdataValueIsNotRepeatableException(messages.getString("metadata_value_not_repeatable_error"), bagInfoEntryRequirement.getKey());
1✔
154
    }
155
  }
1✔
156
  
157
  @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
158
  private static void requiredManifestsExist(final Set<Manifest> manifests, final List<String> requiredManifestTypes, final boolean isPayloadManifest) throws RequiredManifestNotPresentException{
159
    final Set<String> manifestTypesPresent = new HashSet<>();
1✔
160
    logger.debug(messages.getString("check_required_manifests_present"));
1✔
161
    
162
    for(final Manifest manifest : manifests){
1✔
163
      manifestTypesPresent.add(manifest.getBagitAlgorithmName());
1✔
164
    }
1✔
165
    
166
    for(final String requiredManifestType : requiredManifestTypes){
1✔
167
      if(!manifestTypesPresent.contains(requiredManifestType)){
1✔
168
        final StringBuilder explaination = new StringBuilder();
1✔
169
        if(isPayloadManifest){ 
1✔
170
          explaination.append("tag");
1✔
171
          explaination.append(MessageFormatter.format(messages.getString("required_tag_manifest_type_not_present"), requiredManifestType).getMessage());
1✔
172
        }
173
        else{
174
          explaination.append(MessageFormatter.format(messages.getString("required_manifest_type_not_present"), requiredManifestType).getMessage());
1✔
175
        }
176
          
177
        throw new RequiredManifestNotPresentException(explaination.toString());
1✔
178
      }
179
    }
1✔
180
  }
1✔
181
  
182
  //must only contain a single zero byte file in the data directory
183
  @SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
184
  private static void dataDirectoryMustBeEmpty(final Path rootDir, final boolean dataDirMustBeEmpty) throws IOException, DataDirectoryMustBeEmptyException {
185
    logger.debug(messages.getString("checking_data_dir_is_empty"), rootDir);
1✔
186
    if(dataDirMustBeEmpty) {
1✔
187
      final DataDirIsEmptyChecker checker = new DataDirIsEmptyChecker();
×
188
      Files.walkFileTree(rootDir, checker);
×
189
      
190
      if(checker.getNonZeroByteFiles().size() > 0) {
×
191
        final StringBuilder pathFormatter = new StringBuilder();
×
192
        checker.getNonZeroByteFiles().stream()
×
193
          .forEach(path -> pathFormatter.append('"').append(path.toAbsolutePath().toString()).append("\" "));
×
194
        
195
        throw new DataDirectoryMustBeEmptyException(MessageFormatter.format(messages.getString("multiple_non_zero_files_found"), pathFormatter.toString()).getMessage());
×
196
      }
197
      if(checker.getZeroByteFiles().size() != 1) {
×
198
        final StringBuilder pathFormatter = new StringBuilder();
×
199
        checker.getZeroByteFiles().stream()
×
200
          .forEach(path -> pathFormatter.append('"').append(path.toAbsolutePath().toString()).append("\" "));
×
201
        
202
        throw new DataDirectoryMustBeEmptyException(MessageFormatter.format(messages.getString("multiple_zero_byte_files_found"), pathFormatter.toString()).getMessage());
×
203
      }
204
    }
205
  }
1✔
206
  
207
  private static void requiredTagFilesExist(final Path rootDir, final List<String> requiredTagFilePaths) throws RequiredTagFileNotPresentException{
208
    Path requiredTagFile;
209
    logger.debug(messages.getString("checking_required_tag_file_exists"));
1✔
210
    
211
    for(final String requiredTagFilePath : requiredTagFilePaths){
1✔
212
      requiredTagFile = rootDir.resolve(requiredTagFilePath);
1✔
213
      if(!Files.exists(requiredTagFile)){
1✔
214
        throw new RequiredTagFileNotPresentException(messages.getString("required_tag_file_not_found_error"), requiredTagFilePath);
1✔
215
      }
216
    }
1✔
217
  }
1✔
218
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc