Update support for using "." as path separator

Issue: SPR-11660
This commit is contained in:
Rossen Stoyanchev
2014-07-14 18:47:51 -04:00
parent 928a466b5d
commit ab2526a586
26 changed files with 405 additions and 254 deletions

View File

@@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
@@ -55,51 +56,38 @@ public final class DestinationPatternsMessageCondition
* @param patterns 0 or more URL patterns; if 0 the condition will match to every request.
*/
public DestinationPatternsMessageCondition(String... patterns) {
this(patterns, null, true);
this(patterns, null);
}
/**
* Additional constructor with a customized path matcher.
* Alternative constructor accepting a custom PathMatcher.
* @param patterns the URL patterns to use; if 0, the condition will match to every request.
* @param pathMatcher the customized path matcher to use with patterns
* @param pathMatcher the PathMatcher to use.
*/
public DestinationPatternsMessageCondition(String[] patterns, PathMatcher pathMatcher) {
this(asList(patterns), pathMatcher, true);
this(asList(patterns), pathMatcher);
}
/**
* Additional constructor with a customized path matcher and a flag specifying if
* the destination patterns should be prepended with "/".
* @param patterns the URL patterns to use; if 0, the condition will match to every request.
* @param pathMatcher the customized path matcher to use with patterns
* @param prependLeadingSlash to specify whether each pattern that is not empty and does not
* start with "/" will be prepended with "/" or not
* @since 4.1
*/
public DestinationPatternsMessageCondition(String[] patterns, PathMatcher pathMatcher,
boolean prependLeadingSlash) {
this(asList(patterns), pathMatcher, prependLeadingSlash);
}
private DestinationPatternsMessageCondition(Collection<String> patterns,
PathMatcher pathMatcher, boolean prependLeadingSlash) {
this.patterns = Collections.unmodifiableSet(initializePatterns(patterns, prependLeadingSlash));
private DestinationPatternsMessageCondition(Collection<String> patterns, PathMatcher pathMatcher) {
this.pathMatcher = (pathMatcher != null) ? pathMatcher : new AntPathMatcher();
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns, this.pathMatcher));
}
private static List<String> asList(String... patterns) {
return patterns != null ? Arrays.asList(patterns) : Collections.<String>emptyList();
}
private static Set<String> initializePatterns(Collection<String> patterns,
boolean prependLeadingSlash) {
private static Set<String> prependLeadingSlash(Collection<String> patterns, PathMatcher pathMatcher) {
if (patterns == null) {
return Collections.emptySet();
}
boolean slashSeparator = pathMatcher.combine("a", "a").equals("a/a");
Set<String> result = new LinkedHashSet<String>(patterns.size());
for (String pattern : patterns) {
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/") && prependLeadingSlash) {
pattern = "/" + pattern;
if (slashSeparator) {
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
pattern = "/" + pattern;
}
}
result.add(pattern);
}
@@ -149,7 +137,7 @@ public final class DestinationPatternsMessageCondition
else {
result.add("");
}
return new DestinationPatternsMessageCondition(result, this.pathMatcher, false);
return new DestinationPatternsMessageCondition(result, this.pathMatcher);
}
/**
@@ -184,7 +172,7 @@ public final class DestinationPatternsMessageCondition
}
Collections.sort(matches, this.pathMatcher.getPatternComparator(destination));
return new DestinationPatternsMessageCondition(matches, this.pathMatcher, false);
return new DestinationPatternsMessageCondition(matches, this.pathMatcher);
}
/**

View File

@@ -90,27 +90,27 @@ public abstract class AbstractMethodMessageHandler<T>
/**
* Configure one or more prefixes to match to the destinations of handled messages.
* Messages whose destination does not start with one of the configured prefixes
* are ignored. When a destination matches one of the configured prefixes, the
* matching part is removed from destination before performing a lookup for a matching
* message handling method. Prefixes without a trailing slash will have one appended
* automatically.
* <p>By default the list of prefixes is empty in which case all destinations match.
* When this property is configured only messages to destinations matching
* one of the configured prefixes are eligible for handling. When there is a
* match the prefix is removed and only the remaining part of the destination
* is used for method-mapping purposes.
*
* <p>By default no prefixes are configured in which case all messages are
* eligible for handling.
*/
public void setDestinationPrefixes(Collection<String> prefixes) {
this.destinationPrefixes.clear();
if (prefixes != null) {
for (String prefix : prefixes) {
prefix = prefix.trim();
if (!prefix.endsWith("/")) {
prefix += "/";
}
this.destinationPrefixes.add(prefix);
}
}
}
/**
* Return the configured destination prefixes.
*/
public Collection<String> getDestinationPrefixes() {
return this.destinationPrefixes;
}
@@ -346,11 +346,11 @@ public abstract class AbstractMethodMessageHandler<T>
protected abstract String getDestination(Message<?> message);
/**
* Find if the given destination matches any of the configured allowed destination
* prefixes and if a match is found return the destination with the prefix removed.
* <p>If no destination prefixes are configured, the destination is returned as is.
* @return the destination to use to find matching message handling methods
* or {@code null} if the destination does not match
* Check whether the given destination (of an incoming message) matches to
* one of the configured destination prefixes and if so return the remaining
* portion of the destination after the matched prefix.
* <p>If there are no matching prefixes, return {@code null}.
* <p>If there are no destination prefixes, return the destination as is.
*/
protected String getLookupDestination(String destination) {
if (destination == null) {
@@ -361,7 +361,7 @@ public abstract class AbstractMethodMessageHandler<T>
}
for (String prefix : this.destinationPrefixes) {
if (destination.startsWith(prefix)) {
return destination.substring(prefix.length() - 1);
return destination.substring(prefix.length());
}
}
return null;

View File

@@ -93,7 +93,9 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
private ConversionService conversionService = new DefaultFormattingConversionService();
private PathMatcher pathMatcher;
private PathMatcher pathMatcher = new AntPathMatcher();
private boolean slashPathSeparator = true;
private Validator validator;
@@ -112,22 +114,7 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
* @param brokerTemplate a messaging template to send application messages to the broker
*/
public SimpAnnotationMethodMessageHandler(SubscribableChannel clientInboundChannel,
MessageChannel clientOutboundChannel, SimpMessageSendingOperations brokerTemplate) {
this(clientInboundChannel, clientOutboundChannel, brokerTemplate, null);
}
/**
* Create an instance of SimpAnnotationMethodMessageHandler with the given
* message channels and broker messaging template.
* @param clientInboundChannel the channel for receiving messages from clients (e.g. WebSocket clients)
* @param clientOutboundChannel the channel for messages to clients (e.g. WebSocket clients)
* @param brokerTemplate a messaging template to send application messages to the broker
* @param pathSeparator the path separator to use with the destination patterns
* @since 4.1
*/
public SimpAnnotationMethodMessageHandler(SubscribableChannel clientInboundChannel,
MessageChannel clientOutboundChannel, SimpMessageSendingOperations brokerTemplate,
String pathSeparator) {
MessageChannel clientOutboundChannel, SimpMessageSendingOperations brokerTemplate) {
Assert.notNull(clientInboundChannel, "clientInboundChannel must not be null");
Assert.notNull(clientOutboundChannel, "clientOutboundChannel must not be null");
@@ -136,7 +123,6 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
this.clientInboundChannel = clientInboundChannel;
this.clientMessagingTemplate = new SimpMessagingTemplate(clientOutboundChannel);
this.brokerTemplate = brokerTemplate;
this.pathMatcher = new AntPathMatcher(pathSeparator);
Collection<MessageConverter> converters = new ArrayList<MessageConverter>();
converters.add(new StringMessageConverter());
@@ -144,6 +130,36 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
this.messageConverter = new CompositeMessageConverter(converters);
}
/**
* {@inheritDoc}
* <p>Destination prefixes are expected to be slash-separated Strings and
* therefore a slash is automatically appended where missing to ensure a
* proper prefix-based match (i.e. matching complete segments).
*
* <p>Note however that the remaining portion of a destination after the
* prefix may use a different separator (e.g. commonly "." in messaging)
* depending on the configured {@code PathMatcher}.
*/
@Override
public void setDestinationPrefixes(Collection<String> prefixes) {
super.setDestinationPrefixes(appendSlashes(prefixes));
}
private static Collection<String> appendSlashes(Collection<String> prefixes) {
if (CollectionUtils.isEmpty(prefixes)) {
return prefixes;
}
Collection<String> result = new ArrayList<String>(prefixes.size());
for (String prefix : prefixes) {
if (!prefix.endsWith("/")) {
prefix = prefix + "/";
}
result.add(prefix);
}
return result;
}
/**
* Configure a {@link MessageConverter} to use to convert the payload of a message
* from serialize form with a specific MIME type to an Object matching the target
@@ -189,6 +205,7 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
public void setPathMatcher(PathMatcher pathMatcher) {
Assert.notNull(pathMatcher, "PathMatcher must not be null");
this.pathMatcher = pathMatcher;
this.slashPathSeparator = this.pathMatcher.combine("a", "a").equals("a/a");
}
/**
@@ -333,31 +350,31 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
MessageMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, MessageMapping.class);
MessageMapping messageAnnot = AnnotationUtils.findAnnotation(method, MessageMapping.class);
if (messageAnnot != null) {
SimpMessageMappingInfo result = createMessageMappingCondition(messageAnnot, typeAnnotation == null);
SimpMessageMappingInfo result = createMessageMappingCondition(messageAnnot);
if (typeAnnotation != null) {
result = createMessageMappingCondition(typeAnnotation, false).combine(result);
result = createMessageMappingCondition(typeAnnotation).combine(result);
}
return result;
}
SubscribeMapping subsribeAnnotation = AnnotationUtils.findAnnotation(method, SubscribeMapping.class);
if (subsribeAnnotation != null) {
SimpMessageMappingInfo result = createSubscribeCondition(subsribeAnnotation, typeAnnotation == null);
SimpMessageMappingInfo result = createSubscribeCondition(subsribeAnnotation);
if (typeAnnotation != null) {
result = createMessageMappingCondition(typeAnnotation, false).combine(result);
result = createMessageMappingCondition(typeAnnotation).combine(result);
}
return result;
}
return null;
}
private SimpMessageMappingInfo createMessageMappingCondition(MessageMapping annotation, boolean prependLeadingSlash) {
private SimpMessageMappingInfo createMessageMappingCondition(MessageMapping annotation) {
return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.MESSAGE,
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher, prependLeadingSlash));
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher));
}
private SimpMessageMappingInfo createSubscribeCondition(SubscribeMapping annotation, boolean prependLeadingSlash) {
private SimpMessageMappingInfo createSubscribeCondition(SubscribeMapping annotation) {
return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.SUBSCRIBE,
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher, prependLeadingSlash));
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher));
}
@Override
@@ -376,6 +393,27 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
return SimpMessageHeaderAccessor.getDestination(message.getHeaders());
}
@Override
protected String getLookupDestination(String destination) {
if (destination == null) {
return null;
}
if (CollectionUtils.isEmpty(getDestinationPrefixes())) {
return destination;
}
for (String prefix : getDestinationPrefixes()) {
if (destination.startsWith(prefix)) {
if (this.slashPathSeparator) {
return destination.substring(prefix.length() - 1);
}
else {
return destination.substring(prefix.length());
}
}
}
return null;
}
@Override
protected SimpMessageMappingInfo getMatchingMapping(SimpMessageMappingInfo mapping, Message<?> message) {
return mapping.getMatchingCondition(message);

View File

@@ -51,6 +51,8 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
private SubscriptionRegistry subscriptionRegistry;
private PathMatcher pathMatcher;
private MessageHeaderInitializer headerInitializer;
@@ -58,38 +60,21 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
* Create a SimpleBrokerMessageHandler instance with the given message channels
* and destination prefixes.
*
* @param clientInboundChannel the channel for receiving messages from clients (e.g. WebSocket clients)
* @param clientOutboundChannel the channel for sending messages to clients (e.g. WebSocket clients)
* @param inChannel the channel for receiving messages from clients (e.g. WebSocket clients)
* @param outChannel the channel for sending messages to clients (e.g. WebSocket clients)
* @param brokerChannel the channel for the application to send messages to the broker
*/
public SimpleBrokerMessageHandler(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel,
public SimpleBrokerMessageHandler(SubscribableChannel inChannel, MessageChannel outChannel,
SubscribableChannel brokerChannel, Collection<String> destinationPrefixes) {
this(clientInboundChannel, clientOutboundChannel, brokerChannel, destinationPrefixes, null);
}
/**
* Additional constructor with a customized path matcher.
*
* @param clientInboundChannel the channel for receiving messages from clients (e.g. WebSocket clients)
* @param clientOutboundChannel the channel for sending messages to clients (e.g. WebSocket clients)
* @param brokerChannel the channel for the application to send messages to the broker
* @param pathMatcher the path matcher to use
* @since 4.1
*/
public SimpleBrokerMessageHandler(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel,
SubscribableChannel brokerChannel, Collection<String> destinationPrefixes, PathMatcher pathMatcher) {
super(destinationPrefixes);
Assert.notNull(clientInboundChannel, "'clientInboundChannel' must not be null");
Assert.notNull(clientOutboundChannel, "'clientOutboundChannel' must not be null");
Assert.notNull(inChannel, "'clientInboundChannel' must not be null");
Assert.notNull(outChannel, "'clientOutboundChannel' must not be null");
Assert.notNull(brokerChannel, "'brokerChannel' must not be null");
this.clientInboundChannel = clientInboundChannel;
this.clientOutboundChannel = clientOutboundChannel;
this.clientInboundChannel = inChannel;
this.clientOutboundChannel = outChannel;
this.brokerChannel = brokerChannel;
DefaultSubscriptionRegistry subscriptionRegistry = new DefaultSubscriptionRegistry();
if(pathMatcher != null) {
subscriptionRegistry.setPathMatcher(pathMatcher);
}
this.subscriptionRegistry = subscriptionRegistry;
}
@@ -106,15 +91,41 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
return this.brokerChannel;
}
/**
* Configure a custom SubscriptionRegistry to use for storing subscriptions.
*
* <p><strong>Note</strong> that when a custom PathMatcher is configured via
* {@link #setPathMatcher}, if the custom registry is not an instance of
* {@link DefaultSubscriptionRegistry}, the provided PathMatcher is not used
* and must be configured directly on the custom registry.
*/
public void setSubscriptionRegistry(SubscriptionRegistry subscriptionRegistry) {
Assert.notNull(subscriptionRegistry, "SubscriptionRegistry must not be null");
this.subscriptionRegistry = subscriptionRegistry;
initPathMatcherToUse();
}
private void initPathMatcherToUse() {
if (this.pathMatcher != null) {
if (this.subscriptionRegistry instanceof DefaultSubscriptionRegistry) {
((DefaultSubscriptionRegistry) this.subscriptionRegistry).setPathMatcher(this.pathMatcher);
}
}
}
public SubscriptionRegistry getSubscriptionRegistry() {
return this.subscriptionRegistry;
}
/**
* When configured, the given PathMatcher is passed down to the
* SubscriptionRegistry to use for matching destination to subscriptions.
*/
public void setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
initPathMatcherToUse();
}
/**
* Configure a {@link MessageHeaderInitializer} to apply to the headers of all
* messages sent to the client outbound channel.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2013 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.
@@ -23,6 +23,7 @@ import java.util.List;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler;
import org.springframework.util.Assert;
/**
@@ -66,4 +67,6 @@ public abstract class AbstractBrokerRegistration {
return this.destinationPrefixes;
}
protected abstract AbstractBrokerMessageHandler getMessageHandler(SubscribableChannel brokerChannel);
}

View File

@@ -39,9 +39,9 @@ import org.springframework.messaging.simp.user.UserSessionRegistry;
import org.springframework.messaging.support.AbstractSubscribableChannel;
import org.springframework.messaging.support.ExecutorSubscribableChannel;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.PathMatcher;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
@@ -206,17 +206,17 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
@Bean
public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler() {
String defaultSeparator = this.getBrokerRegistry().getDefaultSeparator();
SimpAnnotationMethodMessageHandler handler = new SimpAnnotationMethodMessageHandler(
clientInboundChannel(), clientOutboundChannel(), brokerMessagingTemplate(), defaultSeparator);
clientInboundChannel(), clientOutboundChannel(), brokerMessagingTemplate());
handler.setDestinationPrefixes(getBrokerRegistry().getApplicationDestinationPrefixes());
handler.setMessageConverter(brokerMessageConverter());
handler.setValidator(simpValidator());
AntPathMatcher pathMatcher = new AntPathMatcher(defaultSeparator);
handler.setPathMatcher(pathMatcher);
PathMatcher pathMatcher = this.getBrokerRegistry().getPathMatcher();
if (pathMatcher != null) {
handler.setPathMatcher(pathMatcher);
}
return handler;
}
@@ -234,9 +234,7 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
@Bean
public UserDestinationMessageHandler userDestinationMessageHandler() {
UserDestinationMessageHandler handler = new UserDestinationMessageHandler(
clientInboundChannel(), brokerChannel(), userDestinationResolver());
return handler;
return new UserDestinationMessageHandler(clientInboundChannel(), brokerChannel(), userDestinationResolver());
}
@Bean

View File

@@ -25,6 +25,7 @@ import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
/**
* A registry for configuring message broker options.
@@ -47,9 +48,10 @@ public class MessageBrokerRegistry {
private String userDestinationPrefix;
private PathMatcher pathMatcher;
private ChannelRegistration brokerChannelRegistration = new ChannelRegistration();
private String defaultSeparator;
public MessageBrokerRegistry(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel) {
Assert.notNull(clientInboundChannel);
@@ -111,6 +113,31 @@ public class MessageBrokerRegistry {
return this;
}
/**
* Configure the PathMatcher to use to match the destinations of incoming
* messages to {@code @MessageMapping} and {@code @SubscribeMapping} methods.
*
* <p>By default {@link org.springframework.util.AntPathMatcher} is configured.
* However applications may provide an {@code AntPathMatcher} instance
* customized to use "." (commonly used in messaging) instead of "/" as path
* separator or provide a completely different PathMatcher implementation.
*
* <p>Note that the configured PathMatcher is only used for matching the
* portion of the destination after the configured prefix. For example given
* application destination prefix "/app" and destination "/app/price.stock.**",
* the message might be mapped to a controller with "price" and "stock.**"
* as its type and method-level mappings respectively.
*
* <p>When the simple broker is enabled, the PathMatcher configured here is
* also used to match message destinations when brokering messages.
*
* @since 4.1
*/
public MessageBrokerRegistry setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
return this;
}
/**
* Customize the channel used to send messages from the application to the message
* broker. By default messages from the application to the message broker are sent
@@ -122,24 +149,14 @@ public class MessageBrokerRegistry {
return this.brokerChannelRegistration;
}
/**
* Customize the default separator used for destination patterns matching/combining.
* It can be used to configure "." as the default separator, since it is used in most
* STOMP broker relay, enabling destination patterns like "/topic/PRICE.STOCK.**".
* <p>The default separator is "/".
*/
public MessageBrokerRegistry defaultSeparator(String defaultSeparator) {
this.defaultSeparator = defaultSeparator;
return this;
}
protected SimpleBrokerMessageHandler getSimpleBroker(SubscribableChannel brokerChannel) {
if ((this.simpleBrokerRegistration == null) && (this.brokerRelayRegistration == null)) {
enableSimpleBroker();
}
if (this.simpleBrokerRegistration != null) {
AntPathMatcher pathMatcher = new AntPathMatcher(this.defaultSeparator);
return this.simpleBrokerRegistration.getMessageHandler(brokerChannel, pathMatcher);
SimpleBrokerMessageHandler handler = this.simpleBrokerRegistration.getMessageHandler(brokerChannel);
handler.setPathMatcher(this.pathMatcher);
return handler;
}
return null;
}
@@ -160,12 +177,12 @@ public class MessageBrokerRegistry {
return this.userDestinationPrefix;
}
protected PathMatcher getPathMatcher() {
return this.pathMatcher;
}
protected ChannelRegistration getBrokerChannelRegistration() {
return this.brokerChannelRegistration;
}
protected String getDefaultSeparator() {
return this.defaultSeparator;
}
}

View File

@@ -19,28 +19,25 @@ package org.springframework.messaging.simp.config;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
import org.springframework.util.PathMatcher;
/**
* Registration class for configuring a {@link SimpleBrokerMessageHandler}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 4.0
*/
public class SimpleBrokerRegistration extends AbstractBrokerRegistration {
public SimpleBrokerRegistration(SubscribableChannel clientInboundChannel,
MessageChannel clientOutboundChannel, String[] destinationPrefixes) {
super(clientInboundChannel, clientOutboundChannel, destinationPrefixes);
public SimpleBrokerRegistration(SubscribableChannel inChannel, MessageChannel outChannel, String[] prefixes) {
super(inChannel, outChannel, prefixes);
}
protected SimpleBrokerMessageHandler getMessageHandler(SubscribableChannel brokerChannel, PathMatcher pathMatcher) {
@Override
protected SimpleBrokerMessageHandler getMessageHandler(SubscribableChannel brokerChannel) {
return new SimpleBrokerMessageHandler(getClientInboundChannel(),
getClientOutboundChannel(), brokerChannel, getDestinationPrefixes(), pathMatcher);
getClientOutboundChannel(), brokerChannel, getDestinationPrefixes());
}
}