Spring Social ProviderSignInUtils.getConnection is returning "error: cannot find symbol" - spring

I'm creating a controller to register an user that has logged in using oauth2 but whenever I try to get the connection using ProviderSignInUtils.getConnection(request) it says the function does not exist.
This is my controller:
import org.springframework.social.connect.web.ProviderSignInUtils;
#RequestMapping(value = "/register", method = RequestMethod.GET)
public String showRegistrationForm(WebRequest request, Model model) {
Connection<?> connection = ProviderSignInUtils.getConnection(request);
RegistrationForm registration = createRegistrationDTO(connection);
model.addAttribute("user", registration);
return "user/registrationForm";
}
Those are the maven dependencies:
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-config</artifactId>
<version>1.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-core</artifactId>
<version>1.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
<version>1.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-web</artifactId>
<version>1.1.2.RELEASE</version>
</dependency>

ProviderSignInUtils.getConnection was removed in Spring Social 1.1.2, however the documentation wasn't updated to reflect this. The example code at github shows this instead
#Inject
public SignupController(AccountRepository accountRepository,
ConnectionFactoryLocator connectionFactoryLocator,
UsersConnectionRepository connectionRepository) {
this.accountRepository = accountRepository;
this.providerSignInUtils = new ProviderSignInUtils(connectionFactoryLocator, connectionRepository);
}
#RequestMapping(value="/signup", method=RequestMethod.GET)
public SignupForm signupForm(WebRequest request) {
Connection<?> connection = providerSignInUtils.getConnectionFromSession(request);
if (connection != null) {
request.setAttribute("message", new Message(MessageType.INFO, "Your " + StringUtils.capitalize(connection.getKey().getProviderId()) + " account is not associated with a Spring Social Showcase account. If you're new, please sign up."), WebRequest.SCOPE_REQUEST);
return SignupForm.fromProviderUser(connection.fetchUserProfile());
} else {
return new SignupForm();
}
}
You need to create your own local providerSignInUtils so it has access to the connectionFactoryLocator and connectionRepository.

Related

Broadcast to multiple reactive WebSocketSessions created using springboot webflux is not working

