Update MessageConverter and reactor dependencies
This commit is contained in:
parent
dbc904b647
commit
2803845151
|
|
@ -301,7 +301,9 @@ project("spring-context") {
|
|||
optional("org.hibernate:hibernate-validator:4.3.0.Final")
|
||||
optional("org.aspectj:aspectjweaver:${aspectjVersion}")
|
||||
optional("org.apache.geronimo.specs:geronimo-jta_1.1_spec:1.1")
|
||||
optional("reactor:reactor-core:1.0.0.BUILD-SNAPSHOT")
|
||||
optional("com.fasterxml.jackson.core:jackson-databind:2.2.0")
|
||||
optional("org.projectreactor:reactor-core:1.0.0.BUILD-SNAPSHOT")
|
||||
optional("com.lmax:disruptor:3.1.1")
|
||||
testCompile("commons-dbcp:commons-dbcp:1.2.2")
|
||||
testCompile("javax.inject:javax.inject-tck:1")
|
||||
}
|
||||
|
|
@ -490,8 +492,9 @@ project("spring-websocket") {
|
|||
optional("org.eclipse.jetty.websocket:websocket-server:9.0.4.v20130625")
|
||||
optional("org.eclipse.jetty.websocket:websocket-client:9.0.4.v20130625")
|
||||
optional("com.fasterxml.jackson.core:jackson-databind:2.2.0") // required for SockJS support currently
|
||||
optional("reactor:reactor-core:1.0.0.BUILD-SNAPSHOT") // STOMP message processing
|
||||
optional("reactor:reactor-tcp:1.0.0.BUILD-SNAPSHOT") // STOMP relay to message broker
|
||||
optional("org.projectreactor:reactor-core:1.0.0.BUILD-SNAPSHOT")
|
||||
optional("org.projectreactor:reactor-tcp:1.0.0.BUILD-SNAPSHOT")
|
||||
optional("com.lmax:disruptor:3.1.1")
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ import org.springframework.messaging.MessageHandler;
|
|||
import org.springframework.messaging.SubscribableChannel;
|
||||
|
||||
import reactor.core.Reactor;
|
||||
import reactor.fn.Consumer;
|
||||
import reactor.fn.Event;
|
||||
import reactor.fn.registry.Registration;
|
||||
import reactor.fn.selector.ObjectSelector;
|
||||
import reactor.event.Event;
|
||||
import reactor.event.registry.Registration;
|
||||
import reactor.event.selector.ObjectSelector;
|
||||
import reactor.function.Consumer;
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.messaging.converter;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
* @sicne 4.0
|
||||
*/
|
||||
public class MappingJackson2MessageConverter implements MessageConverter<Object> {
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private Type defaultObjectType = Map.class;
|
||||
|
||||
private Class<?> defaultMessagePayloadClass = byte[].class;
|
||||
|
||||
|
||||
/**
|
||||
* Set the default target Object class to convert to in
|
||||
* {@link #fromMessage(Message, Class)}.
|
||||
*/
|
||||
public void setDefaultObjectClass(Type defaultObjectType) {
|
||||
Assert.notNull(defaultObjectType, "defaultObjectType is required");
|
||||
this.defaultObjectType = defaultObjectType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of Message payload to convert to in {@link #toMessage(Object)}.
|
||||
* @param payloadClass either byte[] or String
|
||||
*/
|
||||
public void setDefaultTargetPayloadClass(Class<?> payloadClass) {
|
||||
Assert.isTrue(byte[].class.equals(payloadClass) || String.class.equals(payloadClass),
|
||||
"Payload class must be byte[] or String: " + payloadClass);
|
||||
this.defaultMessagePayloadClass = payloadClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromMessage(Message<?> message, Type objectType) {
|
||||
|
||||
JavaType javaType = (objectType != null) ?
|
||||
this.objectMapper.constructType(objectType) :
|
||||
this.objectMapper.constructType(this.defaultObjectType);
|
||||
|
||||
Object payload = message.getPayload();
|
||||
try {
|
||||
if (payload instanceof byte[]) {
|
||||
return this.objectMapper.readValue((byte[]) payload, javaType);
|
||||
}
|
||||
else if (payload instanceof String) {
|
||||
return this.objectMapper.readValue((String) payload, javaType);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unexpected message payload type: " + payload);
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new MessageConversionException("Could not read JSON: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <P> Message<P> toMessage(Object object) {
|
||||
P payload;
|
||||
try {
|
||||
if (byte[].class.equals(this.defaultMessagePayloadClass)) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
this.objectMapper.writeValue(out, object);
|
||||
payload = (P) out.toByteArray();
|
||||
}
|
||||
else if (String.class.equals(this.defaultMessagePayloadClass)) {
|
||||
Writer writer = new StringWriter();
|
||||
this.objectMapper.writeValue(writer, object);
|
||||
payload = (P) writer.toString();
|
||||
}
|
||||
else {
|
||||
// Should never happen..
|
||||
throw new IllegalStateException("Unexpected payload class: " + defaultMessagePayloadClass);
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new MessageConversionException("Could not write JSON: " + ex.getMessage(), ex);
|
||||
}
|
||||
return MessageBuilder.withPayload(payload).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.messaging.converter;
|
||||
package org.springframework.messaging.converter;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.springframework.messaging.converter;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
|
||||
|
|
@ -23,10 +25,10 @@ import org.springframework.messaging.Message;
|
|||
* @author Mark Fisher
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface MessageConverter {
|
||||
public interface MessageConverter<T> {
|
||||
|
||||
<T> Message<?> toMessage(T object);
|
||||
<P> Message<P> toMessage(T object);
|
||||
|
||||
<T> T fromMessage(Message<?> message);
|
||||
T fromMessage(Message<?> message, Type targetClass);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package org.springframework.messaging.converter;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
|
||||
|
|
@ -23,19 +25,16 @@ import org.springframework.messaging.support.MessageBuilder;
|
|||
* @author Mark Fisher
|
||||
* @since 4.0
|
||||
*/
|
||||
public class DefaultMessageConverter implements MessageConverter {
|
||||
public class SimplePayloadMessageConverter implements MessageConverter<Object> {
|
||||
|
||||
@Override
|
||||
public <T> Message<?> toMessage(T object) {
|
||||
System.out.println("converting " + object + " to message");
|
||||
public Message<Object> toMessage(Object object) {
|
||||
return MessageBuilder.withPayload(object).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T fromMessage(Message<?> message) {
|
||||
System.out.println("converting " + message + " to object");
|
||||
return (T) message.getPayload();
|
||||
public Object fromMessage(Message<?> message, Type targetClass) {
|
||||
return message.getPayload();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ import org.apache.commons.logging.Log;
|
|||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.converter.DefaultMessageConverter;
|
||||
import org.springframework.messaging.converter.SimplePayloadMessageConverter;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ public abstract class AbstractMessageSendingTemplate<D> implements MessageSendin
|
|||
|
||||
private volatile D defaultDestination;
|
||||
|
||||
protected volatile MessageConverter converter = new DefaultMessageConverter();
|
||||
protected volatile MessageConverter converter = new SimplePayloadMessageConverter();
|
||||
|
||||
|
||||
public void setDefaultDestination(D defaultDestination) {
|
||||
|
|
@ -44,7 +44,7 @@ public abstract class AbstractMessageSendingTemplate<D> implements MessageSendin
|
|||
/**
|
||||
* Set the {@link MessageConverter} that is to be used to convert
|
||||
* between Messages and objects for this template.
|
||||
* <p>The default is {@link DefaultMessageConverter}.
|
||||
* <p>The default is {@link SimplePayloadMessageConverter}.
|
||||
*/
|
||||
public void setMessageConverter(MessageConverter messageConverter) {
|
||||
Assert.notNull(messageConverter, "'messageConverter' must not be null");
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@ public abstract class AbstractMessagingTemplate<D> extends AbstractMessageSendin
|
|||
|
||||
@Override
|
||||
public Object receiveAndConvert(D destination) {
|
||||
Message<Object> message = this.doReceive(destination);
|
||||
return (message != null) ? this.converter.fromMessage(message) : null;
|
||||
Message<?> message = this.doReceive(destination);
|
||||
return (message != null) ? this.converter.fromMessage(message, null) : null;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ public abstract class AbstractMessagingTemplate<D> extends AbstractMessageSendin
|
|||
requestMessage = postProcessor.postProcessMessage(requestMessage);
|
||||
}
|
||||
Message<?> replyMessage = this.sendAndReceive(destination, requestMessage);
|
||||
return this.converter.fromMessage(replyMessage);
|
||||
return this.converter.fromMessage(replyMessage, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,193 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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.web.messaging.converter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract base class for most {@link MessageConverter} implementations.
|
||||
*
|
||||
* <p>This base class adds support for setting supported {@code MediaTypes}, through the
|
||||
* {@link #setSupportedMediaTypes(List) supportedMediaTypes} property.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Arjen Poutsma
|
||||
* @since 4.0
|
||||
*/
|
||||
public abstract class AbstractMessageConverter implements MessageConverter {
|
||||
|
||||
/** Logger available to subclasses */
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private List<MediaType> supportedMediaTypes = Collections.emptyList();
|
||||
|
||||
|
||||
/**
|
||||
* Construct an {@code AbstractMessageConverter} with no supported media types.
|
||||
* @see #setSupportedMediaTypes
|
||||
*/
|
||||
protected AbstractMessageConverter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an {@code AbstractMessageConverter} with one supported media type.
|
||||
*
|
||||
* @param supportedMediaType the supported media type
|
||||
*/
|
||||
protected AbstractMessageConverter(MediaType supportedMediaType) {
|
||||
setSupportedMediaTypes(Collections.singletonList(supportedMediaType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an {@code AbstractMessageConverter} with multiple supported media type.
|
||||
*
|
||||
* @param supportedMediaTypes the supported media types
|
||||
*/
|
||||
protected AbstractMessageConverter(MediaType... supportedMediaTypes) {
|
||||
setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the list of {@link MediaType} objects supported by this converter.
|
||||
*/
|
||||
public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) {
|
||||
Assert.notEmpty(supportedMediaTypes, "'supportedMediaTypes' must not be empty");
|
||||
this.supportedMediaTypes = new ArrayList<MediaType>(supportedMediaTypes);
|
||||
}
|
||||
|
||||
public List<MediaType> getSupportedMediaTypes() {
|
||||
return Collections.unmodifiableList(this.supportedMediaTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation checks if the given class is {@linkplain #supports(Class)
|
||||
* supported}, and if the {@linkplain #getSupportedMediaTypes() supported media types}
|
||||
* {@linkplain MediaType#includes(MediaType) include} the given media type.
|
||||
*/
|
||||
@Override
|
||||
public boolean canConvertFromPayload(Class<?> clazz, MediaType mediaType) {
|
||||
return supports(clazz) && canConvertFrom(mediaType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given class is supported by this converter.
|
||||
* @param clazz the class to test for support
|
||||
* @return {@code true} if supported; {@code false} otherwise
|
||||
*/
|
||||
protected abstract boolean supports(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Returns true if any of the {@linkplain #setSupportedMediaTypes(List) supported
|
||||
* media types} include the given media type.
|
||||
*
|
||||
* @param mediaType the media type to read, can be {@code null} if not specified.
|
||||
* Typically the value of a {@code Content-Type} header.
|
||||
* @return {@code true} if the supported media types include the media type, or if the
|
||||
* media type is {@code null}
|
||||
*/
|
||||
protected boolean canConvertFrom(MediaType mediaType) {
|
||||
if (mediaType == null) {
|
||||
return true;
|
||||
}
|
||||
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
|
||||
if (supportedMediaType.includes(mediaType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation checks if the given class is {@linkplain #supports(Class)
|
||||
* supported}, and if the {@linkplain #getSupportedMediaTypes() supported media types}
|
||||
* {@linkplain MediaType#includes(MediaType) include} the given media type.
|
||||
*/
|
||||
@Override
|
||||
public boolean canConvertToPayload(Class<?> clazz, MediaType mediaType) {
|
||||
return supports(clazz) && canConvertTo(mediaType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the given media type includes any of the
|
||||
* {@linkplain #setSupportedMediaTypes(List) supported media types}.
|
||||
*
|
||||
* @param mediaType the media type to write, can be {@code null} if not specified.
|
||||
* Typically the value of an {@code Accept} header.
|
||||
* @return {@code true} if the supported media types are compatible with the media
|
||||
* type, or if the media type is {@code null}
|
||||
*/
|
||||
protected boolean canConvertTo(MediaType mediaType) {
|
||||
if (mediaType == null || MediaType.ALL.equals(mediaType)) {
|
||||
return true;
|
||||
}
|
||||
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
|
||||
if (supportedMediaType.isCompatibleWith(mediaType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation simply delegates to
|
||||
* {@link #convertFromPayloadInternal(Class, MediaType, byte[])}. Future
|
||||
* implementations might add some default behavior, however.
|
||||
*/
|
||||
@Override
|
||||
public Object convertFromPayload(Class<?> clazz, MediaType contentType, byte[] payload)
|
||||
throws IOException, ContentTypeNotSupportedException {
|
||||
|
||||
return convertFromPayloadInternal(clazz, contentType, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract template method that reads the actual object. Invoked from {@link #read}.
|
||||
* @param clazz the type of object to return
|
||||
* @param contentType
|
||||
* @param payload the content to convert from
|
||||
* @return the converted object
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
protected abstract Object convertFromPayloadInternal(Class<?> clazz, MediaType contentType, byte[] payload)
|
||||
throws IOException, ContentTypeNotSupportedException;
|
||||
|
||||
/**
|
||||
* This implementation simply delegates to
|
||||
* {@link #convertToPayloadInternal(Object, MediaType)}. Future
|
||||
* implementations might add some default behavior, however.
|
||||
*/
|
||||
@Override
|
||||
public byte[] convertToPayload(Object content, MediaType contentType)
|
||||
throws IOException, ContentTypeNotSupportedException {
|
||||
|
||||
return convertToPayloadInternal(content, contentType);
|
||||
}
|
||||
|
||||
protected abstract byte[] convertToPayloadInternal(Object content, MediaType contentType)
|
||||
throws IOException, ContentTypeNotSupportedException;
|
||||
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.web.messaging.converter;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class ByteArrayMessageConverter implements MessageConverter {
|
||||
|
||||
@Override
|
||||
public boolean canConvertFromPayload(Class<?> clazz, MediaType contentType) {
|
||||
return byte[].class.equals(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertFromPayload(Class<?> clazz, MediaType contentType, byte[] payload) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canConvertToPayload(Class<?> clazz, MediaType mediaType) {
|
||||
return byte[].class.equals(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] convertToPayload(Object content, MediaType contentType) {
|
||||
return (byte[]) content;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.web.messaging.converter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class CompositeMessageConverter implements MessageConverter {
|
||||
|
||||
private static final boolean jackson2Present =
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", CompositeMessageConverter.class.getClassLoader()) &&
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", CompositeMessageConverter.class.getClassLoader());
|
||||
|
||||
private final List<MessageConverter> converters;
|
||||
|
||||
|
||||
public CompositeMessageConverter(List<MessageConverter> converters) {
|
||||
if (converters == null) {
|
||||
this.converters = new ArrayList<MessageConverter>();
|
||||
this.converters.add(new ByteArrayMessageConverter());
|
||||
this.converters.add(new StringMessageConverter());
|
||||
if (jackson2Present) {
|
||||
this.converters.add(new MappingJackson2MessageConverter());
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.converters = converters;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canConvertFromPayload(Class<?> clazz, MediaType contentType) {
|
||||
for (MessageConverter converter : this.converters) {
|
||||
if (converter.canConvertFromPayload(clazz, contentType)) {
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertFromPayload(Class<?> clazz, MediaType contentType, byte[] payload)
|
||||
throws IOException, ContentTypeNotSupportedException {
|
||||
|
||||
for (MessageConverter converter : this.converters) {
|
||||
if (converter.canConvertFromPayload(clazz, contentType)) {
|
||||
return converter.convertFromPayload(clazz, contentType, payload);
|
||||
}
|
||||
}
|
||||
throw new ContentTypeNotSupportedException(contentType, clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canConvertToPayload(Class<?> clazz, MediaType mediaType) {
|
||||
for (MessageConverter converter : this.converters) {
|
||||
if (converter.canConvertToPayload(clazz, mediaType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] convertToPayload(Object content, MediaType contentType)
|
||||
throws IOException, ContentTypeNotSupportedException {
|
||||
|
||||
for (MessageConverter converter : this.converters) {
|
||||
if (converter.canConvertToPayload(content.getClass(), contentType)) {
|
||||
return converter.convertToPayload(content, contentType);
|
||||
}
|
||||
}
|
||||
throw new ContentTypeNotSupportedException(contentType, content.getClass());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.web.messaging.converter;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class ContentTypeNotSupportedException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -3597879520747071896L;
|
||||
|
||||
private final MediaType mediaType;
|
||||
|
||||
private final Class<?> sourceOrTargetType;
|
||||
|
||||
|
||||
public ContentTypeNotSupportedException(MediaType mediaType, Class<?> sourceOrTargetType) {
|
||||
super("Content type '" + mediaType + "' not supported");
|
||||
this.mediaType = mediaType;
|
||||
this.sourceOrTargetType = sourceOrTargetType;
|
||||
}
|
||||
|
||||
public MediaType getMediaType() {
|
||||
return this.mediaType;
|
||||
}
|
||||
|
||||
public Class<?> getSourceOrTargetType() {
|
||||
return this.sourceOrTargetType;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.web.messaging.converter;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonEncoding;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
/**
|
||||
* Implementation of {@link MessageConverter} that can read and write JSON using <a
|
||||
* href="http://jackson.codehaus.org/">Jackson 2.x's</a> {@link ObjectMapper}.
|
||||
* <p>
|
||||
* This converter can be used to bind to typed beans, or untyped {@link java.util.HashMap
|
||||
* HashMap} instances.
|
||||
* <p>
|
||||
* By default, this converter supports {@code application/json}. This can be overridden by
|
||||
* setting the {@link #setSupportedMediaTypes supportedMediaTypes} property.
|
||||
* <p>
|
||||
* Tested against Jackson 2.2; compatible with Jackson 2.0 and higher.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Arjen Poutsma
|
||||
* @since 4.0
|
||||
*/
|
||||
public class MappingJackson2MessageConverter extends AbstractMessageConverter {
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private boolean prefixJson = false;
|
||||
|
||||
private Boolean prettyPrint;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new {@code MappingJackson2HttpMessageConverter}.
|
||||
*/
|
||||
public MappingJackson2MessageConverter() {
|
||||
super(new MediaType("application", "json"), new MediaType("application", "*+json"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the {@code ObjectMapper} for this view. If not set, a default
|
||||
* {@link ObjectMapper#ObjectMapper() ObjectMapper} is used.
|
||||
* <p>
|
||||
* Setting a custom-configured {@code ObjectMapper} is one way to take further control
|
||||
* of the JSON serialization process. For example, an extended
|
||||
* {@link org.codehaus.jackson.map.SerializerFactory} can be configured that provides
|
||||
* custom serializers for specific types. The other option for refining the
|
||||
* serialization process is to use Jackson's provided annotations on the types to be
|
||||
* serialized, in which case a custom-configured ObjectMapper is unnecessary.
|
||||
*/
|
||||
public void setObjectMapper(ObjectMapper objectMapper) {
|
||||
Assert.notNull(objectMapper, "ObjectMapper must not be null");
|
||||
this.objectMapper = objectMapper;
|
||||
configurePrettyPrint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying {@code ObjectMapper} for this view.
|
||||
*/
|
||||
public ObjectMapper getObjectMapper() {
|
||||
return this.objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether the JSON output by this view should be prefixed with "{} &&".
|
||||
* Default is false.
|
||||
* <p>
|
||||
* Prefixing the JSON string in this manner is used to help prevent JSON Hijacking.
|
||||
* The prefix renders the string syntactically invalid as a script so that it cannot
|
||||
* be hijacked. This prefix does not affect the evaluation of JSON, but if JSON
|
||||
* validation is performed on the string, the prefix would need to be ignored.
|
||||
*/
|
||||
public void setPrefixJson(boolean prefixJson) {
|
||||
this.prefixJson = prefixJson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use the {@link DefaultPrettyPrinter} when writing JSON.
|
||||
* This is a shortcut for setting up an {@code ObjectMapper} as follows:
|
||||
* <pre class="code">
|
||||
* ObjectMapper mapper = new ObjectMapper();
|
||||
* mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
* converter.setObjectMapper(mapper);
|
||||
* </pre>
|
||||
*/
|
||||
public void setPrettyPrint(boolean prettyPrint) {
|
||||
this.prettyPrint = prettyPrint;
|
||||
configurePrettyPrint();
|
||||
}
|
||||
|
||||
private void configurePrettyPrint() {
|
||||
if (this.prettyPrint != null) {
|
||||
this.objectMapper.configure(SerializationFeature.INDENT_OUTPUT, this.prettyPrint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canConvertFromPayload(Class<?> clazz, MediaType mediaType) {
|
||||
JavaType javaType = getJavaType(clazz, null);
|
||||
return (this.objectMapper.canDeserialize(javaType) && canConvertFrom(mediaType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canConvertToPayload(Class<?> clazz, MediaType mediaType) {
|
||||
return (this.objectMapper.canSerialize(clazz) && canConvertTo(mediaType));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
// should not be called, since we override canRead/Write instead
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object convertFromPayloadInternal(Class<? extends Object> clazz,
|
||||
MediaType contentType, byte[] payload) throws IOException {
|
||||
|
||||
JavaType javaType = getJavaType(clazz, null);
|
||||
return readJavaType(javaType, payload);
|
||||
}
|
||||
|
||||
private Object readJavaType(JavaType javaType, byte[] payload) {
|
||||
try {
|
||||
return this.objectMapper.readValue(payload, javaType);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] convertToPayloadInternal(Object object, MediaType contentType) throws IOException {
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
// The following has been deprecated as late as Jackson 2.2 (April 2013);
|
||||
// preserved for the time being, for Jackson 2.0/2.1 compatibility.
|
||||
@SuppressWarnings("deprecation")
|
||||
JsonGenerator jsonGenerator =
|
||||
this.objectMapper.getJsonFactory().createJsonGenerator(out, JsonEncoding.UTF8);
|
||||
|
||||
// A workaround for JsonGenerators not applying serialization features
|
||||
// https://github.com/FasterXML/jackson-databind/issues/12
|
||||
if (this.objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
|
||||
jsonGenerator.useDefaultPrettyPrinter();
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.prefixJson) {
|
||||
jsonGenerator.writeRaw("{} && ");
|
||||
}
|
||||
this.objectMapper.writeValue(jsonGenerator, object);
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
// TODO: more specific exception
|
||||
throw new IllegalStateException("Could not write JSON: " + ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Jackson {@link JavaType} for the specified type and context class.
|
||||
* <p>The default implementation returns {@code typeFactory.constructType(type, contextClass)},
|
||||
* but this can be overridden in subclasses, to allow for custom generic collection handling.
|
||||
* For instance:
|
||||
* <pre class="code">
|
||||
* protected JavaType getJavaType(Type type) {
|
||||
* if (type instanceof Class && List.class.isAssignableFrom((Class)type)) {
|
||||
* return TypeFactory.collectionType(ArrayList.class, MyBean.class);
|
||||
* } else {
|
||||
* return super.getJavaType(type);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* @param type the type to return the java type for
|
||||
* @param contextClass a context class for the target type, for example a class
|
||||
* in which the target type appears in a method signature, can be {@code null}
|
||||
* signature, can be {@code null}
|
||||
* @return the java type
|
||||
*/
|
||||
protected JavaType getJavaType(Type type, Class<?> contextClass) {
|
||||
return this.objectMapper.getTypeFactory().constructType(type, contextClass);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2010 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.web.messaging.converter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* Strategy for converting a byte array message payload to and from a typed object.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface MessageConverter {
|
||||
|
||||
|
||||
/**
|
||||
* Whether instances of the given class can be converted to from a byte array.
|
||||
*
|
||||
* @param clazz the class to convert from
|
||||
* @param mediaType the media type of the content, can be {@code null} if not
|
||||
* specified. Typically the value of a {@code Content-Type} header.
|
||||
* @return {@code true} if it can be converted; {@code false} otherwise
|
||||
*/
|
||||
boolean canConvertFromPayload(Class<?> clazz, MediaType mediaType);
|
||||
|
||||
/**
|
||||
* Convert the byte array payload to the given type.
|
||||
*
|
||||
* @param clazz the type of object to return. This type must have previously been
|
||||
* passed to {@link #canConvertFromPayload(Class, MediaType)} and it must have
|
||||
* returned {@code true}.
|
||||
* @param contentType the content type of the payload, can be {@code null}
|
||||
* @param payload the payload to convert from
|
||||
* @return the converted object
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
Object convertFromPayload(Class<?> clazz, MediaType contentType, byte[] payload)
|
||||
throws IOException, ContentTypeNotSupportedException;
|
||||
|
||||
/**
|
||||
* Whether instances of the given class can be converted to a byte array.
|
||||
*
|
||||
* @param clazz the class to test
|
||||
* @param mediaType the media type of the content, can be {@code null} if not specified.
|
||||
* @return {@code true} if writable; {@code false} otherwise
|
||||
*/
|
||||
boolean canConvertToPayload(Class<?> clazz, MediaType mediaType);
|
||||
|
||||
/**
|
||||
* Convert the given object to a byte array.
|
||||
*
|
||||
* @param t the object to convert. The type of this object must have previously been
|
||||
* passed to {@link #canConvertToPayload(Class, MediaType)} and it must have returned
|
||||
* {@code true}.
|
||||
* @param headers
|
||||
* @return the output message
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
byte[] convertToPayload(Object content, MediaType contentType) throws IOException, ContentTypeNotSupportedException;
|
||||
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.web.messaging.converter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class StringMessageConverter extends AbstractMessageConverter {
|
||||
|
||||
private static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
|
||||
public StringMessageConverter() {
|
||||
super(MediaType.TEXT_PLAIN);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return String.class.equals(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String convertFromPayloadInternal(Class<?> clazz, MediaType contentType, byte[] payload)
|
||||
throws IOException {
|
||||
|
||||
return new String(payload, UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] convertToPayloadInternal(Object content, MediaType contentType) throws IOException {
|
||||
return ((String) content).getBytes(UTF_8);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ import org.springframework.core.annotation.AnnotationUtils;
|
|||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.annotation.MessageMapping;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
|
@ -42,7 +43,6 @@ import org.springframework.util.ReflectionUtils.MethodFilter;
|
|||
import org.springframework.web.messaging.MessageType;
|
||||
import org.springframework.web.messaging.annotation.SubscribeEvent;
|
||||
import org.springframework.web.messaging.annotation.UnsubscribeEvent;
|
||||
import org.springframework.web.messaging.converter.MessageConverter;
|
||||
import org.springframework.web.messaging.service.AbstractWebMessageHandler;
|
||||
import org.springframework.web.messaging.support.MessageHolder;
|
||||
import org.springframework.web.messaging.support.WebMessageHeaderAccesssor;
|
||||
|
|
@ -61,7 +61,7 @@ public class AnnotationWebMessageHandler extends AbstractWebMessageHandler
|
|||
|
||||
private final MessageChannel outboundChannel;
|
||||
|
||||
private List<MessageConverter> messageConverters;
|
||||
private MessageConverter<?> messageConverter;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
|
|
@ -90,8 +90,11 @@ public class AnnotationWebMessageHandler extends AbstractWebMessageHandler
|
|||
this.outboundChannel = outboundChannel;
|
||||
}
|
||||
|
||||
public void setMessageConverters(List<MessageConverter> converters) {
|
||||
this.messageConverters = converters;
|
||||
/**
|
||||
* TODO: multiple converters with 'content-type' header
|
||||
*/
|
||||
public void setMessageConverter(MessageConverter<?> converter) {
|
||||
this.messageConverter = converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -109,11 +112,10 @@ public class AnnotationWebMessageHandler extends AbstractWebMessageHandler
|
|||
|
||||
initHandlerMethods();
|
||||
|
||||
this.argumentResolvers.addResolver(new MessageChannelArgumentResolver(this.inboundChannel));
|
||||
this.argumentResolvers.addResolver(new MessageBodyArgumentResolver(this.messageConverters));
|
||||
this.argumentResolvers.addResolver(new MessageBodyArgumentResolver(this.messageConverter));
|
||||
|
||||
this.returnValueHandlers.addHandler(new MessageReturnValueHandler(this.outboundChannel));
|
||||
this.returnValueHandlers.addHandler(new PayloadReturnValueHandler(this.outboundChannel));
|
||||
this.returnValueHandlers.addHandler(
|
||||
new MessageSendingReturnValueHandler(this.outboundChannel, this.messageConverter));
|
||||
}
|
||||
|
||||
protected void initHandlerMethods() {
|
||||
|
|
|
|||
|
|
@ -16,16 +16,11 @@
|
|||
|
||||
package org.springframework.web.messaging.service.method;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.messaging.annotation.MessageBody;
|
||||
import org.springframework.web.messaging.converter.CompositeMessageConverter;
|
||||
import org.springframework.web.messaging.converter.MessageConversionException;
|
||||
import org.springframework.web.messaging.converter.MessageConverter;
|
||||
import org.springframework.web.messaging.support.WebMessageHeaderAccesssor;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -34,11 +29,12 @@ import org.springframework.web.messaging.support.WebMessageHeaderAccesssor;
|
|||
*/
|
||||
public class MessageBodyArgumentResolver implements ArgumentResolver {
|
||||
|
||||
private final MessageConverter converter;
|
||||
private final MessageConverter<?> converter;
|
||||
|
||||
|
||||
public MessageBodyArgumentResolver(List<MessageConverter> converters) {
|
||||
this.converter = new CompositeMessageConverter(converters);
|
||||
public MessageBodyArgumentResolver(MessageConverter<?> converter) {
|
||||
Assert.notNull(converter, "converter is required");
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -52,19 +48,16 @@ public class MessageBodyArgumentResolver implements ArgumentResolver {
|
|||
Object arg = null;
|
||||
|
||||
MessageBody annot = parameter.getParameterAnnotation(MessageBody.class);
|
||||
MediaType contentType = (MediaType) message.getHeaders().get(WebMessageHeaderAccesssor.CONTENT_TYPE);
|
||||
|
||||
if (annot == null || annot.required()) {
|
||||
Class<?> sourceType = message.getPayload().getClass();
|
||||
Class<?> parameterType = parameter.getParameterType();
|
||||
if (parameterType.isAssignableFrom(sourceType)) {
|
||||
Class<?> sourceClass = message.getPayload().getClass();
|
||||
Class<?> targetClass = parameter.getParameterType();
|
||||
if (targetClass.isAssignableFrom(sourceClass)) {
|
||||
return message.getPayload();
|
||||
}
|
||||
else if (byte[].class.equals(sourceType)) {
|
||||
return this.converter.convertFromPayload(parameterType, contentType, (byte[]) message.getPayload());
|
||||
}
|
||||
else {
|
||||
throw new MessageConversionException(message, "Unexpected payload type", null);
|
||||
// TODO: use content-type header
|
||||
return this.converter.fromMessage(message, targetClass);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.web.messaging.service.method;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class MessageChannelArgumentResolver implements ArgumentResolver {
|
||||
|
||||
private MessageChannel inboundChannel;
|
||||
|
||||
|
||||
public MessageChannelArgumentResolver(MessageChannel inboundChannel) {
|
||||
Assert.notNull(inboundChannel, "inboundChannel is required");
|
||||
this.inboundChannel = inboundChannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return MessageChannel.class.equals(parameter.getParameterType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
|
||||
return this.inboundChannel;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ package org.springframework.web.messaging.service.method;
|
|||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.messaging.support.WebMessageHeaderAccesssor;
|
||||
|
|
@ -28,47 +29,50 @@ import org.springframework.web.messaging.support.WebMessageHeaderAccesssor;
|
|||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class MessageReturnValueHandler implements ReturnValueHandler {
|
||||
public class MessageSendingReturnValueHandler implements ReturnValueHandler {
|
||||
|
||||
private MessageChannel outboundChannel;
|
||||
|
||||
private final MessageConverter converter;
|
||||
|
||||
public MessageReturnValueHandler(MessageChannel outboundChannel) {
|
||||
|
||||
public MessageSendingReturnValueHandler(MessageChannel outboundChannel, MessageConverter<?> converter) {
|
||||
Assert.notNull(outboundChannel, "outboundChannel is required");
|
||||
Assert.notNull(converter, "converter is required");
|
||||
this.outboundChannel = outboundChannel;
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
// TODO: List<Message> return value
|
||||
Class<?> paramType = returnType.getParameterType();
|
||||
return Message.class.isAssignableFrom(paramType);
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void handleReturnValue(Object returnValue, MethodParameter returnType, Message<?> message)
|
||||
throws Exception {
|
||||
|
||||
Assert.notNull(this.outboundChannel, "No clientChannel to send messages to");
|
||||
|
||||
Message<?> returnMessage = (Message<?>) returnValue;
|
||||
if (message == null) {
|
||||
if (returnValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.wrap(message);
|
||||
Assert.notNull(headers.getSubscriptionId(), "No subscription id: " + message);
|
||||
WebMessageHeaderAccesssor inputHeaders = WebMessageHeaderAccesssor.wrap(message);
|
||||
Message<?> returnMessage = (returnValue instanceof Message) ? (Message<?>) returnValue : null;
|
||||
Object returnPayload = (returnMessage != null) ? returnMessage.getPayload() : returnValue;
|
||||
|
||||
WebMessageHeaderAccesssor returnHeaders = WebMessageHeaderAccesssor.wrap(returnMessage);
|
||||
returnHeaders.setSessionId(headers.getSessionId());
|
||||
returnHeaders.setSubscriptionId(headers.getSubscriptionId());
|
||||
WebMessageHeaderAccesssor returnHeaders = (returnMessage != null) ?
|
||||
WebMessageHeaderAccesssor.wrap(returnMessage) : WebMessageHeaderAccesssor.create();
|
||||
|
||||
returnHeaders.setSessionId(inputHeaders.getSessionId());
|
||||
returnHeaders.setSubscriptionId(inputHeaders.getSubscriptionId());
|
||||
if (returnHeaders.getDestination() == null) {
|
||||
returnHeaders.setDestination(headers.getDestination());
|
||||
returnHeaders.setDestination(inputHeaders.getDestination());
|
||||
}
|
||||
|
||||
returnMessage = MessageBuilder.withPayload(
|
||||
returnMessage.getPayload()).copyHeaders(returnHeaders.toMap()).build();
|
||||
returnMessage = this.converter.toMessage(returnPayload);
|
||||
returnMessage = MessageBuilder.fromMessage(returnMessage).copyHeaders(returnHeaders.toMap()).build();
|
||||
|
||||
this.outboundChannel.send(returnMessage);
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.web.messaging.service.method;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.messaging.support.WebMessageHeaderAccesssor;
|
||||
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class PayloadReturnValueHandler implements ReturnValueHandler {
|
||||
|
||||
private MessageChannel outboundChannel;
|
||||
|
||||
|
||||
public PayloadReturnValueHandler(MessageChannel outboundChannel) {
|
||||
Assert.notNull(outboundChannel, "outboundChannel is required");
|
||||
this.outboundChannel = outboundChannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleReturnValue(Object returnValue, MethodParameter returnType, Message<?> message)
|
||||
throws Exception {
|
||||
|
||||
Assert.notNull(this.outboundChannel, "No outboundChannel to send messages to");
|
||||
|
||||
if (returnValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.wrap(message);
|
||||
|
||||
WebMessageHeaderAccesssor returnHeaders = WebMessageHeaderAccesssor.create();
|
||||
returnHeaders.setDestination(headers.getDestination());
|
||||
returnHeaders.setSessionId(headers.getSessionId());
|
||||
returnHeaders.setSubscriptionId(headers.getSubscriptionId());
|
||||
|
||||
Message<?> returnMessage = MessageBuilder.withPayload(
|
||||
returnValue).copyHeaders(returnHeaders.toMap()).build();
|
||||
|
||||
this.outboundChannel.send(returnMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -27,27 +27,25 @@ import java.util.concurrent.LinkedBlockingQueue;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.context.SmartLifecycle;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.messaging.MessageType;
|
||||
import org.springframework.web.messaging.converter.CompositeMessageConverter;
|
||||
import org.springframework.web.messaging.converter.MessageConverter;
|
||||
import org.springframework.web.messaging.service.AbstractWebMessageHandler;
|
||||
import org.springframework.web.messaging.stomp.StompCommand;
|
||||
import org.springframework.web.messaging.support.WebMessageHeaderAccesssor;
|
||||
|
||||
import reactor.core.Environment;
|
||||
import reactor.core.Promise;
|
||||
import reactor.fn.Consumer;
|
||||
import reactor.core.composable.Promise;
|
||||
import reactor.function.Consumer;
|
||||
import reactor.tcp.TcpClient;
|
||||
import reactor.tcp.TcpConnection;
|
||||
import reactor.tcp.encoding.DelimitedCodec;
|
||||
import reactor.tcp.encoding.StandardCodecs;
|
||||
import reactor.tcp.netty.NettyTcpClient;
|
||||
import reactor.tcp.spec.TcpClientSpec;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -71,8 +69,6 @@ public class StompRelayWebMessageHandler extends AbstractWebMessageHandler imple
|
|||
|
||||
private final StompMessageConverter stompMessageConverter = new StompMessageConverter();
|
||||
|
||||
private MessageConverter payloadConverter;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
private TcpClient<String, String> tcpClient;
|
||||
|
|
@ -90,7 +86,6 @@ public class StompRelayWebMessageHandler extends AbstractWebMessageHandler imple
|
|||
public StompRelayWebMessageHandler(MessageChannel outboundChannel) {
|
||||
Assert.notNull(outboundChannel, "outboundChannel is required");
|
||||
this.outboundChannel = outboundChannel;
|
||||
this.payloadConverter = new CompositeMessageConverter(null);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -154,10 +149,6 @@ public class StompRelayWebMessageHandler extends AbstractWebMessageHandler imple
|
|||
return this.systemPasscode;
|
||||
}
|
||||
|
||||
public void setMessageConverters(List<MessageConverter> converters) {
|
||||
this.payloadConverter = new CompositeMessageConverter(converters);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<MessageType> getSupportedMessageTypes() {
|
||||
return null;
|
||||
|
|
@ -183,9 +174,10 @@ public class StompRelayWebMessageHandler extends AbstractWebMessageHandler imple
|
|||
@Override
|
||||
public void start() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
|
||||
this.environment = new Environment();
|
||||
this.tcpClient = new TcpClient.Spec<String, String>(NettyTcpClient.class)
|
||||
.using(this.environment)
|
||||
this.tcpClient = new TcpClientSpec<String, String>(NettyTcpClient.class)
|
||||
.env(this.environment)
|
||||
.codec(new DelimitedCodec<String, String>((byte) 0, true, StandardCodecs.STRING_CODEC))
|
||||
.connect(this.relayHost, this.relayPort)
|
||||
.get();
|
||||
|
|
@ -284,10 +276,6 @@ public class StompRelayWebMessageHandler extends AbstractWebMessageHandler imple
|
|||
StompHeaderAccessor headers = StompHeaderAccessor.wrap(message);
|
||||
headers.setStompCommandIfNotSet(command);
|
||||
|
||||
if (headers.getSessionId() == null && (StompCommand.SEND.equals(command))) {
|
||||
|
||||
}
|
||||
|
||||
String sessionId = headers.getSessionId();
|
||||
if (sessionId == null) {
|
||||
if (StompCommand.SEND.equals(command)) {
|
||||
|
|
@ -301,8 +289,7 @@ public class StompRelayWebMessageHandler extends AbstractWebMessageHandler imple
|
|||
|
||||
RelaySession session = this.relaySessions.get(sessionId);
|
||||
if (session == null) {
|
||||
// TODO: default (non-user) session for sending messages?
|
||||
logger.warn("No relay session for " + sessionId + ". Message '" + message + "' cannot be forwarded");
|
||||
logger.warn("Session id=" + sessionId + " not found. Message cannot be forwarded: " + message);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -438,16 +425,12 @@ public class StompRelayWebMessageHandler extends AbstractWebMessageHandler imple
|
|||
try {
|
||||
headers.setStompCommandIfNotSet(StompCommand.SEND);
|
||||
|
||||
MediaType contentType = headers.getContentType();
|
||||
byte[] payload = payloadConverter.convertToPayload(message.getPayload(), contentType);
|
||||
Message<?> byteMessage = MessageBuilder.withPayload(payload).copyHeaders(headers.toMap()).build();
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Forwarding message " + byteMessage);
|
||||
logger.trace("Forwarding message " + message);
|
||||
}
|
||||
|
||||
byte[] bytesToWrite = stompMessageConverter.fromMessage(byteMessage);
|
||||
connection.send(new String(bytesToWrite, Charset.forName("UTF-8")));
|
||||
byte[] bytes = stompMessageConverter.fromMessage(message);
|
||||
connection.send(new String(bytes, Charset.forName("UTF-8")));
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.error("Failed to forward message " + message, ex);
|
||||
|
|
|
|||
|
|
@ -17,21 +17,17 @@ package org.springframework.web.messaging.stomp.support;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.web.messaging.MessageType;
|
||||
import org.springframework.web.messaging.converter.CompositeMessageConverter;
|
||||
import org.springframework.web.messaging.converter.MessageConverter;
|
||||
import org.springframework.web.messaging.stomp.StompCommand;
|
||||
import org.springframework.web.messaging.stomp.StompConversionException;
|
||||
import org.springframework.web.messaging.support.WebMessageHeaderAccesssor;
|
||||
|
|
@ -59,8 +55,6 @@ public class StompWebSocketHandler extends TextWebSocketHandlerAdapter implement
|
|||
|
||||
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<String, WebSocketSession>();
|
||||
|
||||
private MessageConverter payloadConverter = new CompositeMessageConverter(null);
|
||||
|
||||
|
||||
/**
|
||||
* @param outputChannel the channel to which incoming STOMP/WebSocket messages should
|
||||
|
|
@ -72,15 +66,10 @@ public class StompWebSocketHandler extends TextWebSocketHandlerAdapter implement
|
|||
}
|
||||
|
||||
|
||||
public void setMessageConverters(List<MessageConverter> converters) {
|
||||
this.payloadConverter = new CompositeMessageConverter(converters);
|
||||
}
|
||||
|
||||
public StompMessageConverter getStompMessageConverter() {
|
||||
return this.stompMessageConverter;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
||||
Assert.notNull(this.outputChannel, "No output channel for STOMP messages.");
|
||||
|
|
@ -161,7 +150,7 @@ public class StompWebSocketHandler extends TextWebSocketHandlerAdapter implement
|
|||
|
||||
Message<?> connectedMessage = MessageBuilder.withPayload(EMPTY_PAYLOAD).copyHeaders(
|
||||
connectedHeaders.toMap()).build();
|
||||
byte[] bytes = getStompMessageConverter().fromMessage(connectedMessage);
|
||||
byte[] bytes = this.stompMessageConverter.fromMessage(connectedMessage);
|
||||
session.sendMessage(new TextMessage(new String(bytes, Charset.forName("UTF-8"))));
|
||||
}
|
||||
|
||||
|
|
@ -221,36 +210,32 @@ public class StompWebSocketHandler extends TextWebSocketHandlerAdapter implement
|
|||
String sessionId = headers.getSessionId();
|
||||
if (sessionId == null) {
|
||||
// TODO: failed message delivery mechanism
|
||||
logger.error("No \"sessionId\" header in message: " + message);
|
||||
logger.error("Ignoring message, no sessionId header: " + message);
|
||||
return;
|
||||
}
|
||||
|
||||
WebSocketSession session = this.sessions.get(sessionId);
|
||||
if (session == null) {
|
||||
// TODO: failed message delivery mechanism
|
||||
logger.error("WebSocketSession not found for sessionId=" + sessionId);
|
||||
logger.error("Ignoring message, session not found: " + sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (headers.getSubscriptionId() == null) {
|
||||
// TODO: failed message delivery mechanism
|
||||
logger.error("No subscription id: " + message);
|
||||
logger.error("Ignoring message, no subscriptionId header: " + message);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] payload;
|
||||
try {
|
||||
MediaType contentType = headers.getContentType();
|
||||
payload = payloadConverter.convertToPayload(message.getPayload(), contentType);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
logger.error("Failed to send " + message, t);
|
||||
if (!(message.getPayload() instanceof byte[])) {
|
||||
// TODO: failed message delivery mechanism
|
||||
logger.error("Ignoring message, expected byte[] content: " + message);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Message<?> byteMessage = MessageBuilder.withPayload(payload).copyHeaders(headers.toMap()).build();
|
||||
byte[] bytes = getStompMessageConverter().fromMessage(byteMessage);
|
||||
message = MessageBuilder.fromMessage(message).copyHeaders(headers.toMap()).build();
|
||||
byte[] bytes = this.stompMessageConverter.fromMessage(message);
|
||||
session.sendMessage(new TextMessage(new String(bytes, Charset.forName("UTF-8"))));
|
||||
}
|
||||
catch (Throwable t) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue