Skip to content

Commit

Permalink
Merge pull request #90 from LibraryOfCongress/Internationalize
Browse files Browse the repository at this point in the history
Internationalize messages
  • Loading branch information
johnscancella authored Apr 27, 2017
2 parents af44e84 + 068ddfb commit 66fc465
Show file tree
Hide file tree
Showing 47 changed files with 603 additions and 221 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ this functionality, the
demonstrates how you can implement this feature with your additional application
and workflow requirements.

##### Internationalization
All logging and error messages have been put into a [ResourceBundle](https://docs.oracle.com/javase/7/docs/api/java/util/ResourceBundle.html).
This allows for all the messages to be translated to multiple languages and automatically used during runtime.
If you would like to contribute to translations please visit https://www.transifex.com/acdha/bagit-java/dashboard/

##### New Interfaces

The 5.x version is a complete rewrite of the bagit-java library which attempts
Expand Down Expand Up @@ -170,3 +175,4 @@ Simply run `gradle eclipse` and it will automatically create a eclipse project f
### Roadmap for this library
* Further refine reading and writing of bags version 0.93-0.97
* Fix bugs/issues reported with new library (on going)
* Translate to various languages (on going)
64 changes: 59 additions & 5 deletions code-quality.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ jacocoTestReport {

cpdCheck {
source = sourceSets.main.allJava
// reports {
// text.enabled = true
// xml.enabled = false
// }
reports {
text.enabled = true
xml.enabled = false
}
}

dependencyCheck {
Expand All @@ -97,4 +97,58 @@ dependencyCheck {
// xml.enabled = false
// html.enabled = true
// }
//}
//}

import java.util.Map.Entry;
task checkMessageBundle(){
inputs.files(fileTree(dir: "src/main/resources", include: "**/MessageBundle*.properties"))
outputs.dir("$buildDir/checkMessageBundle") //hack: define a output dir so gradle will check if up-to-date

doLast{
Set<String> messageKeys = new HashSet<>()

inputs.getFiles().each{File file ->
file.eachLine {String line ->
if(line && !line.trim().startsWith('#')){
String[] keyValue = checkMessageBundleLineIsCorrectlyFormatted(line, file)

if(messageKeys.contains(keyValue[0])){
throw new GradleException("Internationalization message bundle contains duplicate key [${keyValue[0]}]!")
}
messageKeys.add(keyValue[0])
}
}
}

checkAllMessageKeysAreUsed(messageKeys)
}
}
check.dependsOn checkMessageBundle

String[] checkMessageBundleLineIsCorrectlyFormatted(String line, File file){
String[] keyValue = line.split("=", 2)

if(keyValue.size() != 2 || keyValue[1].isEmpty()){
throw new GradleException("Line [${line}] in file [${file}] is not a valid entry for the internationalization message bundle!")
}

return keyValue
}

void checkAllMessageKeysAreUsed(Set<String> messageKeys){
sourceSets.main.allJava.each { File file ->
file.eachLine{ String line ->
for(String key : messageKeys.clone()){
if(line.contains(key)){
messageKeys.remove(key)
}
}
}
}

if(messageKeys.size() > 0){
messageKeys.each{String key ->
throw new GradleException("[${key}] is listed in the internationalization message bundle but never actually used!")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;

import org.slf4j.Logger;
Expand Down Expand Up @@ -40,6 +41,7 @@
*/
public final class BagLinter {
private static final Logger logger = LoggerFactory.getLogger(BagLinter.class);
private static final ResourceBundle messages = ResourceBundle.getBundle("MessageBundle");
private static final Version VERSION_1_0 = new Version(1,0);

private BagLinter(){
Expand Down Expand Up @@ -117,38 +119,36 @@ public static Set<BagitWarning> lintBag(final Path rootDir, final Collection<Bag
checkForExtraLines(bagitFile, warnings, warningsToIgnore);
final SimpleImmutableEntry<Version, Charset> bagitInfo = BagitTextFileReader.readBagitTextFile(bagitFile);

logger.debug("Checking encoding problems.");
logger.info(messages.getString("checking_encoding_problems"));
EncodingChecker.checkEncoding(bagitInfo.getValue(), warnings, warningsToIgnore);

logger.debug("checking for latest version.");
logger.info(messages.getString("checking_latest_version"));
VersionChecker.checkVersion(bagitInfo.getKey(), warnings, warningsToIgnore);

logger.debug("checking manifests for problems.");
logger.info(messages.getString("checking_manifest_problems"));
ManifestChecker.checkManifests(bagitDir, bagitInfo.getValue(), warnings, warningsToIgnore);

logger.debug("checking bag metadata for problems.");
logger.info(messages.getString("checking_metadata_problems"));
MetadataChecker.checkBagMetadata(bagitDir, bagitInfo.getValue(), warnings, warningsToIgnore);

return warnings;
}

private static void checkForExtraLines(final Path bagitFile, final Collection<BagitWarning> warnings, final Collection<BagitWarning> warningsToIgnore) throws InvalidBagMetadataException, IOException, UnparsableVersionException{
if(warningsToIgnore.contains(BagitWarning.EXTRA_LINES_IN_BAGIT_FILES)){
logger.debug("skipping check for extra lines in bagit files");
logger.debug(messages.getString("skipping_check_extra_lines"));
return;
}

logger.debug("checking if [{}] contains more than 2 lines");
logger.debug(messages.getString("checking_extra_lines"));
final List<SimpleImmutableEntry<String, String>> pairs = KeyValueReader.readKeyValuesFromFile(bagitFile, ":", StandardCharsets.UTF_8);

for(final SimpleImmutableEntry<String, String> pair : pairs){
if("BagIt-Version".equals(pair.getKey())){
final Version version = BagitTextFileReader.parseVersion(pair.getValue());
//versions before 1.0 specified it must be exactly 2 lines
if(pairs.size() > 2 && version.isOlder(VERSION_1_0)){
logger.warn("The bagit specification states that the bagit.txt file must contain exactly 2 lines. "
+ "However we found {} lines, some implementations will "
+ "ignore this but may cause imcompatibility issues with other tools.", pairs.size());
logger.warn(messages.getString("extra_lines_warning"), pairs.size());
warnings.add(BagitWarning.EXTRA_LINES_IN_BAGIT_FILES);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ResourceBundle;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.helpers.MessageFormatter;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
Expand All @@ -37,6 +39,7 @@
*/
public final class BagProfileChecker {
private static final Logger logger = LoggerFactory.getLogger(BagProfileChecker.class);
private static final ResourceBundle messages = ResourceBundle.getBundle("MessageBundle");

private BagProfileChecker(){
//intentionally left empty
Expand Down Expand Up @@ -75,8 +78,7 @@ public static void bagConformsToProfile(final InputStream jsonProfile, final Bag
requiredManifestsExist(bag.getTagManifests(), profile.getTagManifestTypesRequired(), false);

if(!profile.getAcceptableBagitVersions().contains(bag.getVersion().toString())){
throw new BagitVersionIsNotAcceptableException("Version [" + bag.getVersion().toString() + "] is not in the acceptable list of " +
profile.getAcceptableBagitVersions());
throw new BagitVersionIsNotAcceptableException(messages.getString("bagit_version_not_acceptable_error"), bag.getVersion(), profile.getAcceptableBagitVersions());
}

requiredTagFilesExist(bag.getRootDir(), profile.getTagFilesRequired());
Expand All @@ -92,9 +94,9 @@ private static BagitProfile parseBagitProfile(final InputStream jsonProfile) thr
}

private static void checkFetch(final Path rootDir, final boolean allowFetchFile, final List<FetchItem> itemsToFetch) throws FetchFileNotAllowedException{
logger.debug("Checking if the fetch file is allowed for bag [{}]", rootDir);
logger.debug(messages.getString("checking_fetch_file_allowed"), rootDir);
if(!allowFetchFile && !itemsToFetch.isEmpty()){
throw new FetchFileNotAllowedException("Fetch File was found in bag [" + rootDir + "]");
throw new FetchFileNotAllowedException(messages.getString("fetch_file_not_allowed_error"), rootDir);
}
}

Expand All @@ -104,19 +106,19 @@ private static void checkMetadata(final Metadata bagMetadata, final Map<String,
for(final Entry<String, BagInfoRequirement> bagInfoEntryRequirement : bagInfoEntryRequirements.entrySet()){
final boolean metadataContainsKey = bagMetadata.contains(bagInfoEntryRequirement.getKey());

logger.debug("Checking if [{}] is required in the bag metadata", 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("Profile specifies metadata field [" + bagInfoEntryRequirement.getKey() + "] is required but was not found!");
throw new RequiredMetadataFieldNotPresentException(messages.getString("required_metadata_field_not_present_error"), bagInfoEntryRequirement.getKey());
}

//a size of zero implies that all values are acceptable
if(!bagInfoEntryRequirement.getValue().getAcceptableValues().isEmpty()){
logger.debug("Checking if all the values listed for [{}] are acceptable", bagInfoEntryRequirement.getKey());
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("Profile specifies that acceptable values for [" + bagInfoEntryRequirement.getKey() +
"] are " + bagInfoEntryRequirement.getValue().getAcceptableValues() + " but found [" + metadataValue + "]");
throw new MetatdataValueIsNotAcceptableException(messages.getString("metadata_value_not_acceptable_error"),
bagInfoEntryRequirement.getKey(), bagInfoEntryRequirement.getValue().getAcceptableValues(), metadataValue);
}
}
}
Expand All @@ -126,18 +128,21 @@ private static void checkMetadata(final Metadata bagMetadata, final Map<String,
@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<>();
logger.debug("Checking if all the required manifests are present");
logger.debug(messages.getString("check_required_manifests_present"));

for(final Manifest manifest : manifests){
manifestTypesPresent.add(manifest.getAlgorithm().getBagitName());
}

for(final String requiredManifestType : requiredManifestTypes){
if(!manifestTypesPresent.contains(requiredManifestType)){
final StringBuilder sb = new StringBuilder(25);
sb.append("Required ");
if(isPayloadManifest){ sb.append("tag");}
sb.append("manifest type [").append(requiredManifestType).append("] was not present");
final StringBuilder sb = new StringBuilder();
if(isPayloadManifest){ sb.append("tag");
sb.append(MessageFormatter.format(messages.getString("required_tag_manifest_type_not_present"), requiredManifestType).getMessage());
}
else{
sb.append(MessageFormatter.format(messages.getString("required_manifest_type_not_present"), requiredManifestType).getMessage());
}

throw new RequiredManifestNotPresentException(sb.toString());
}
Expand All @@ -146,12 +151,12 @@ private static void requiredManifestsExist(final Set<Manifest> manifests, final

private static void requiredTagFilesExist(final Path rootDir, final List<String> requiredTagFilePaths) throws RequiredTagFileNotPresentException{
Path requiredTagFile;
logger.debug("Checking if all the required tag files exist");
logger.debug(messages.getString("checking_required_tag_file_exists"));

for(final String requiredTagFilePath : requiredTagFilePaths){
requiredTagFile = rootDir.resolve(requiredTagFilePath);
if(!Files.exists(requiredTagFile)){
throw new RequiredTagFileNotPresentException("Required tag file [" + requiredTagFilePath + "] was not found");
throw new RequiredTagFileNotPresentException(messages.getString("required_tag_file_not_found_error"), requiredTagFilePath);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,35 @@
package gov.loc.repository.bagit.conformance;

import java.util.ResourceBundle;

/**
* The BagIt specification is very flexible in what it allows.
* This leads to situations where something may be technically allowed, but should be discouraged.
* This class is for that purpose, to allow reporting of these allowed but discouraged situations to the user.
*/
public enum BagitWarning {
BAG_WITHIN_A_BAG("A data directory can contain anything,"
+ " including another bag. However it would be better to merge the bags together."),
DIFFERENT_CASE("The bag contains two files that differ only in case. "
+ "This can cause problems on a filesystem like the one used by apple (HFS)."),
DIFFERENT_NORMALIZATION("The bag contains two files that differ only in the normalization. "
+ "This can cause verification to fail on some systems, and general user confusion."),
EXTRA_LINES_IN_BAGIT_FILES("The bagit specification says it must only contain 2 lines. "
+ "However, some implementations have decided to ignore this which may cause compatibility issues"),
LEADING_DOT_SLASH("A manifest lists all data files as relative to the bag root directory,"
+ " it is superfluous to therefore specify it with a dot."),
NON_STANDARD_ALGORITHM("The checksum algorithm used does not come standard with the Java runtime. Consider using SHA512 instead."),
MD5SUM_TOOL_GENERATED_MANIFEST("The manifest was created using a using checksum utilities such as those\n" +
"contained in the GNU Coreutils package (md5sum, sha1sum, etc.), collectively referred to here as 'md5sum'. "
+ "This creates slight differences in generated manifests that can cause problems in some implementations."),
MISSING_TAG_MANIEST("The tag manifest guards against a truncated payload manifest as well as other potental "
+ "problems and is always recommened that it be included."),
OLD_BAGIT_VERSION("The bagit specification version is not the newest. Consider converting to the latest version."),
OS_SPECIFIC_FILES("Files created by the operating system (OS) for its own use. They are non-portable across OS versions "
+ "and should not be included in any manifest. Examples Thumbs.db on Windows or .DS_Store on OS X"),
PAYLOAD_OXUM_MISSING("It is recommended to always include the Payload-Oxum in the bag metadata "
+ "since it allows for a 'quick verification' of the bag."),
TAG_FILES_ENCODING("It is recommended to always use UTF-8"),
WEAK_CHECKSUM_ALGORITHM("The checksum algorithm used is known to be weak. We recommend using SHA512 at a minium");
BAG_WITHIN_A_BAG("bag_within_a_bag"),
DIFFERENT_CASE("different_case"),
DIFFERENT_NORMALIZATION("different_normalization"),
EXTRA_LINES_IN_BAGIT_FILES("extra_lines_in_bagit_files"),
LEADING_DOT_SLASH("leading_dot_slash"),
NON_STANDARD_ALGORITHM("non_standard_algorithm"),
MD5SUM_TOOL_GENERATED_MANIFEST("md5sum_tool_generated_manifest"),
MISSING_TAG_MANIFEST("missing_tag_manifest"),
OLD_BAGIT_VERSION("old_bagit_version"),
OS_SPECIFIC_FILES("os_specific_files"),
PAYLOAD_OXUM_MISSING("payload_oxum_missing"),
TAG_FILES_ENCODING("tag_files_encoding"),
WEAK_CHECKSUM_ALGORITHM("weak_checksum_algorithm");

private final String reason;
private final String messageBundleKey;
private static final ResourceBundle messages = ResourceBundle.getBundle("MessageBundle");

private BagitWarning(final String reason){
this.reason = reason;
this.messageBundleKey = reason;
}

public String getReason() {
return reason;
return messages.getString(messageBundleKey);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.ResourceBundle;
import java.util.Set;

import org.slf4j.Logger;
Expand All @@ -14,13 +15,14 @@
*/
public interface EncodingChecker {
Logger logger = LoggerFactory.getLogger(EncodingChecker.class);
ResourceBundle messages = ResourceBundle.getBundle("MessageBundle");

/*
* It is now normal for all files to be UTF-8
*/
static void checkEncoding(final Charset encoding, final Set<BagitWarning> warnings, final Collection<BagitWarning> warningsToIgnore){
if(!warningsToIgnore.contains(BagitWarning.TAG_FILES_ENCODING) && !StandardCharsets.UTF_8.equals(encoding)){
logger.warn("Tag files are encoded with [{}]. We recommend always using UTF-8 instead.", encoding);
logger.warn(messages.getString("tag_files_not_encoded_with_utf8_warning"), encoding);
warnings.add(BagitWarning.TAG_FILES_ENCODING);
}
}
Expand Down
Loading

0 comments on commit 66fc465

Please sign in to comment.