/*
* Copyright 2016 The Simple File Server Authors
*
* 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.sfs.nodes.compute.object;
import com.google.common.base.Optional;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.logging.Logger;
import org.sfs.Server;
import org.sfs.VertxContext;
import org.sfs.encryption.ContainerKeys;
import org.sfs.io.BufferEndableWriteStream;
import org.sfs.io.NoEndEndableWriteStream;
import org.sfs.nodes.all.segment.GetSegmentReadStream;
import org.sfs.vo.Segment;
import org.sfs.vo.TransientBlobReference;
import org.sfs.vo.TransientSegment;
import org.sfs.vo.XVersion;
import rx.Observable;
import rx.functions.Func1;
import static com.google.common.base.Preconditions.checkState;
import static io.vertx.core.logging.LoggerFactory.getLogger;
import static java.lang.Boolean.TRUE;
import static org.sfs.io.AsyncIO.end;
import static org.sfs.rx.Defer.just;
import static org.sfs.rx.RxHelper.iterate;
import static org.sfs.vo.Segment.SegmentCipher;
public class CopySegmentsReadStreams implements Func1<Iterable<TransientSegment>, Observable<Iterable<TransientSegment>>> {
private static final Logger LOGGER = getLogger(CopySegmentsReadStreams.class);
private final VertxContext<Server> vertxContext;
private final BufferEndableWriteStream writeStream;
private final boolean verifyChecksum;
public CopySegmentsReadStreams(VertxContext<Server> vertxContext, BufferEndableWriteStream writeStream, boolean verifyChecksum) {
this.vertxContext = vertxContext;
this.writeStream = writeStream;
this.verifyChecksum = verifyChecksum;
}
@Override
public Observable<Iterable<TransientSegment>> call(Iterable<TransientSegment> transientSegments) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("begin copy segment read streams");
}
Vertx vertx = vertxContext.vertx();
return iterate(
vertx,
transientSegments,
transientSegment -> {
if (!transientSegment.isTinyData()) {
return just(transientSegment)
.flatMap(new GetSegmentReadStream(vertxContext, verifyChecksum))
.doOnNext(oHolder -> {
if (!oHolder.isPresent()) {
throw new SegmentReadStreamNotFoundException(String.format("Failed to find ReadStream for segment %d from object %s %s", transientSegment.getId(), transientSegment.getParent().getParent().getId(), transientSegment.getParent().getParent().toJsonObject().encodePrettily()));
}
})
.map(Optional::get)
.flatMap(holder -> {
TransientBlobReference transientBlobReference = holder.value0();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("begin copy of blob reference object=" + transientBlobReference.getSegment().getParent().getParent().getId() + ", version=" + transientBlobReference.getSegment().getParent().getId() + ", segment=" + transientBlobReference.getSegment().getId() + ", volume=" + transientBlobReference.getVolumeId() + ", position=" + transientBlobReference.getPosition());
}
return prepareWriteStream(new NoEndEndableWriteStream(writeStream), transientSegment)
.flatMap(writeStream -> holder.value1().produce(writeStream))
.doOnNext(aVoid -> {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("end copy of blob reference object=" + transientBlobReference.getSegment().getParent().getParent().getId() + ", version=" + transientBlobReference.getSegment().getParent().getId() + ", segment=" + transientBlobReference.getSegment().getId() + ", volume=" + transientBlobReference.getVolumeId() + ", position=" + transientBlobReference.getPosition());
}
});
})
.map(aVoid -> true);
} else {
Buffer tinyData = Buffer.buffer(transientSegment.getTinyData());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("begin copy of blob reference object=" + transientSegment.getParent().getParent().getId() + ", version=" + transientSegment.getParent().getId() + ", segment=" + transientSegment.getId());
}
return prepareWriteStream(new NoEndEndableWriteStream(writeStream), transientSegment)
.flatMap(writeStream -> end(tinyData, writeStream))
.doOnNext(aVoid -> {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("bend copy of blob reference object=" + transientSegment.getParent().getParent().getId() + ", version=" + transientSegment.getParent().getId() + ", segment=" + transientSegment.getId());
}
})
.map(aVoid -> true);
}
}
)
.map(_continue -> {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("end copy segment read streams");
}
return transientSegments;
});
}
public Observable<BufferEndableWriteStream> prepareWriteStream(
BufferEndableWriteStream delegateWriteStream, Segment<? extends Segment> segment) {
final XVersion<? extends XVersion> transientVersion = segment.getParent();
Optional<Boolean> oServerSideEncryption = transientVersion.getServerSideEncryption();
boolean serverSideEncryption = oServerSideEncryption.isPresent() && TRUE.equals(oServerSideEncryption.get());
if (serverSideEncryption) {
Optional<SegmentCipher> oSegmentCipher = segment.getSegmentCipher();
SegmentCipher segmentCipher = oSegmentCipher.get();
Optional<String> oContainerKeyId = segmentCipher.getContainerKeyId();
checkState(oContainerKeyId.isPresent(), "SegmentCipher missing ContainerKeyId for Object %s", transientVersion.getId());
String containerKeyId = oContainerKeyId.get();
Optional<byte[]> oSalt = segmentCipher.getSalt();
checkState(oSalt.isPresent(), "SegmentCipher missing salt for Object %s", transientVersion.getId());
final byte[] salt = oSalt.get();
ContainerKeys containerKeys = vertxContext.verticle().containerKeys();
return containerKeys.algorithm(vertxContext, transientVersion.getParent().getParent(), containerKeyId, salt)
.map(keyResponse -> keyResponse.getData().decrypt(delegateWriteStream));
} else {
return Observable.just(delegateWriteStream);
}
}
public static class SegmentReadStreamNotFoundException extends RuntimeException {
public SegmentReadStreamNotFoundException(String message) {
super(message);
}
}
}