Add DataBufferUtils.compose
Added a utility method that composes data buffers into a single buffer. Depending on the `DataBuffer` implementation, the returned buffer may be a single buffer containing all data of the provided buffers, or it may be a true composite that contains references to the buffers. Issue: SPR-16365
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
package org.springframework.core.io.buffer;
|
package org.springframework.core.io.buffer;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory for {@link DataBuffer}s, allowing for allocation and wrapping of
|
* A factory for {@link DataBuffer}s, allowing for allocation and wrapping of
|
||||||
@@ -61,4 +62,14 @@ public interface DataBufferFactory {
|
|||||||
*/
|
*/
|
||||||
DataBuffer wrap(byte[] bytes);
|
DataBuffer wrap(byte[] bytes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a composite data buffer from the list of provided data buffers. Depending on the
|
||||||
|
* implementation, the returned buffer may be a single buffer containing all data of the
|
||||||
|
* provided buffers, or it may be a true composite that contains references to the buffers.
|
||||||
|
* <p>Note that the given data buffers do <strong>not</strong> have to be released, as they are
|
||||||
|
* released as part of the returned composite.
|
||||||
|
* @param dataBuffers the data buffers to be composed
|
||||||
|
* @return a buffer that composes {@code dataBuffers} into one
|
||||||
|
*/
|
||||||
|
DataBuffer compose(List<DataBuffer> dataBuffers);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import org.reactivestreams.Subscription;
|
|||||||
import reactor.core.publisher.BaseSubscriber;
|
import reactor.core.publisher.BaseSubscriber;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.FluxSink;
|
import reactor.core.publisher.FluxSink;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.publisher.SynchronousSink;
|
import reactor.core.publisher.SynchronousSink;
|
||||||
|
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
@@ -434,6 +435,27 @@ public abstract class DataBufferUtils {
|
|||||||
return WRITE_AGGREGATOR;
|
return WRITE_AGGREGATOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composes the buffers in the given {@link Publisher} into a single data buffer. Depending on
|
||||||
|
* the {@code DataBuffer} implementation, the returned buffer may be a single buffer containing
|
||||||
|
* all data of the provided buffers, or it may be a true composite that contains references to
|
||||||
|
* the buffers.
|
||||||
|
* @param publisher the data buffers that are to be composed
|
||||||
|
* @return the composed data buffer
|
||||||
|
*/
|
||||||
|
public static Mono<DataBuffer> compose(Publisher<DataBuffer> publisher) {
|
||||||
|
Assert.notNull(publisher, "'publisher' must not be null");
|
||||||
|
|
||||||
|
Flux<DataBuffer> source = Flux.from(publisher);
|
||||||
|
|
||||||
|
return source.collectList()
|
||||||
|
.filter(dataBuffers -> !dataBuffers.isEmpty())
|
||||||
|
.map(dataBuffers -> {
|
||||||
|
DataBufferFactory bufferFactory = dataBuffers.get(0).factory();
|
||||||
|
return bufferFactory.compose(dataBuffers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class ReadableByteChannelGenerator
|
private static class ReadableByteChannelGenerator
|
||||||
implements BiFunction<ReadableByteChannel, SynchronousSink<DataBuffer>, ReadableByteChannel> {
|
implements BiFunction<ReadableByteChannel, SynchronousSink<DataBuffer>, ReadableByteChannel> {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
package org.springframework.core.io.buffer;
|
package org.springframework.core.io.buffer;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
@@ -102,6 +103,23 @@ public class DefaultDataBufferFactory implements DataBufferFactory {
|
|||||||
return DefaultDataBuffer.fromFilledByteBuffer(this, wrapper);
|
return DefaultDataBuffer.fromFilledByteBuffer(this, wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* <p>This implementation creates a single {@link DefaultDataBuffer} to contain the data
|
||||||
|
* in {@code dataBuffers}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public DataBuffer compose(List<DataBuffer> dataBuffers) {
|
||||||
|
Assert.notEmpty(dataBuffers, "'dataBuffers' must not be empty");
|
||||||
|
|
||||||
|
int capacity = dataBuffers.stream()
|
||||||
|
.mapToInt(DataBuffer::readableByteCount)
|
||||||
|
.sum();
|
||||||
|
DefaultDataBuffer dataBuffer = allocateBuffer(capacity);
|
||||||
|
return dataBuffers.stream()
|
||||||
|
.reduce(dataBuffer, DataBuffer::write);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "DefaultDataBufferFactory (preferDirect=" + this.preferDirect + ")";
|
return "DefaultDataBufferFactory (preferDirect=" + this.preferDirect + ")";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -17,9 +17,11 @@
|
|||||||
package org.springframework.core.io.buffer;
|
package org.springframework.core.io.buffer;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
|
import io.netty.buffer.CompositeByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@@ -80,6 +82,22 @@ public class NettyDataBufferFactory implements DataBufferFactory {
|
|||||||
return new NettyDataBuffer(byteBuf, this);
|
return new NettyDataBuffer(byteBuf, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* <p>This implementation uses Netty's {@link CompositeByteBuf}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public DataBuffer compose(List<DataBuffer> dataBuffers) {
|
||||||
|
Assert.notNull(dataBuffers, "'dataBuffers' must not be null");
|
||||||
|
CompositeByteBuf composite = this.byteBufAllocator.compositeBuffer(dataBuffers.size());
|
||||||
|
for (DataBuffer dataBuffer : dataBuffers) {
|
||||||
|
Assert.isInstanceOf(NettyDataBuffer.class, dataBuffer);
|
||||||
|
NettyDataBuffer nettyDataBuffer = (NettyDataBuffer) dataBuffer;
|
||||||
|
composite.addComponent(true, nettyDataBuffer.getNativeBuffer());
|
||||||
|
}
|
||||||
|
return new NettyDataBuffer(composite, this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap the given Netty {@link ByteBuf} in a {@code NettyDataBuffer}.
|
* Wrap the given Netty {@link ByteBuf} in a {@code NettyDataBuffer}.
|
||||||
* @param byteBuf the Netty byte buffer to wrap
|
* @param byteBuf the Netty byte buffer to wrap
|
||||||
|
|||||||
@@ -479,5 +479,18 @@ public class DataBufferTests extends AbstractDataBufferAllocatingTestCase {
|
|||||||
release(buffer);
|
release(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void composite() {
|
||||||
|
DataBuffer composite = this.bufferFactory.compose(Arrays.asList(stringBuffer("a"),
|
||||||
|
stringBuffer("b"), stringBuffer("c")));
|
||||||
|
assertEquals(3, composite.readableByteCount());
|
||||||
|
byte[] bytes = new byte[3];
|
||||||
|
composite.read(bytes);
|
||||||
|
|
||||||
|
assertArrayEquals(new byte[] {'a','b','c'}, bytes);
|
||||||
|
|
||||||
|
release(composite);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -351,5 +351,19 @@ public class DataBufferUtilsTests extends AbstractDataBufferAllocatingTestCase {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compose() {
|
||||||
|
DataBuffer foo = stringBuffer("foo");
|
||||||
|
DataBuffer bar = stringBuffer("bar");
|
||||||
|
DataBuffer baz = stringBuffer("baz");
|
||||||
|
Flux<DataBuffer> flux = Flux.just(foo, bar, baz);
|
||||||
|
|
||||||
|
DataBuffer result = DataBufferUtils.compose(flux).block(Duration.ofSeconds(5));
|
||||||
|
|
||||||
|
assertEquals("foobarbaz", DataBufferTestUtils.dumpString(result, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
release(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user