001/* 002 * Licensed to DuraSpace under one or more contributor license agreements. 003 * See the NOTICE file distributed with this work for additional information 004 * regarding copyright ownership. 005 * 006 * DuraSpace licenses this file to you under the Apache License, 007 * Version 2.0 (the "License"); you may not use this file except in 008 * compliance with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.fcrepo.kernel.api.utils; 019 020import static org.apache.commons.codec.binary.Hex.encodeHexString; 021import static org.fcrepo.kernel.api.utils.ContentDigest.DIGEST_ALGORITHM.SHA1; 022import static org.slf4j.LoggerFactory.getLogger; 023 024import java.net.URI; 025import java.net.URISyntaxException; 026import java.util.Arrays; 027import java.util.Set; 028import java.util.stream.Collectors; 029 030import org.apache.commons.lang3.ArrayUtils; 031import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 032import org.slf4j.Logger; 033 034/** 035 * Digest helpers to convert digests (checksums) into URI strings 036 * (based loosely on Magnet URIs) 037 * @author Chris Beer 038 * @since Mar 6, 2013 039 */ 040public final class ContentDigest { 041 042 private static final Logger LOGGER = getLogger(ContentDigest.class); 043 044 private final static String DEFAULT_DIGEST_ALGORITHM_PROPERTY = "fcrepo.persistence.defaultDigestAlgorithm"; 045 public final static DIGEST_ALGORITHM DEFAULT_DIGEST_ALGORITHM; 046 047 static { 048 // Establish the default digest algorithm for fedora, defaulting to SHA512 049 DEFAULT_DIGEST_ALGORITHM = DIGEST_ALGORITHM.fromAlgorithm( 050 System.getProperty(DEFAULT_DIGEST_ALGORITHM_PROPERTY, DIGEST_ALGORITHM.SHA512.algorithm)); 051 052 // Throw error if the configured default digest is not known to fedora 053 if (DIGEST_ALGORITHM.MISSING.equals(DEFAULT_DIGEST_ALGORITHM)) { 054 throw new IllegalArgumentException("Invalid " + DEFAULT_DIGEST_ALGORITHM_PROPERTY 055 + " property configured: " + System.getProperty(DEFAULT_DIGEST_ALGORITHM_PROPERTY)); 056 } 057 } 058 059 public enum DIGEST_ALGORITHM { 060 SHA1("SHA", "urn:sha1", "sha-1", "sha1"), 061 SHA256("SHA-256", "urn:sha-256", "sha256"), 062 SHA512("SHA-512", "urn:sha-512", "sha512"), 063 SHA512256("SHA-512/256", "urn:sha-512/256", "sha512/256"), 064 MD5("MD5", "urn:md5"), 065 MISSING("NONE", "missing"); 066 067 final public String algorithm; 068 final private String scheme; 069 final private Set<String> aliases; 070 071 DIGEST_ALGORITHM(final String alg, final String scheme, final String... aliases) { 072 this.algorithm = alg; 073 this.scheme = scheme; 074 this.aliases = Arrays.stream(ArrayUtils.add(aliases, algorithm)) 075 .map(String::toLowerCase) 076 .collect(Collectors.toSet()); 077 } 078 079 /** 080 * Return the scheme associated with the provided algorithm (e.g. SHA-1 returns urn:sha1) 081 * 082 * @param alg for which scheme is requested 083 * @return scheme 084 */ 085 public static String getScheme(final String alg) { 086 return Arrays.stream(values()).filter(value -> 087 value.algorithm.equalsIgnoreCase(alg) || value.algorithm.replace("-", "").equalsIgnoreCase(alg) 088 ).findFirst().orElse(MISSING).scheme; 089 } 090 091 /** 092 * Return enum value for the provided scheme (e.g. urn:sha1 returns SHA-1) 093 * 094 * @param argScheme for which enum is requested 095 * @return enum value associated with the arg scheme 096 */ 097 public static DIGEST_ALGORITHM fromScheme(final String argScheme) { 098 return Arrays.stream(values()).filter(value -> value.scheme.equalsIgnoreCase(argScheme) 099 ).findFirst().orElse(MISSING); 100 } 101 102 /** 103 * Return enum value for the provided algorithm 104 * 105 * @param alg algorithm name to seek 106 * @return enum value associated with the algorithm name, or missing if not found 107 */ 108 public static DIGEST_ALGORITHM fromAlgorithm(final String alg) { 109 final String seek = alg.toLowerCase(); 110 return Arrays.stream(values()) 111 .filter(value -> value.aliases.contains(seek)) 112 .findFirst() 113 .orElse(MISSING); 114 } 115 116 /** 117 * Return true if the provided algorithm is included in this enum 118 * 119 * @param alg to test 120 * @return true if arg algorithm is supported 121 */ 122 public static boolean isSupportedAlgorithm(final String alg) { 123 return !getScheme(alg).equals(MISSING.scheme); 124 } 125 126 /** 127 * @return the aliases 128 */ 129 public Set<String> getAliases() { 130 return aliases; 131 } 132 } 133 134 public static final String DEFAULT_ALGORITHM = DIGEST_ALGORITHM.SHA1.algorithm; 135 136 private ContentDigest() { 137 } 138 139 /** 140 * Convert a MessageDigest algorithm and checksum value to a URN 141 * @param algorithm the message digest algorithm 142 * @param value the checksum value 143 * @return URI 144 */ 145 public static URI asURI(final String algorithm, final String value) { 146 try { 147 final String scheme = DIGEST_ALGORITHM.getScheme(algorithm); 148 149 return new URI(scheme, value, null); 150 } catch (final URISyntaxException unlikelyException) { 151 LOGGER.warn("Exception creating checksum URI: alg={}; value={}", 152 algorithm, value); 153 throw new RepositoryRuntimeException(unlikelyException.getMessage(), unlikelyException); 154 } 155 } 156 157 /** 158 * Convert a MessageDigest algorithm and checksum byte-array data to a URN 159 * @param algorithm the message digest algorithm 160 * @param data the checksum byte-array data 161 * @return URI 162 */ 163 public static URI asURI(final String algorithm, final byte[] data) { 164 return asURI(algorithm, asString(data)); 165 } 166 167 /** 168 * Given a digest URI, get the corresponding MessageDigest algorithm 169 * @param digestUri the digest uri 170 * @return MessageDigest algorithm 171 */ 172 public static String getAlgorithm(final URI digestUri) { 173 if (digestUri == null) { 174 return DEFAULT_ALGORITHM; 175 } 176 return DIGEST_ALGORITHM.fromScheme(digestUri.getScheme() + ":" + 177 digestUri.getSchemeSpecificPart().split(":", 2)[0]).algorithm; 178 } 179 180 private static String asString(final byte[] data) { 181 return encodeHexString(data); 182 } 183 184 /** 185 * Placeholder checksum value. 186 * @return URI 187 */ 188 public static URI missingChecksum() { 189 return asURI(SHA1.algorithm, SHA1.scheme); 190 } 191 192}