/**
* Copyright 2016 StreamSets Inc.
*
* Licensed under the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 com.streamsets.pipeline.lib.io.fileref;
import com.google.common.collect.ImmutableMap;
import com.google.common.hash.Hasher;
import com.streamsets.pipeline.api.impl.Utils;
import com.streamsets.pipeline.lib.generator.StreamCloseEventHandler;
import com.streamsets.pipeline.lib.hashing.HashingUtil;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* The Implementation of {@link AbstractWrapperStream} which uses a checksum algorithm
* to calculate the checksum of the stream which is being read.
*
* The consumers of {@link ChecksumCalculatingWrapperStream} can call {@link #getCalculatedChecksum()}
* after calling {@link #close()} to get the calculated checksum of the stream.
*
* @param <T> Stream implementation of {@link AutoCloseable}
*/
class ChecksumCalculatingWrapperStream<T extends AutoCloseable> extends AbstractWrapperStream<T> {
private final Hasher hasher;
private final HashingUtil.HashType checksumAlgorithm;
private final StreamCloseEventHandler streamCloseEventHandler;
private boolean isCalculated;
private String calculatedChecksum;
ChecksumCalculatingWrapperStream(
T stream,
HashingUtil.HashType checksumAlgorithm,
StreamCloseEventHandler streamCloseEventHandler
) {
super(stream);
Utils.checkNotNull(checksumAlgorithm, "checksumAlgorithm");
this.checksumAlgorithm = checksumAlgorithm;
hasher = HashingUtil.getHasher(checksumAlgorithm).newHasher();
isCalculated = false;
this.streamCloseEventHandler = streamCloseEventHandler;
}
private void updateChecksum(byte[] b, int offset, int len) {
if (len > 0) {
hasher.putBytes(b, offset, len);
}
}
@Override
public int read() throws IOException {
int readByte = super.read();
if (readByte != -1) {
hasher.putByte((byte) readByte);
}
return readByte;
}
@Override
public int read(ByteBuffer dst) throws IOException {
int bytesRead = super.read(dst);
if (bytesRead > 0) {
ByteBuffer readOnlyBuffer = dst.asReadOnlyBuffer();
readOnlyBuffer.flip();
readOnlyBuffer = readOnlyBuffer.slice();
byte[] b;
if (readOnlyBuffer.hasArray()) {
b = readOnlyBuffer.array();
} else {
b = new byte[bytesRead];
readOnlyBuffer.get(b);
}
updateChecksum(b, 0, bytesRead);
}
return bytesRead;
}
@Override
public int read(byte[] b) throws IOException {
int bytesRead = super.read(b);
updateChecksum(b, 0, bytesRead);
return bytesRead;
}
@Override
public int read(byte[] b, int offset, int len) throws IOException {
int bytesRead = super.read(b, offset, len);
updateChecksum(b, offset, bytesRead);
return bytesRead;
}
@Override
@SuppressWarnings("unchecked")
public void close() throws IOException {
if (!isCalculated) {
//toString returns the hex string representation.
calculatedChecksum = hasher.hash().toString();
isCalculated = true;
if (streamCloseEventHandler != null) {
streamCloseEventHandler.handleCloseEvent(
new ImmutableMap.Builder<String, Object>()
.put(FileRefUtil.WHOLE_FILE_CHECKSUM, getCalculatedChecksum())
.put(FileRefUtil.WHOLE_FILE_CHECKSUM_ALGO, checksumAlgorithm)
.build()
);
}
}
super.close();
}
String getCalculatedChecksum() {
Utils.checkState(isCalculated, "Checksum not calculated until close() is called.");
return calculatedChecksum;
}
}