/*
* Copyright 2016 Christoph Böhme
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.culturegraph.mf.files;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.culturegraph.mf.framework.FluxCommand;
import org.culturegraph.mf.framework.MetafactureException;
import org.culturegraph.mf.framework.ObjectReceiver;
import org.culturegraph.mf.framework.annotations.Description;
import org.culturegraph.mf.framework.annotations.In;
import org.culturegraph.mf.framework.annotations.Out;
import org.culturegraph.mf.framework.helpers.DefaultObjectPipe;
import org.culturegraph.mf.framework.objects.Triple;
/**
* Interprets the input string as a file name and computes a cryptographic hash
* for the file.
*
* @author Christoph Böhme
*
*/
@Description("Uses the input string as a file name and computes a cryptographic hash the file")
@In(String.class)
@Out(Triple.class)
@FluxCommand("digest-file")
public final class FileDigestCalculator extends
DefaultObjectPipe<String, ObjectReceiver<Triple>> {
private static final int BUFFER_SIZE = 1024;
private static final int HIGH_NIBBLE = 0xf0;
private static final int LOW_NIBBLE = 0x0f;
private static final char[] NIBBLE_TO_HEX =
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
private final DigestAlgorithm algorithm;
private final MessageDigest messageDigest;
public FileDigestCalculator(final DigestAlgorithm algorithm) {
this.algorithm = algorithm;
this.messageDigest = this.algorithm.getInstance();
}
public FileDigestCalculator(final String algorithm) {
this.algorithm = DigestAlgorithm.valueOf(algorithm.toUpperCase());
this.messageDigest = this.algorithm.getInstance();
}
@Override
public void process(final String file) {
final String digest;
InputStream stream = null;
try {
stream = new FileInputStream(file);
digest = bytesToHex(getDigest(stream, messageDigest));
} catch (IOException e) {
throw new MetafactureException(e);
} finally {
if (stream != null) {
try { stream.close(); }
catch (final IOException e) { }
}
}
getReceiver().process(new Triple(file, algorithm.name(), digest));
}
private static byte[] getDigest(final InputStream stream, final MessageDigest messageDigest) throws IOException {
final byte[] buffer = new byte[BUFFER_SIZE];
int read = stream.read(buffer, 0, BUFFER_SIZE);
while (read > -1) {
messageDigest.update(buffer, 0, read);
read = stream.read(buffer, 0, BUFFER_SIZE);
}
return messageDigest.digest();
}
private static String bytesToHex(final byte[] bytes) {
final char[] hex = new char[bytes.length * 2];
for (int i=0; i < bytes.length; ++i) {
hex[i * 2] = NIBBLE_TO_HEX[(bytes[i] & HIGH_NIBBLE) >>> 4];
hex[i * 2 + 1] = NIBBLE_TO_HEX[bytes[i] & LOW_NIBBLE];
}
return new String(hex);
}
/**
* Message digests which can be used by modules.
*
* @author Christoph Böhme
*/
public enum DigestAlgorithm {
MD2("MD2"),
MD5("MD5"),
SHA1("SHA-1"),
SHA256("SHA-256"),
SHA384("SHA-384"),
SHA512 ("SHA-512");
private final String identifier;
private DigestAlgorithm(final String identifier) {
this.identifier = identifier;
}
public MessageDigest getInstance() {
try {
return MessageDigest.getInstance(identifier);
} catch (NoSuchAlgorithmException e) {
throw new MetafactureException (e);
}
}
}
}