/*
* Copyright 2016 Netflix, Inc.
*
* 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 io.reactivex.netty.util;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import rx.Observable;
import rx.Observable.Transformer;
import rx.exceptions.OnErrorThrowable;
import rx.functions.Action2;
import rx.functions.Func0;
/**
* An {@link Observable.Transformer} to collect a stream of {@link ByteBuf ByteBufs} into a single
* ByteBuf. On success the receiver must release the returned ByteBuf.
* On failure this will release all received ByteBufs.
* <p>
* This Transformer should not be used with {@link io.reactivex.netty.channel.ContentSource#autoRelease()}
* as this will release the underlying collected ByteBufs before the collection is complete.
*/
public class CollectBytes implements Transformer<ByteBuf, ByteBuf> {
private final int maxBytes;
/**
* Collect all emitted ByteBufs into a single ByteBuf. This will return at most
* {@link Integer#MAX_VALUE}
* bytes. This is the upper limit of {@link ByteBuf#readableBytes()}. If more than
* Integer#MAX_VALUE bytes are received a {@link TooMuchDataException} will be emitted.
* {@link TooMuchDataException#getCause()}
* will contain an
* {@link OnErrorThrowable.OnNextValue} with the bytes accumulated before the exception
* was thrown.
*/
public static CollectBytes all() {
return upTo(Integer.MAX_VALUE);
}
/**
* Collect all emitted ByteBufs into a single ByteBuf until maxBytes have
* been collected. If more than maxBytes are received this will unsubscribe from
* the upstream Observable and will emit a
* {@link TooMuchDataException}. {@link TooMuchDataException#getCause()}
* will contain an
* {@link OnErrorThrowable.OnNextValue} with the bytes accumulated before the exception
* was thrown.
* @param maxBytes the maximum number of bytes to read
* @throws IllegalArgumentException when maxBytes is negative
*/
public static CollectBytes upTo(int maxBytes) {
return new CollectBytes(maxBytes);
}
private CollectBytes(int maxBytes) {
if (maxBytes < 0) {
throw new IllegalArgumentException("maxBytes must not be negative");
}
this.maxBytes = maxBytes;
}
@Override
public Observable<ByteBuf> call(Observable<ByteBuf> upstream) {
return upstream
.collect(
new Func0<CompositeByteBuf>() {
@Override
public CompositeByteBuf call() {
return Unpooled.compositeBuffer();
}
},
new Action2<CompositeByteBuf, ByteBuf>() {
@Override
public void call(CompositeByteBuf collector, ByteBuf buf) {
long newLength = collector.readableBytes() + buf.readableBytes();
if (newLength <= maxBytes) {
collector.addComponent(true, buf);
} else {
collector.release();
buf.release();
throw new TooMuchDataException("More than " + maxBytes + "B received");
}
}
}
)
.cast(ByteBuf.class);
}
public static class TooMuchDataException extends RuntimeException {
public TooMuchDataException(String message) {
super(message);
}
}
}