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;
027
028import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
029import org.slf4j.Logger;
030
031/**
032 * Digest helpers to convert digests (checksums) into URI strings
033 * (based loosely on Magnet URIs)
034 * @author Chris Beer
035 * @since Mar 6, 2013
036 */
037public final class ContentDigest {
038
039    private static final Logger LOGGER = getLogger(ContentDigest.class);
040
041    public enum DIGEST_ALGORITHM {
042        SHA1("SHA-1", "urn:sha1"), SHA256("SHA-256", "urn:sha256"), MD5("MD5", "urn:md5"), MISSING("NONE", "missing");
043
044        final public String algorithm;
045        final private String scheme;
046
047        DIGEST_ALGORITHM(final String alg, final String scheme) {
048            this.algorithm = alg;
049            this.scheme = scheme;
050        }
051
052        /**
053         * Return the scheme associated with the provided algorithm (e.g. SHA-1 returns urn:sha1)
054         *
055         * @param alg for which scheme is requested
056         * @return scheme
057         */
058        public static String getScheme(final String alg) {
059            return Arrays.stream(values()).filter(value ->
060                    value.algorithm.equalsIgnoreCase(alg) || value.algorithm.replace("-", "").equalsIgnoreCase(alg)
061            ).findFirst().orElse(MISSING).scheme;
062        }
063
064        /**
065         * Return enum value for the provided scheme (e.g. urn:sha1 returns SHA-1)
066         *
067         * @param argScheme for which enum is requested
068         * @return enum value associated with the arg scheme
069         */
070        public static DIGEST_ALGORITHM fromScheme(final String argScheme) {
071            return Arrays.stream(values()).filter(value -> value.scheme.equalsIgnoreCase(argScheme)
072            ).findFirst().orElse(MISSING);
073        }
074
075        /**
076         * Return true if the provided algorithm is included in this enum
077         *
078         * @param alg to test
079         * @return true if arg algorithm is supported
080         */
081        public static boolean isSupportedAlgorithm(final String alg) {
082            return !getScheme(alg).equals(MISSING.scheme);
083        }
084    }
085
086    public static final String DEFAULT_ALGORITHM = DIGEST_ALGORITHM.SHA1.algorithm;
087
088    private ContentDigest() {
089    }
090
091    /**
092     * Convert a MessageDigest algorithm and checksum value to a URN
093     * @param algorithm the message digest algorithm
094     * @param value the checksum value
095     * @return URI
096     */
097    public static URI asURI(final String algorithm, final String value) {
098        try {
099            final String scheme = DIGEST_ALGORITHM.getScheme(algorithm);
100
101            return new URI(scheme, value, null);
102        } catch (final URISyntaxException unlikelyException) {
103            LOGGER.warn("Exception creating checksum URI: {}",
104                               unlikelyException);
105            throw new RepositoryRuntimeException(unlikelyException);
106        }
107    }
108
109    /**
110     * Convert a MessageDigest algorithm and checksum byte-array data to a URN
111     * @param algorithm the message digest algorithm
112     * @param data the checksum byte-array data
113     * @return URI
114     */
115    public static URI asURI(final String algorithm, final byte[] data) {
116        return asURI(algorithm, asString(data));
117    }
118
119    /**
120     * Given a digest URI, get the corresponding MessageDigest algorithm
121     * @param digestUri the digest uri
122     * @return MessageDigest algorithm
123     */
124    public static String getAlgorithm(final URI digestUri) {
125        if (digestUri == null) {
126            return DEFAULT_ALGORITHM;
127        }
128        return DIGEST_ALGORITHM.fromScheme(digestUri.getScheme() + ":" +
129             digestUri.getSchemeSpecificPart().split(":", 2)[0]).algorithm;
130    }
131
132    private static String asString(final byte[] data) {
133        return encodeHexString(data);
134    }
135
136    /**
137     * Placeholder checksum value.
138     * @return URI
139     */
140    public static URI missingChecksum() {
141        return asURI(SHA1.algorithm, SHA1.scheme);
142    }
143
144}