Following is the scenario:
I create a reactor kafka receiver
Data consumed from kafka receiver is published to a WebSocketHanlder
WebSocketHanlder is mapped to a URL using SimpleUrlHandlerMapping
URL pattern is api/v1/ws/{ID} and I expect multiple WebSocketSession to get created based on different ID used in URI which are managed by single WebSocketHanlder, which is actually happening
But when data from kafka receiver is published, only first created WebSocketSession receiveds it and all other WebSocketSessions do not receive the data
I am using spring-boot 2.6.3 with starter-tomcat
How to publish data to all the WebSocketSessions created
My Code:
Config for web socket handler
#Configuration
#Slf4j
public class OneSecPollingWebSocketConfig
{
private OneSecPollingWebSocketHandler oneSecPollingHandler;
#Autowired
public OneSecPollingWebSocketConfig(OneSecPollingWebSocketHandler oneSecPollingHandler)
{
this.oneSecPollingHandler = oneSecPollingHandler;
}
#Bean
public HandlerMapping webSocketHandlerMapping()
{
log.info("onesecpolling websocket configured");
Map<String, WebSocketHandler> handlerMap = new HashMap<>();
handlerMap.put(WEB_SOCKET_ENDPOINT, oneSecPollingHandler);
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(handlerMap);
mapping.setOrder(1);
return mapping;
}
}
Code for WebSocket HAndler
#Component
#Slf4j
public class OneSecPollingWebSocketHandler implements WebSocketHandler
{
private ObjectMapper objectMapper;
private OneSecPollingKafkaConsumerService oneSecPollingKafkaConsumerService;
private Map<String, WebSocketSession> wsSessionsByUserSessionId = new HashMap<>();
#Autowired
public OneSecPollingWebSocketHandler(ObjectMapper objectMapper, OneSecPollingKafkaConsumerService oneSecPollingKafkaConsumerService)
{
this.objectMapper = objectMapper;
this.oneSecPollingKafkaConsumerService = oneSecPollingKafkaConsumerService;
}
#Override
public Mono<Void> handle(WebSocketSession webSocketSession)
{
Many<String> sink = Sinks.many().multicast().onBackpressureBuffer(Queues.SMALL_BUFFER_SIZE, false);
wsSessionsByUserSessionId.put(getUserPollingSessionId(webSocketSession), webSocketSession);
sinkSubscription(webSocketSession, sink);
Mono<Void> output = webSocketSession.send(sink.asFlux().map(webSocketSession::textMessage)).doOnSubscribe(subscription ->
{
});
return Mono.zip(webSocketSession.receive().then(), output).then();
}
public void sinkSubscription(WebSocketSession webSocketSession, Many<String> sink)
{
log.info("number of sessions; {}", wsSessionsByUserSessionId.size());
oneSecPollingKafkaConsumerService.getTestTopicFlux().doOnNext(record ->
{
//log.info("record: {}", record);
sink.tryEmitNext(record.value());
record.receiverOffset().acknowledge();
}).subscribe();
}
public String getOneSecPollingTopicRecord(ReceiverRecord<Integer, String> record, WebSocketSession webSocketSession)
{
String lastRecord = record.value();
log.info("record to send: {} : webSocketSession: {}", record.value(), webSocketSession.getId());
record.receiverOffset().acknowledge();
return lastRecord;
}
public String getUserPollingSessionId(WebSocketSession webSocketSession)
{
UriTemplate template = new UriTemplate(WEB_SOCKET_ENDPOINT);
URI uri = webSocketSession.getHandshakeInfo().getUri();
Map<String, String> parameters = template.match(uri.getPath());
String userPollingSessionId = parameters.get("userPollingSessionId");
return userPollingSessionId;
}
}
Kafka Receiver
#Service
#Slf4j
public class OneSecPollingKafkaConsumerService
{
private String bootStrapServers;
#Autowired
public OneSecPollingKafkaConsumerService(#Value("${bootstrap.servers}") String bootStrapServers)
{
this.bootStrapServers = bootStrapServers;
}
private ReceiverOptions<Integer, String> getRecceiverOPtions()
{
Map<String, Object> consumerProps = new HashMap<>();
consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootStrapServers);
//consumerProps.put(ConsumerConfig.CLIENT_ID_CONFIG, "reactive-consumer");
consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, "onesecpolling-group");
consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, IntegerDeserializer.class);
consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
//consumerProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
//consumerProps.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
ReceiverOptions<Integer, String> receiverOptions = ReceiverOptions
.<Integer, String> create(consumerProps)
.subscription(Collections.singleton("HighFrequencyPollingKPIsComputedValues"));
return receiverOptions;
}
public Flux<ReceiverRecord<Integer, String>> getTestTopicFlux()
{
return createTopicCache();
}
private Flux<ReceiverRecord<Integer, String>> createTopicCache()
{
Flux<ReceiverRecord<Integer, String>> oneSecPollingMessagesFlux = KafkaReceiver.create(getRecceiverOPtions())
.receive()
.delayElements(Duration.ofMillis(500));
return oneSecPollingMessagesFlux;
}
}
POM dependencies
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor.kafka</groupId>
<artifactId>reactor-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- This is breaking WebFlux
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<classifier>test-binder</classifier>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<!-- <dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency> -->
</dependencies>
I also tried changing the handle(...) method definition in WebSocketHanlder to following, but still data from kafka is pushed to only one websocket session:
#Override
public Mono<Void> handle(WebSocketSession webSocketSession)
{
Mono<Void> input = webSocketSession.receive().then();
Mono<Void> output = webSocketSession.send(oneSecPollingKafkaConsumerService.getTestTopicFlux().map(ReceiverRecord::value).map(webSocketSession::textMessage));
return Mono.zip(input, output).then();
}
Also, I tried following:
public Mono<Void> handle(WebSocketSession webSocketSession)
{
Mono<Void> input = webSocketSession.receive()
.doOnSubscribe(subscribe -> log.info("sesseion created sessionId:{}:userId:{};sessionhash:{}",
webSocketSession.getId(),
getUserPollingSessionId(webSocketSession),
webSocketSession.hashCode()))
.then();
Flux<String> source = oneSecPollingKafkaConsumerService.getTestTopicFlux().map(record -> getOneSecPollingTopicRecord(record, webSocketSession)).log();
Mono<Void> output = webSocketSession.send(source.map(webSocketSession::textMessage)).log();
return Mono.zip(input, output).then().log();
}
I enabled log() and got following output:
20:09:22.652 [http-nio-8080-exec-9] INFO c.m.e.w.p.i.w.v.OneSecPollingWebSocketHandler - sesseion created sessionId:a:userId:124;sessionhash:1974799413
20:09:22.652 [http-nio-8080-exec-9] INFO reactor.Flux.RefCount.41 - | onSubscribe([Fuseable] FluxRefCount.RefCountInner)
20:09:22.652 [http-nio-8080-exec-9] INFO reactor.Flux.Map.42 - onSubscribe(FluxMap.MapSubscriber)
20:09:22.652 [http-nio-8080-exec-9] INFO reactor.Flux.Map.42 - request(1)
20:09:22.652 [http-nio-8080-exec-9] INFO reactor.Flux.RefCount.41 - | request(32)
20:09:22.659 [http-nio-8080-exec-9] INFO reactor.Mono.FromPublisher.43 - onSubscribe(MonoNext.NextSubscriber)
20:09:22.659 [http-nio-8080-exec-9] INFO reactor.Mono.FromPublisher.43 - request(unbounded)
20:09:25.942 [http-nio-8080-exec-10] INFO reactor.Mono.IgnorePublisher.48 - onSubscribe(MonoIgnoreElements.IgnoreElementsSubscriber)
20:09:25.942 [http-nio-8080-exec-10] INFO reactor.Mono.IgnorePublisher.48 - request(unbounded)
20:09:25.942 [http-nio-8080-exec-10] INFO c.m.e.w.p.i.w.v.OneSecPollingWebSocketHandler - sesseion created sessionId:b:userId:123;sessionhash:1582184236
20:09:25.942 [http-nio-8080-exec-10] INFO reactor.Flux.RefCount.45 - | onSubscribe([Fuseable] FluxRefCount.RefCountInner)
20:09:25.942 [http-nio-8080-exec-10] INFO reactor.Flux.Map.46 - onSubscribe(FluxMap.MapSubscriber)
20:09:25.942 [http-nio-8080-exec-10] INFO reactor.Flux.Map.46 - request(1)
20:09:25.942 [http-nio-8080-exec-10] INFO reactor.Flux.RefCount.45 - | request(32)
20:09:25.947 [http-nio-8080-exec-10] INFO reactor.Mono.FromPublisher.47 - onSubscribe(MonoNext.NextSubscriber)
20:09:25.949 [http-nio-8080-exec-10] INFO reactor.Mono.FromPublisher.47 - request(unbounded)
20:10:00.880 [reactive-kafka-onesecpolling-group-11] INFO reactor.Flux.RefCount.41 - | onNext(ConsumerRecord(topic = HighFrequencyPollingKPIsComputedValues, partition = 0, leaderEpoch = null, offset = 474, CreateTime = 1644071999871, serialized key size = -1, serialized value size = 43, headers = RecordHeaders(headers = [], isReadOnly = false), key = null, value = {"greeting" : "Hello", "name" : "Prashant"}))
20:10:01.387 [parallel-5] INFO reactor.Flux.Map.42 - onNext({"greeting" : "Hello", "name" : "Prashant"})
20:10:01.389 [parallel-5] INFO reactor.Flux.Map.42 - request(1)
Here we can see that we have 2 subscribers to reactor-kafka flux:
reactor.Flux.Map.42 - onSubscribe(FluxMap.MapSubscriber
reactor.Flux.Map.46 - onSubscribe(FluxMap.MapSubscriber)
but when data is read from kafka topic, it is received by only one subscriber:
reactor.Flux.Map.42 - onNext({"greeting" : "Hello", "name" :
"Prashant"})
Is it a bug in the Webflux API itself ?
I have found the issue and the solution.
Problem
The way I was using Flux (obtained from KafkaReceiver) in WebSocketHandler handle() method is not correct.
For each websocket session created from multiple client requests, handle method is get called. And so, multiple Flux objects for KafkaReceiver.create().receive() are created. One of the Flux reads data from KafkaReceiver but other flux objects failed to do so.
public Mono<Void> handle(WebSocketSession webSocketSession)
{
Mono<Void> input = webSocketSession.receive()
.doOnSubscribe(subscribe -> log.info("sesseion created sessionId:{}:userId:{};sessionhash:{}",
webSocketSession.getId(),
getUserPollingSessionId(webSocketSession),
webSocketSession.hashCode()))
.then();
**Flux<String> source = oneSecPollingKafkaConsumerService.getTestTopicFlux()**.map(record -> getOneSecPollingTopicRecord(record, webSocketSession)).log();
Mono<Void> output = webSocketSession.send(source.map(webSocketSession::textMessage)).log();
return Mono.zip(input, output).then().log();
}
Solution
Make sure that only one Flux is created for
KafkaReceiver.create().receive(). One way to do so is to make Flux in the constructor of WebSocketHandler (or KAfkaCOnsumer class)
private final Flux<String> source;
#Autowired
public OneSecPollingWebSocketHandler(OneSecPollingKafkaConsumerService oneSecPollingKafkaConsumerService)
{
source = oneSecPollingKafkaConsumerService.getOneSecPollingTopicFlux().map(r -> getOneSecPollingTopicRecord(r));
}
#Override
public Mono<Void> handle(WebSocketSession webSocketSession)
{
// add usersession id as session attribute
Mono<Void> input = getInputMessageMono(webSocketSession);
Mono<Void> output = getOutputMessageMono(webSocketSession);
return Mono.zip(input, output).then().log();
}
private Mono<Void> getOutputMessageMono(WebSocketSession webSocketSession)
{
Mono<Void> output = webSocketSession.send(source.map(webSocketSession::textMessage)).doOnError(err -> log.error(err.getMessage())).doOnTerminate(() ->
{
log.info("onesecpolling session terminated;{}", webSocketSession.getId());
}).log();
return output;
}
private Mono<Void> getInputMessageMono(WebSocketSession webSocketSession)
{
Mono<Void> input = webSocketSession.receive().doOnSubscribe(subscribe ->
{
log.info("onesecpolling session created sessionId:{}:userId:{}", webSocketSession.getId(), getUserPollingSessionId(webSocketSession));
}).then();
return input;
}
private String getOneSecPollingTopicRecord(ReceiverRecord<Integer, String> record)
{
String lastRecord = record.value();
record.receiverOffset().acknowledge();
return lastRecord;
}
private String getUserPollingSessionId(WebSocketSession webSocketSession)
{
UriTemplate template = new UriTemplate(WEB_SOCKET_ENDPOINT);
URI uri = webSocketSession.getHandshakeInfo().getUri();
Map<String, String> parameters = template.match(uri.getPath());
String userPollingSessionId = parameters.get(WEB_SOCKET_ENDPOINT_USERID_SUBPATH);
return userPollingSessionId;
}

Spring Boot Application using Oracle - ORA-01000: maximum open cursors exceeded while using Spring Jdbctemplate

I know there are lot of solution for this in internet but nothing seems to work for me.
I have following entries for in the pom.xml file for my jdk11 app
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
</parent>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>21.1.0.0</version>
</dependency>
<dependency>
<groupId>com.oracle.database.ha</groupId>
<artifactId>ons</artifactId>
<version>21.1.0.0</version>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ucp</artifactId>
<version>21.1.0.0</version>
</dependency>
I am using connection pool with config as follows
platform: oracle
url: jdbc:oracle:thin:#localhost:1521:TEST
connectionFactoryClassName: oracle.jdbc.pool.OracleDataSource
fastConnectionFailoverEnabled: true
username: test
password: test
initialPoolSize: 3
minPoolSize: 0
maxPoolSize: 12
inactivityTimeout: 300
queryTimeout: 600
validateConnectionOnBorrow: true
I am only querying for table no add or update to oracle record, something like this
PoolDataSource dataSource = PoolDataSourceFactory.getPoolDataSource();
#Bean
public DaoSpringJdbcImpl listenerDAO(final DataSource dataSource ) {
NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(dataSource );
DaoSpringJdbcImpl listenerDAO = new DaoSpringJdbcImpl(template);
return listenerDAO;
}
public class DaoSpringJdbcImpl {
private NamedParameterJdbcTemplate jdbcTemplate;
public DaoSpringJdbcImpl(NamedParameterJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void method() {
List<String> results = jdbcTemplate.query(SQL_QUERY, params,
new RowMapper<String>()
{
public String mapRow(ResultSet rs, int rowNum) throws SQLException
{ return rs.getString(0)}
}
}
so everytime my application queries it opens up new cursor and never close it ultimately resulting to open cursor exception
PS I tried adding env property spring.jdbc.getParameterType.ignore = true which didnt work

Issue getting MessageAttributes in spring-cloud SNS/SQS listener

I am using spring-boot-1.5.10 and spring-cloud and using spring-cloud-starter-aws-messaging. I am able to send and receive the message but couldn't get the SNS message attributes. Any help would be really appreciable. Please find the code below,
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.19.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.aws.sample</groupId>
<artifactId>aws</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>aws</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Edgware.SR5</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-aws</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-aws-messaging</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
controller.java
#RestController
#RequestMapping(value = "/sns")
#AllArgsConstructor
public class SimpleSnsController {
private NotificationMessagingTemplate notificationMessagingTemplate;
#PostMapping("/saveEmployee")
public String save(#RequestBody Employee employee){
Map<String,Object> headers = new HashMap<>();
headers.put("subject", "send employee details to sqs");
headers.put("name","murugan");
headers.put("traceId","sample");
//notificationMessagingTemplate.sendNotification("sample-sns", employee, "send employee details to sqs");
notificationMessagingTemplate.convertAndSend("sample-sns", employee, headers);
return "success";
}
//#SqsListener(value = "sample-queue")
#SqsListener(value = "${sqs.consumer.name}")
public void receiveSnsSqs(String message, #NotificationMessage Employee employee) {
System.out.println("SNS Consumer received the message::"+message);
System.out.println("SNS Consumer received the notificationMessage::"+employee);
//Here i would like to get the message attribute
}
}
output message received:
{
"Type" : "Notification",
"MessageId" : "ba9dab52-aae8-5940-a3e2-ff8c8458ef52",
"TopicArn" : "arn:aws:sns:XXX",
"Message" : "{\"name\":\"David\",\"age\":\"31\",\"designation\":\"developer\"}",
"Timestamp" : "2019-02-13T14:40:48.501Z",
"SignatureVersion" : "1",
"Signature" : "XXX",
"SigningCertURL" : "XXX",
"UnsubscribeURL" : "XXX",
"MessageAttributes" : {
"traceId" : {"Type":"String","Value":"sample"},
"subject" : {"Type":"String","Value":"send employee details to sqs"},
"name" : {"Type":"String","Value":"murugan"},
"id" : {"Type":"String","Value":"68bf17f2-0f88-4cc5-0609-0ccd42b19ce4"},
"SenderId" : {"Type":"String","Value":"David"},
"contentType" : {"Type":"String","Value":"application/json;charset=UTF-8"},
"timestamp" : {"Type":"Number.java.lang.Long","Value":"1550068848349"}
}
}
I would like to fetch the messageAttribute like name,traceId in consumer which I set in SNS producer. I have browsed a lot but couldn't find any solution. Any help would be really appreciable.
Try enabling Raw Message Delivery.
It will not wrap the original SNS message, and allow you to get the message and headers through the normal annotations #Header, #Headers
https://docs.aws.amazon.com/sns/latest/dg/sns-large-payload-raw-message-delivery.html
If you can't use Raw Message Delivery, I made a new annotation to assist in retrieving a Notification Header
#NotificationHeader
import org.springframework.core.annotation.AliasFor;
import org.springframework.messaging.handler.annotation.ValueConstants;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface NotificationHeader {
/**
* Alias for {#link #name}.
*/
#AliasFor("name")
String value() default "";
/**
* The name of the request header to bind to.
*/
#AliasFor("value")
String name() default "";
/**
* Whether the header is required.
* <p>Default is {#code true}, leading to an exception if the header is
* missing. Switch this to {#code false} if you prefer a {#code null}
* value in case of a header missing.
* #see #defaultValue
*/
boolean required() default true;
/**
* The default value to use as a fallback.
* <p>Supplying a default value implicitly sets {#link #required} to {#code false}.
*/
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
*NotificationHeaderArgumentResolver
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.cloud.aws.messaging.support.NotificationMessageArgumentResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.handler.annotation.support.HeaderMethodArgumentResolver;
import org.springframework.util.Assert;
public class NotificationHeaderArgumentResolver extends HeaderMethodArgumentResolver {
private NotificationMessageArgumentResolver notificationArgumentResolver;
public NotificationHeaderArgumentResolver(ConversionService cs, ConfigurableBeanFactory beanFactory) {
super(cs, beanFactory);
notificationArgumentResolver = new NotificationMessageArgumentResolver(new NoOptMessageConverter());
}
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(NotificationHeader.class);
}
#Override
#Nullable
protected Object resolveArgumentInternal(MethodParameter parameter, Message<?> message, String name)
throws Exception {
Message notificationMessage = (Message) notificationArgumentResolver.resolveArgument(parameter, message);
return super.resolveArgumentInternal(parameter, notificationMessage, name);
}
#Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
NotificationHeader annotation = parameter.getParameterAnnotation(NotificationHeader.class);
Assert.state(annotation != null, "No Header annotation");
return new HeaderNamedValueInfo(annotation);
}
private static class HeaderNamedValueInfo extends NamedValueInfo {
private HeaderNamedValueInfo(NotificationHeader annotation) {
super(annotation.name(), annotation.required(), annotation.defaultValue());
}
}
public static class NoOptMessageConverter implements MessageConverter {
#Override
public Message<?> toMessage(Object payload, #Nullable MessageHeaders headers) {
return null;
}
#Override
public Object fromMessage(Message<?> message, Class<?> targetClass) {
return message;
}
}
}
*NotificationHeaderConfiguration
#Bean
public QueueMessageHandlerFactory queueMessageHandlerFactory() {
QueueMessageHandlerFactory queueMessageHandlerFactory = new QueueMessageHandlerFactory();
queueMessageHandlerFactory.setArgumentResolvers(Collections.singletonList(new NotificationHeaderArgumentResolver(null, null)));
return queueMessageHandlerFactory;
}

Websocket with ActiveMQ support multi session?

I have spring boot application use websocket and embeded ActiveMQ, when user (TestUser) subscribe /user/TestUser/reply in two different browser at the same time ,then send message to him one browser received the another not , send again new one the second receive but first one not and so on…… .
What I expected when send message to /user/TestUser/reply , if he is open two browsers as the same time should receive the message in two browser at the same time.
POM.xml
<!-- WebSocket libraries -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-stomp</artifactId>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-kahadb-store</artifactId>
<scope>runtime</scope>
</dependency>
WebSocketConfig
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/app")
.setUserDestinationPrefix("/user")
.enableStompBrokerRelay("/user");
}
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket").addInterceptors(new HttpHandshakeInterceptor()).withSockJS();
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new TopicSubscriptionInterceptor());
}
#Bean
public BrokerService broker() throws Exception {
BrokerService broker = new BrokerService();
broker.setSchedulePeriodForDestinationPurge(10000);
broker.addConnector("stomp://localhost:61613");
PolicyMap policyMap = new PolicyMap();
PolicyEntry policyEntry = new PolicyEntry();
policyEntry.setGcInactiveDestinations(true);
policyEntry.setInactiveTimeoutBeforeGC(30000);
policyEntry.setQueue(">");
List<PolicyEntry> entries = new ArrayList<PolicyEntry>();
entries.add(policyEntry);
policyMap.setPolicyEntries(entries);
broker.setDestinationPolicy(policyMap);
return broker;
}
UI
function connect() {
var socket = new SockJS('/websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/user/TestUser/reply', function (greeting) {
message = greeting;
showGreeting(greeting);
}, {'ack': 'client-individual'});
});
}
If you change your UI code to use topic, like this:
stompClient.subscribe('/topic/reply/'+ frame.headers['user-name'], function (greeting) { ...
and configure relay to /topic
config.setApplicationDestinationPrefixes("/app")
.enableStompBrokerRelay("/topic");
and send messages like :
simpMessageTemplate.convertAndSend("/topic/reply/" + principal, payload, headers);
than every connected client will recieve his message copy.
frame.headers['user-name'] some spring magic header with current logged in principal, returned on connect. don't know who adds it, maybe spring-security.
If you want to do it with anon user, you have to generate user-id on client side, store it somewhere ( localstorage or cookie ) and pass to server side to use as principal name.
EDIT:
If you want to restict connection to topic to only one user, you can do this like this (in TopicSubscriptionInterceptor):
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(message);
switch (sha.getCommand()) {
case SUBSCRIBE:
case SEND:
if(!sha.getDestination().equals("/topic/reply/" + sha.getUser().getName()) return null;
break;
}
...
return message;
}

Returning JSONP from Jersey

I am currently using Jersey to return JSON. How do I return JSONP instead? For example, my current RESTful method is:
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Order> getOrders() {
return OrderRepository.getOrders();
}
I would like to deploy on Tomcat (don't want to require the use of GlassFish). My only dependencies on Jersey are:
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.9.1</version>
</dependency>
What additional dependencies are needed? How will the code change?
Thanks.
Naresh
Take a look at the JSONP example (especially the ChangeList resource) in Jersey (you can download the whole project here).
What you basically need to do is something like this:
#GET
#Produces(MediaType.APPLICATION_JSON)
public JSONWithPadding getOrders() {
return new JSONWithPadding(new GenericEntity<List<Order>>(OrderRepository.getOrders()){});
}
It's important to modify the correct answer, as #s_t_e_v_e says.
It should be something like this:
#GET
#Produces({"application/javascript"})
public JSONWithPadding getOrdersJSonP(#QueryParam("callback") String callback) {
List<Order> orderList = OrderRepository.getOrders();
return new JSONWithPadding(new GenericEntity<List<Order>>(orderList){},
callback);
}

Resources