This commit adds the support of JMS annotated endpoint. Can be
activated both by @EnableJms or <jms:annotation-driven/> and
detects methods of managed beans annotated with @JmsListener,
either directly or through a meta-annotation.
Containers are created and managed under the cover by a registry
at application startup time. Container creation is delegated to a
JmsListenerContainerFactory that is identified by the containerFactory
attribute of the JmsListener annotation. Containers can be
retrieved from the registry using a custom id that can be specified
directly on the annotation.
A "factory-id" attribute is available on the container element of
the XML namespace. When it is present, the configuration defined at
the namespace level is used to build a JmsListenerContainerFactory
that is exposed with the value of the "factory-id" attribute. This can
be used as a smooth migration path for users having listener containers
defined at the namespace level. It is also possible to migrate all
listeners to annotated endpoints and yet keep the
<jms:listener-container> or <jms:jca-listener-container> element to
share the container configuration.
The configuration can be fine-tuned by implementing the
JmsListenerConfigurer interface which gives access to the registrar
used to register endpoints. This includes a programmatic registration
of endpoints in complement to the declarative approach. A default
JmsListenerContainerFactory can also be specified to be used if no
containerFactory has been set on the annotation.
Annotated methods can have flexible method arguments that are similar
to what @MessageMapping provides. In particular, jms listener endpoint
methods can fully use the messaging abstraction, including convenient
header accessors. It is also possible to inject the raw
javax.jms.Message and the Session for more advanced use cases. The
payload can be injected as long as the conversion service is able to
convert it from the original type of the JMS payload. By
default, a DefaultJmsHandlerMethodFactory is used but it can be
configured further to support additional method arguments or to
customize conversion and validation support.
The return type of an annotated method can also be an instance of
Spring's Message abstraction. Instead of just converting the payload,
such response type allows to communicate standard and custom headers.
The JmsHeaderMapper infrastructure from Spring integration has also
been migrated to the Spring framework. SimpleJmsHeaderMapper is based
on SI's DefaultJmsHeaderMapper. The simple implementation maps all
JMS headers so that the generated Message abstraction has all the
information stored in the protocol specific message.
Issue: SPR-9882
An initial commit with expanded support for static resource handling:
- Add ResourceResolver strategy for resolving a request to a Resource
along with a few implementations.
- Add PublicResourceUrlProvider to get URLs for client-side use.
- Add ResourceUrlEncodingFilter and
PublicResourceUrlProviderExposingInterceptor along with initial
MVC Java config support.
Issue: SPR-10933
Introduce MessageHeaderInitializer strategy for initializing a
MessageHeaderAccessor.
Add IdTimestampMessageHeaderInitializer implementation that provides
control over ID and timestamp header generation.
Disable ID and timestamp by default in SimpMessageHeaderAccessor and
therefore its sub-class StompHeaderAccessor.
Issue: SPR-11468
The BufferingStompDecoder now decorates rather than extend
StompDecoder. This allows a single StompDecoder instance to be
configured and extended independantly while buffering remains a
separate concern.
AbstractMessageConverter and messaging template implementations now
detect and use mutable headers if passed in.
The SimpMessagingTemplate is optimized to supporting using a single
MessageHeaders instance while preparing a message.
This commit also updates code using the SimpMessagingTemplate to take
advantage of its new capabilities.
Issue: SPR-11468
Mutate rather than re-create headers when decoding STOMP messages
before a message is sent on a message channel.
Use MessageBuilder.createMessage to ensure the fully prepared
MessageHeaders is used directly MessageHeaderAccessor instance.
Issue: SPR-11468
Refine semantics of ID and TIMESTAMP headers provided to protected
MessageHeaders constructor.
Refactor internal implementation of MessageHeaderAccessor.
Support mutating headers from a single thread while a message is being
built (e.g. StompDecoder creating message + then adding session id).
Improve immutablity in NativeMessageHeaderAccessor and in
StompHeaderAccessor.
Optimize object creation for initializing messages and subsequent
accessing their headers.
Introduce MessageHeaderAccessorFactory support to enable applying a
common strategies for ID and TIMESTAMP generation to every message.
Add MessageBuilder shortcut factory method for creating messages from
payload and a full-prepared MessageHeaders instance. Also add
equivalent constructors to GenericMessage and ErrorMessage.
Issue: SPR-11468
The UserDestinationMessageHandler adds a header providing a hint for
what the original destination a user may have used when subscribing.
That is then used when writing messages back to WebSocket clients to
ensure they dont see the internally used, transformed user destination.
This change moves the header name constatn to make it more broadly
applicable. For example SPR-11645.
Prior to this commit, @SubscribeMapping mapped methods (backed with
@SendTo* annotations, or not) would send MESSAGEs with the wrong
destination. Instead of using the original SUBSCRIBE destination, it
would use the lookup path computed from the configured prefixes in the
application.
This commit fixes this issue - now @SubscribeMapping MESSAGEs use the
original SUBSCRIBE destination.
Issue: SPR-11648
This commit validates that the payload type of the message is
assignable to the one declared in the method signature. If that
is not the case, a meaningful exception message is thrown with
the types mismatch.
Prior to this commit, only the Message interface could be defined
in the method signature: it is now possible to define a sub-class
of Message if necessary which will match as long as the Message
parameter is assignable to that type.
Issue: SPR-11584
BufferingStompDecoder message buffer size limit can now be configured
with JavaConfig MessageBrokerRegistry.setMessageBufferSizeLimit() or
with XML <websocket:message-brocker message-buffer-size="">.
Issue: SPR-11527
Before this change the StompDecoder decoded and returned only the first
Message in the ByteBuffer passed to it. So to obtain all messages from
the buffer, one had to loop passing the same buffer in until no more
complete STOMP frames could be decoded.
This chage modifies StompDecoder to return List<Message> after
exhaustively decoding all available STOMP frames from the input buffer.
Also an overloaded decode method allows passing in Map that will be
populated with any headers successfully parsed, which is useful for
"peeking" at the "content-length" header.
This change also adds a BufferingStompDecoder sub-class which buffers
any content left in the input buffer after parsing one or more STOMP
frames. This sub-class can also deal with fragmented messages,
re-assembling them and parsing as a whole message.
Issue: SPR-11527
If a payload is present but conversion returns null (meaning no
converter knows how to convert), raise a MessageConversionException
that provides information about the type we were trying to convert
to and the message itself whose headers (namely content-type) contain
crucial information required to debug the problem.
Issue: SPR-11577
When no @Payload is provided, it is equivalent to @Payload with default
attribute values. Since the default value of required=true, then
an argument that's not annotated is required.
A payload that is required will now throw an appropriate exception
regardless of if a conversion is required or not.
isEmptyPayload now takes the payload instead of the message
so that both the original payload and the converted payload, if
necessary, share the same logic.
JSR-303 validation is now consistently applied.
Issue: SPR-11577
Prior to this commit, the codebase was using a mix of log4j.xml
and log4j.properties for test-related logging configuration. This
can be an issue as log4j takes the xml variant first when looking
for a default bootstrap configuration.
In practice, some modules declaring the properties variant were
taking the xml variant configuration from another module.
The general structure of the configuration has also been
harmonized to provide a standard console output as well as an
easy way to enable trace logs for the current module.
The clientInboundChannel and clientOutboundChannel now use twice
the number of available processors by default to accomodate for some
degree of blocking in task execution on average.
In practice these settings still need to be configured explicitly in
applications but these should serve as better default values than
the default values in ThreadPoolTaskExecutor.
Issue: SPR-11450
This change exposes the WebSocketSession attributes through a message header.
The StompSubProtocolHandler adds this to incoming messages.
For now messaging handling methods can access the map via @Header, e.g.:
@Header(StompHeaderAccessor.SESSION_ATTRIBUTES) Map<String, Object> attrs) {
Issue: SPR-11566
Use a custom ConfigReader to enforce the use of SynchronousDispatcher
and no other dispatchers otherwise created by default. Avoids the
creation thread pools never to be used.
Ignore DISCONNECT messages if already disconnected. This can occur if
the client explicitly sends a DISCONNECT frame and then closes the
socket quickly. The closing of the WebSocket sessions also sends a
DISCONNECT upstream to ensure the broker is aware.
Even though Netty is a required dependency of reactor-tcp at present,
there is no hard dependency in the spring-messaging Reactor-based
implementation.
Configure explicitly use of SynchronousDispatcher instead of the one
used otherwise by default (RingBufferDispatcher). As a result TCP
optations are now scoped to Netty's threads.
Remove Environment field. It is no longer required to shut it down
since we're now using SynchronousDispatcher by default.
Replace connection.in() with connection.consume() when composing
connection handling. The former creates a Stream for further composing,
e.g. via map(), filter() but all we need is to read a message.
Provide additional constructor that aceepts a pre-configured Reactor
TcpClient instance.
Issue: SPR-11531
Add accessor for brokerAvailable in AbstractBrokerMessageHandler
Ensure brokerAvailable is set even if eventPublisher is not
Add tests BrokerMessageHandlerTests
Turn off brokerAvailable when StompBrokerRelayMessageHandler stops
Actually stop message handling when brokerAvailable=false
Improve log messages
Issue: SPR-11563
After this change if a content-length header is provided but is less
than 0 or cannot be parsed as a number, it is ignored and the body
is read sequentially, i.e. until we reach a null byte terminator.
This provides better protection against clients that may set the
content-length header in error.
Issue: SPR-11528
Fix NPE exception when closing TcpConnection
Ensure a ConnectionHandler is cleared when a TcpConnection is closed
(at the same time), logging an exception if the closing fails.
Improve error messages.
Issue: SPR-11531
Before this change CompositeMessageConverter had a ContentTypeResolver
field that was in turn set on all contained converters.
After this change that field is removed and effectively
CompositeMessageConverter is a simple container of other converters.
Each converter in turn must have been configured with a
ContentTypeResolver.
Doing so means it is less likely to have unexpected consequences when
configuring converters, the ContentTypeResolver set in the composite
converter overriding the one configured in a contained converter.
Also commit 676ce6 added default ContentTypeResolver initialization
to AbstractMessageConverter, which ensures that converters are still
straight forward to configure.
Issue: SPR-11462
Previously AbstractMessageConverter did not have a ContentTypeResolver
configured by default. However the Java config and XML namespace in
spring-messaging and spring-websocket always configured one.
This change ensures every AbstractMessageConverter is configured with an
instance of DefaultContentTypeResolver by default. This makes sense since
all the resolver does is make an attempt to find a content type to use
for matching. If it can't it returns null and it's up to the converter
to decide whether it can convert or not.
Issue: SPR-11462