5 changed files with 38 additions and 284 deletions
@ -1,240 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2016 the original author or 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.springframework.core.codec.support; |
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; |
|
||||||
import java.util.concurrent.atomic.AtomicLongFieldUpdater; |
|
||||||
|
|
||||||
import org.reactivestreams.Publisher; |
|
||||||
import org.reactivestreams.Subscriber; |
|
||||||
import reactor.core.publisher.Flux; |
|
||||||
import reactor.core.subscriber.SubscriberBarrier; |
|
||||||
import reactor.core.util.BackpressureUtils; |
|
||||||
|
|
||||||
import org.springframework.core.ResolvableType; |
|
||||||
import org.springframework.core.codec.Decoder; |
|
||||||
import org.springframework.core.io.buffer.DataBuffer; |
|
||||||
import org.springframework.core.io.buffer.DataBufferAllocator; |
|
||||||
import org.springframework.util.Assert; |
|
||||||
import org.springframework.util.MimeType; |
|
||||||
|
|
||||||
/** |
|
||||||
* Abstract {@link Decoder} that plugs a {@link SubscriberBarrier} into the {@code Flux} |
|
||||||
* pipeline in order to apply splitting/aggregation operations on the stream of data. |
|
||||||
* |
|
||||||
* @author Brian Clozel |
|
||||||
*/ |
|
||||||
public abstract class AbstractRawByteStreamDecoder<T> extends AbstractDecoder<T> { |
|
||||||
|
|
||||||
private final DataBufferAllocator allocator; |
|
||||||
|
|
||||||
public AbstractRawByteStreamDecoder(DataBufferAllocator allocator, |
|
||||||
MimeType... supportedMimeTypes) { |
|
||||||
super(supportedMimeTypes); |
|
||||||
Assert.notNull(allocator, "'allocator' must not be null"); |
|
||||||
|
|
||||||
this.allocator = allocator; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public Flux<T> decode(Publisher<DataBuffer> inputStream, ResolvableType type, |
|
||||||
MimeType mimeType, Object... hints) { |
|
||||||
|
|
||||||
return decodeInternal(Flux.from(inputStream).lift(bbs -> subscriberBarrier(bbs)), |
|
||||||
type, mimeType, hints); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Create a {@link SubscriberBarrier} instance that will be plugged into the Publisher pipeline |
|
||||||
* |
|
||||||
* <p>Implementations should provide their own {@link SubscriberBarrier} or use one of the |
|
||||||
* provided implementations by this class
|
|
||||||
*/ |
|
||||||
public abstract SubscriberBarrier<DataBuffer, DataBuffer> subscriberBarrier( |
|
||||||
Subscriber<? super DataBuffer> subscriber); |
|
||||||
|
|
||||||
public abstract Flux<T> decodeInternal(Publisher<DataBuffer> inputStream, |
|
||||||
ResolvableType type |
|
||||||
, MimeType mimeType, Object... hints); |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* {@code SubscriberBarrier} implementation that buffers all received elements and emits a single |
|
||||||
* {@code DataBuffer} once the incoming stream has been completed |
|
||||||
*/ |
|
||||||
public static class ReduceSingleByteStreamBarrier |
|
||||||
extends SubscriberBarrier<DataBuffer, DataBuffer> { |
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes") |
|
||||||
static final AtomicLongFieldUpdater<ReduceSingleByteStreamBarrier> REQUESTED = |
|
||||||
AtomicLongFieldUpdater.newUpdater(ReduceSingleByteStreamBarrier.class, "requested"); |
|
||||||
|
|
||||||
static final AtomicIntegerFieldUpdater<ReduceSingleByteStreamBarrier> TERMINATED = |
|
||||||
AtomicIntegerFieldUpdater.newUpdater(ReduceSingleByteStreamBarrier.class, "terminated"); |
|
||||||
|
|
||||||
private volatile long requested; |
|
||||||
|
|
||||||
private volatile int terminated; |
|
||||||
|
|
||||||
private DataBuffer buffer; |
|
||||||
|
|
||||||
public ReduceSingleByteStreamBarrier(Subscriber<? super DataBuffer> subscriber, |
|
||||||
DataBufferAllocator allocator) { |
|
||||||
super(subscriber); |
|
||||||
this.buffer = allocator.allocateBuffer(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected void doRequest(long n) { |
|
||||||
BackpressureUtils.getAndAdd(REQUESTED, this, n); |
|
||||||
if (TERMINATED.compareAndSet(this, 1, 2)) { |
|
||||||
drainLast(); |
|
||||||
} |
|
||||||
else { |
|
||||||
super.doRequest(Long.MAX_VALUE); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected void doComplete() { |
|
||||||
if (TERMINATED.compareAndSet(this, 0, 1)) { |
|
||||||
drainLast(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
* TODO: when available, wrap buffers with a single buffer and avoid copying data for every method call. |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
protected void doNext(DataBuffer dataBuffer) { |
|
||||||
this.buffer.write(dataBuffer); |
|
||||||
} |
|
||||||
|
|
||||||
protected void drainLast() { |
|
||||||
if (BackpressureUtils.getAndSub(REQUESTED, this, 1L) > 0) { |
|
||||||
subscriber.onNext(this.buffer); |
|
||||||
super.doComplete(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* {@code SubscriberBarrier} implementation that splits incoming elements |
|
||||||
* using line return delimiters: {@code "\n"} and {@code "\r\n"} |
|
||||||
*/ |
|
||||||
public static class SplitLinesByteStreamBarrier |
|
||||||
extends SubscriberBarrier<DataBuffer, DataBuffer> { |
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes") |
|
||||||
static final AtomicLongFieldUpdater<SplitLinesByteStreamBarrier> REQUESTED = |
|
||||||
AtomicLongFieldUpdater.newUpdater(SplitLinesByteStreamBarrier.class, "requested"); |
|
||||||
|
|
||||||
static final AtomicIntegerFieldUpdater<SplitLinesByteStreamBarrier> TERMINATED = |
|
||||||
AtomicIntegerFieldUpdater.newUpdater(SplitLinesByteStreamBarrier.class, "terminated"); |
|
||||||
|
|
||||||
private final DataBufferAllocator allocator; |
|
||||||
|
|
||||||
|
|
||||||
private volatile long requested; |
|
||||||
|
|
||||||
private volatile int terminated; |
|
||||||
|
|
||||||
private DataBuffer buffer; |
|
||||||
|
|
||||||
public SplitLinesByteStreamBarrier(Subscriber<? super DataBuffer> subscriber, |
|
||||||
DataBufferAllocator allocator) { |
|
||||||
super(subscriber); |
|
||||||
this.allocator = allocator; |
|
||||||
this.buffer = allocator.allocateBuffer(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected void doRequest(long n) { |
|
||||||
BackpressureUtils.getAndAdd(REQUESTED, this, n); |
|
||||||
if (TERMINATED.compareAndSet(this, 1, 2)) { |
|
||||||
drainLast(); |
|
||||||
} |
|
||||||
else { |
|
||||||
super.doRequest(n); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected void doComplete() { |
|
||||||
if (TERMINATED.compareAndSet(this, 0, 1)) { |
|
||||||
drainLast(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
* TODO: when available, wrap buffers with a single buffer and avoid copying data for every method call. |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
protected void doNext(DataBuffer dataBuffer) { |
|
||||||
this.buffer.write(dataBuffer); |
|
||||||
|
|
||||||
while (REQUESTED.get(this) > 0) { |
|
||||||
int separatorIndex = findEndOfLine(this.buffer); |
|
||||||
if (separatorIndex != -1) { |
|
||||||
if (BackpressureUtils.getAndSub(REQUESTED, this, 1L) > 0) { |
|
||||||
byte[] message = new byte[separatorIndex]; |
|
||||||
this.buffer.read(message); |
|
||||||
consumeSeparator(this.buffer); |
|
||||||
// this.buffer = this.buffer.slice();
|
|
||||||
DataBuffer buffer2 = allocator.allocateBuffer(message.length); |
|
||||||
buffer2.write(message); |
|
||||||
super.doNext(buffer2); |
|
||||||
} |
|
||||||
} |
|
||||||
else { |
|
||||||
super.doRequest(1); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected int findEndOfLine(DataBuffer buffer) { |
|
||||||
|
|
||||||
final int n = buffer.readableByteCount(); |
|
||||||
for (int i = 0; i < n; i++) { |
|
||||||
final byte b = buffer.get(i); |
|
||||||
if (b == '\n') { |
|
||||||
return i; |
|
||||||
} |
|
||||||
else if (b == '\r' && i < n - 1 && buffer.get(i + 1) == '\n') { |
|
||||||
return i; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return -1; |
|
||||||
} |
|
||||||
|
|
||||||
protected void consumeSeparator(DataBuffer buffer) { |
|
||||||
byte sep = buffer.read(); |
|
||||||
if (sep == '\r') { |
|
||||||
buffer.read(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected void drainLast() { |
|
||||||
if (BackpressureUtils.getAndSub(REQUESTED, this, 1L) > 0) { |
|
||||||
subscriber.onNext(this.buffer); |
|
||||||
super.doComplete(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
Loading…
Reference in new issue