org.springframework.messaging.rsocket.DefaultMetadataExtractor Maven / Gradle / Ivy
/*
* Copyright 2002-2019 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
*
* https://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.messaging.rsocket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.rsocket.Payload;
import io.rsocket.metadata.CompositeMetadata;
import io.rsocket.metadata.RoutingMetadata;
import io.rsocket.metadata.WellKnownMimeType;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.Decoder;
import org.springframework.core.io.buffer.NettyDataBuffer;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.MimeType;
/**
* Default {@link MetadataExtractor} implementation that relies on
* {@link Decoder}s to deserialize the content of metadata entries.
* By default only {@code "message/x.rsocket.routing.v0""} is extracted and
* saved under {@link MetadataExtractor#ROUTE_KEY}. Use {@code metadataToExtract}
* methods to specify other metadata mime types of interest to extract.
*
* @author Rossen Stoyanchev
* @since 5.2
*/
public class DefaultMetadataExtractor implements MetadataExtractor, MetadataExtractorRegistry {
private final List> decoders;
private final Map> registrations = new HashMap<>();
/**
* Constructor with decoders for de-serializing metadata entries.
*/
public DefaultMetadataExtractor(Decoder... decoders) {
this(Arrays.asList(decoders));
}
/**
* Constructor with list of decoders for de-serializing metadata entries.
*/
public DefaultMetadataExtractor(List> decoders) {
this.decoders = Collections.unmodifiableList(new ArrayList<>(decoders));
}
/**
* Return a read-only list with the configured decoders.
*/
public List> getDecoders() {
return this.decoders;
}
@Override
public void metadataToExtract(
MimeType mimeType, Class targetType, BiConsumer> mapper) {
registerMetadata(mimeType, ResolvableType.forClass(targetType), mapper);
}
@Override
public void metadataToExtract(
MimeType mimeType, ParameterizedTypeReference type, BiConsumer> mapper) {
registerMetadata(mimeType, ResolvableType.forType(type), mapper);
}
@SuppressWarnings("unchecked")
private void registerMetadata(
MimeType mimeType, ResolvableType targetType, BiConsumer> mapper) {
for (Decoder decoder : this.decoders) {
if (decoder.canDecode(targetType, mimeType)) {
this.registrations.put(mimeType.toString(),
new EntryExtractor<>((Decoder) decoder, mimeType, targetType, mapper));
return;
}
}
throw new IllegalArgumentException("No decoder for " + mimeType + " and " + targetType);
}
@Override
public Map extract(Payload payload, MimeType metadataMimeType) {
Map result = new HashMap<>();
if (metadataMimeType.toString().equals(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.toString())) {
for (CompositeMetadata.Entry entry : new CompositeMetadata(payload.metadata(), false)) {
extractEntry(entry.getContent(), entry.getMimeType(), result);
}
}
else {
extractEntry(payload.metadata().slice(), metadataMimeType.toString(), result);
}
return result;
}
private void extractEntry(ByteBuf content, @Nullable String mimeType, Map result) {
if (content.readableBytes() == 0) {
return;
}
EntryExtractor extractor = this.registrations.get(mimeType);
if (extractor != null) {
extractor.extract(content, result);
return;
}
if (mimeType != null && mimeType.equals(WellKnownMimeType.MESSAGE_RSOCKET_ROUTING.getString())) {
Iterator iterator = new RoutingMetadata(content).iterator();
if (iterator.hasNext()) {
result.put(MetadataExtractor.ROUTE_KEY, iterator.next());
}
}
}
private static class EntryExtractor {
// We only need this to wrap ByteBufs
private final static NettyDataBufferFactory bufferFactory =
new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
private final Decoder decoder;
private final MimeType mimeType;
private final ResolvableType targetType;
private final BiConsumer> accumulator;
EntryExtractor(Decoder decoder, MimeType mimeType, ResolvableType targetType,
BiConsumer> accumulator) {
this.decoder = decoder;
this.mimeType = mimeType;
this.targetType = targetType;
this.accumulator = accumulator;
}
public void extract(ByteBuf content, Map result) {
NettyDataBuffer dataBuffer = bufferFactory.wrap(content.retain());
T value = this.decoder.decode(dataBuffer, this.targetType, this.mimeType, Collections.emptyMap());
this.accumulator.accept(value, result);
}
@Override
public String toString() {
return "mimeType=" + this.mimeType + ", targetType=" + this.targetType;
}
}
}