package diskCacheV111.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Set;
import org.dcache.pool.repository.RepositoryChannel;
import org.dcache.util.Checksum;
import org.dcache.util.ChecksumType;
import static org.dcache.util.ByteUnit.BYTES;
import static org.dcache.util.ByteUnit.KiB;
public abstract class ChecksumFactory
{
public abstract ChecksumType getType();
public abstract MessageDigest create();
public abstract Checksum create(byte [] digest);
public abstract Checksum create(String stringDigest);
public abstract Checksum find(Set<Checksum> checksums);
public abstract Checksum computeChecksum(RepositoryChannel channel)
throws IOException, InterruptedException;
/**
* Compute the checksum for a file with a limit on how many bytes/second to
* checksum.
* @param file the file to compute a checksum for.
* @param throughputLimit a limit on how many bytes/second that may be
* checksummed.
* @return the computed checksum.
* @throws IOException
* @throws InterruptedException
*/
public abstract Checksum computeChecksum(RepositoryChannel file, double throughputLimit)
throws IOException, InterruptedException;
public static ChecksumFactory getFactory(ChecksumType type)
throws NoSuchAlgorithmException
{
return new GenericIdChecksumFactory(type);
}
/**
* Returns a ChecksumFactory for the first supported checksum.
*
* @param preferredChecksums Ordered list of checksums
* @param defaultType Default type used when none of the preferred types are supported
*/
public static ChecksumFactory getFactory(Iterable<Checksum> preferredChecksums, ChecksumType defaultType)
throws NoSuchAlgorithmException
{
for (Checksum checksum : preferredChecksums) {
try {
return getFactory(checksum.getType());
} catch (NoSuchAlgorithmException ignored) {
}
}
return getFactory(defaultType);
}
public static ChecksumFactory getFactoryFor(Checksum checksum)
throws NoSuchAlgorithmException
{
return getFactory(checksum.getType());
}
public static void main( String [] args ) throws Exception {
System.out.println("Getting MD4 first time");
ChecksumFactory.getFactory(ChecksumType.MD4_TYPE);
System.out.println("Getting MD4 second time");
ChecksumFactory.getFactory(ChecksumType.MD5_TYPE);
}
}
class GenericIdChecksumFactory extends ChecksumFactory
{
private static final Logger _log =
LoggerFactory.getLogger(GenericIdChecksumFactory.class);
private static final long MILLISECONDS_IN_SECOND = 1000;
private final ChecksumType _type;
public GenericIdChecksumFactory(ChecksumType type)
throws NoSuchAlgorithmException
{
_type = type;
if (_type != ChecksumType.MD5_TYPE && _type != ChecksumType.ADLER32) {
// we know we support the above too; check the rest
MessageDigest.getInstance(_type.getName());
}
}
@Override
public ChecksumType getType()
{
return _type;
}
@Override
public MessageDigest create()
{
try {
if (_type == ChecksumType.ADLER32) {
return new Adler32();
}
return MessageDigest.getInstance(_type.getName());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("This is a bug in ChecksumFactory", e);
}
}
@Override
public Checksum create(byte[] digest)
{
return new Checksum(_type, digest);
}
@Override
public Checksum create(String digest)
{
return new Checksum(_type, digest);
}
@Override
public Checksum find(Set<Checksum> checksums)
{
for (Checksum checksum: checksums) {
if (checksum.getType() == _type) {
return checksum;
}
}
return null;
}
@Override
public Checksum computeChecksum(RepositoryChannel channel) throws IOException,
InterruptedException
{
return computeChecksum(channel, Double.POSITIVE_INFINITY);
}
/**
* Compute how much to sleep for current throughput not to exceed
* <code>throughputLimit</code> given how many bytes read/written for a
* certain amount of time.
* <h3>Formula</h3>
* <p><code>throughputLimit = numBytes/(elapsedTime + adjust)</code>
* <p>gives:<p>
* <code>adjust = numBytes/throughputLimit - elapsedTime</code>
*
* @param throughputLimit max throughput (bytes/second). Must not be <= 0.
* @param numBytes no. of bytes read/written for <code>elapsedTime
* </code> milliseconds. If 0, adjust will be 0.
* @param elapsedTime elapsed time (milliseconds) when <code>numBytes
* </code> bytes were read/written.
* @return how much to sleep (milliseconds) for throughput
* not to exceed <code>throughputLimit</code>.
* Guaranteed to be >= 0.
*/
private long throughputAdjustment(double throughputLimit, long numBytes,
long elapsedTime)
{
assert throughputLimit > 0 && numBytes >= 0 && elapsedTime >= 0;
/**
* Adjust is < 0 when numBytes/elapsedTime < throughputLimit
* (-elapsedTime when throughputLimit is ∞). Adjust is 0 when numBytes
* is 0.
*/
long desiredDuration = (long) Math.ceil(MILLISECONDS_IN_SECOND *
(numBytes / throughputLimit));
long adjust = desiredDuration - elapsedTime;
return Math.max(0, adjust);
}
/**
* Return the string representation of throughput given the amount of bytes
* read/written over a certain time period.
* @param numBytes no. of bytes read/written for <code>millis</code>
* milliseconds.
* @param millis elapsed time (milliseconds) when <code>numBytes</code>
* bytes were read/written. If 0 increment by 1 to avoid
* printing Infinity or NaN.
* @return throughput in (MiB/s) as the string representation of a
* floating point number. Neither NaN or Infinity will be
* printed due to the incrementing of <code>millis</code>
* to 1 if it has a value of 0.
*/
private String throughputAsString(long numBytes, long millis)
{
return Double.toString(BYTES.toMiB((double) numBytes)
/ (( millis == 0 ? 1 : millis ) / (double) MILLISECONDS_IN_SECOND));
}
@Override
public Checksum computeChecksum(RepositoryChannel channel, double throughputLimit)
throws IOException, InterruptedException
{
long start = System.currentTimeMillis();
MessageDigest digest = create();
long pos = 0L;
ByteBuffer buffer = ByteBuffer.allocate(KiB.toBytes(64));
int rc;
while ((rc = channel.read(buffer, pos)) > 0) {
pos += rc;
buffer.flip();
digest.update(buffer);
buffer.clear();
if (Thread.interrupted()) {
throw new InterruptedException();
}
long adjust
= throughputAdjustment(throughputLimit,
pos,
System.currentTimeMillis() - start);
if (adjust > 0) {
Thread.sleep(adjust);
}
}
Checksum checksum = create(digest.digest());
_log.debug("Computed checksum, length {}, checksum {} in {} ms{}", pos, checksum,
System.currentTimeMillis() - start, pos == 0 ? ""
: ", throughput " +
throughputAsString(pos, System.currentTimeMillis() - start) +
" MiB/s" +
(Double.isInfinite(throughputLimit)
? ""
: " (limit " + BYTES.toMiB(throughputLimit) + " MiB/s)"));
return checksum;
}
}