Want to send notifications to specific client with websockets. Have a scheduled task for sending notifications, but cannot get Principal inside that task. Found this post, but as I know Spring scheduled methods must be parameter-free.
#Scheduled(fixedDelay=5000)
public void sendMessages(Principal principal)
messagingTemplate
.convertAndSendToUser(principal.getName(), "/queue/horray", "Horray, " + principal.getName() + "!");
}
Is this possible? How can I get websocket principal within scheduled method?
You can not get principal in a scheduled method , because the method call is not initiated by user.
You can follow this approach:
1) Create a websocket end point "/app/events"
2) Let all users to subscribe to that end point
3) Get all the userids you want to send notifications
4) Send notification to single user
simpMessagingTemplate.convertAndSendToUser("userId", "/app/events", "messageEntity");
userId: can be actual user id if authenticated or it can be websocket session id.
I wrote a workaround for this situation. At first I create a listener for websockets events. In case of subscription request I keep the userId from the request and keep in a ConcurrentHashMap. On the other hand when client disconnects or send unsubscribe request I remove his userId from that Map.
My Listener class:
#Service
public class MyEventListener {
#Autowired
private NotificationPublisher notificationPublisher;
#EventListener({SessionSubscribeEvent.class})
public void onWebSocketSubscribeEvent(SessionSubscribeEvent event) {
notificationPublisher.subscribedUsers.put(event.getUser().getName(), Calendar.getInstance().getTimeInMillis());
}
#EventListener({SessionUnsubscribeEvent.class})
public void onWebSocketUnsubscribeEvent(SessionUnsubscribeEvent event) {
notificationPublisher.subscribedUsers.remove(event.getUser().getName());
}
#EventListener({SessionDisconnectEvent.class})
public void onWebSocketDisconnectEvent(SessionDisconnectEvent event) {
notificationPublisher.subscribedUsers.remove(event.getUser().getName());
}
}
Notification publisher class where actual job is running:
public class NotificationPublisher {
public final Map<String, Long> subscribedUsers = new ConcurrentHashMap<>();
#Autowired
private SimpMessagingTemplate messagingTemplate;
#Autowired
private MyService myService;
#Value("${task.notifications.publisher.websocket_timeout_seconds}")
private int websocketSessionTimeout;
public void sendDataUpdates() {
SocketResponseCount count = null;
for(String key: subscribedUsers.keySet()) {
long subscribeTime = subscribedUsers.get(key);
if(Calendar.getInstance().getTimeInMillis() - subscribeTime > websocketSessionTimeout*1000) {
subscribedUsers.remove(key);
continue;
}
count = myService.getNotificationsCount(key);
this.messagingTemplate.convertAndSendToUser(key, "/queue/publish",count);
}
}
}
Maybe it will help someone
my solution: I get all user sessions
#Autowired
private SimpMessagingTemplate template;
#Autowired
private MyRepository myRepository;
#Autowired
private SessionRegistry sessionRegistry;
#Scheduled(fixedRate = 5000)
public void greeting() {
List<SessionInformation> activeSessions = new ArrayList<>();
for (Object principal : sessionRegistry.getAllPrincipals() )
{
activeSessions.addAll( sessionRegistry.getAllSessions( principal, false ) );
}
for (SessionInformation session : activeSessions )
{
Object principalObj = session.getPrincipal();
if ( principalObj instanceof CurrentUser)
{
CurrentUser user = (CurrentUser) principalObj;
this.template.convertAndSendToUser(user.getUsername().toUpperCase(),"/queue/reply2",myRepository.findAll());
}
}
}
Related
I am trying out to write data to my local Elasticsearch Docker Container (7.4.2), for simplicity I used the AbstractReactiveElasticsearchConfiguration given from Spring also Overriding the entityMapper function. The I constructed my repository extending the ReactiveElasticsearchRepository
Then in the end I used my autowired repository to saveAll() my collection of elements containing the data. However Elasticsearch doesn't write any data. Also i have a REST controller which is starting my whole process returning nothing basicly, DeferredResult>
The REST method coming from my ApiDelegateImpl
#Override
public DeferredResult<ResponseEntity<Void>> openUsageExporterStartPost() {
final DeferredResult<ResponseEntity<Void>> deferredResult = new DeferredResult<>();
ForkJoinPool.commonPool().execute(() -> {
try {
openUsageExporterAdapter.startExport();
deferredResult.setResult(ResponseEntity.accepted().build());
} catch (Exception e) {
deferredResult.setErrorResult(e);
}
}
);
return deferredResult;
}
My Elasticsearch Configuration
#Configuration
public class ElasticSearchConfig extends AbstractReactiveElasticsearchConfiguration {
#Value("${spring.data.elasticsearch.client.reactive.endpoints}")
private String elasticSearchEndpoint;
#Bean
#Override
public EntityMapper entityMapper() {
final ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(), new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions());
return entityMapper;
}
#Override
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo(elasticSearchEndpoint)
.build();
return ReactiveRestClients.create(clientConfiguration);
}
}
My Repository
public interface OpenUsageRepository extends ReactiveElasticsearchRepository<OpenUsage, Long> {
}
My DTO
#Data
#Document(indexName = "open_usages", type = "open_usages")
#TypeAlias("OpenUsage")
public class OpenUsage {
#Field(name = "id")
#Id
private Long id;
......
}
My Adapter Implementation
#Autowired
private final OpenUsageRepository openUsageRepository;
...transform entity into OpenUsage...
public void doSomething(final List<OpenUsage> openUsages){
openUsageRepository.saveAll(openUsages)
}
And finally my IT test
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#Testcontainers
#TestPropertySource(locations = {"classpath:application-it.properties"})
#ContextConfiguration(initializers = OpenUsageExporterApplicationIT.Initializer.class)
class OpenUsageExporterApplicationIT {
#LocalServerPort
private int port;
private final static String STARTCALL = "http://localhost:%s/open-usage-exporter/start/";
#Container
private static ElasticsearchContainer container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:6.8.4").withExposedPorts(9200);
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(final ConfigurableApplicationContext configurableApplicationContext) {
final List<String> pairs = new ArrayList<>();
pairs.add("spring.data.elasticsearch.client.reactive.endpoints=" + container.getContainerIpAddress() + ":" + container.getFirstMappedPort());
pairs.add("spring.elasticsearch.rest.uris=http://" + container.getContainerIpAddress() + ":" + container.getFirstMappedPort());
TestPropertyValues.of(pairs).applyTo(configurableApplicationContext);
}
}
#Test
void testExportToES() throws IOException, InterruptedException {
final List<OpenUsageEntity> openUsageEntities = dbPreparator.insertTestData();
assertTrue(openUsageEntities.size() > 0);
final String result = executeRestCall(STARTCALL);
// Awaitility here tells me nothing is in ElasticSearch :(
}
private String executeRestCall(final String urlTemplate) throws IOException {
final String url = String.format(urlTemplate, port);
final HttpUriRequest request = new HttpPost(url);
final HttpResponse response = HttpClientBuilder.create().build().execute(request);
// Get the result.
return EntityUtils.toString(response.getEntity());
}
}
public void doSomething(final List<OpenUsage> openUsages){
openUsageRepository.saveAll(openUsages)
}
This lacks a semicolon at the end, so it should not compile.
But I assume this is just a typo, and there is a semicolon in reality.
Anyway, saveAll() returns a Flux. This Flux is just a recipe for saving your data, and it is not 'executed' until subscribe() is called by someone (or something like blockLast()). You just throw that Flux away, so the saving never gets executed.
How to fix this? One option is to add .blockLast() call:
openUsageRepository.saveAll(openUsages).blockLast();
But this will save the data in a blocking way effectively defeating the reactivity.
Another option is, if the code you are calling saveAll() from supports reactivity is just to return the Flux returned by saveAll(), but, as your doSomething() has void return type, this is doubtful.
It is not seen how your startExport() connects to doSomething() anyway. But it looks like your 'calling code' does not use any notion of reactivity, so a real solution would be to either rewrite the calling code to use reactivity (obtain a Publisher and subscribe() on it, then wait till the data arrives), or revert to using blocking API (ElasticsearchRepository instead of ReactiveElasticsearchRepository).
I have this code:
#Saga
#Slf4j
public class ActionsSaga2 {
#Autowired
transient CommandGateway commandGateway;
#Autowired
transient ActionService actionService;
String id;
ApplicationState state;
#StartSaga
#SagaEventHandler(associationProperty = "applicationId")
public void on(ApplicationCreatedEvent event) {
id = event.getApplicationId();
state = event.getState();
commandGateway.send(ScheduleActionCommand.builder()
.applicationId(event.getApplicationId())
.actionId(id)
.targetState(event.getState())
.build());
}
#EndSaga
#SagaEventHandler(associationProperty = "applicationId")
public void on(ActionDoneEvent event) {
assert id != null;
}
}
First #SagaEventHandler on(ApplicationCreatedEvent event) sets the private fields id and state.
But in the second #SagaEventHandler on(ActionDoneEvent event) both properties are null.
I'm pretty sure that the calls are routed to the same saga (the only record in saga_entry that is deleted after the second method call)
Could you help me where is the problem?
I have no special configuration for saga, using AxonServer and Spring boot
I found the solution, the problem was in the association property.
correct event handler is
#EndSaga
#SagaEventHandler(associationProperty = "applicationId", keyName = "id")
public void on(ActionDoneEvent event) {
assert id != null;
}
We are using spring-cloud-stream to manage messages between our applications.
We have custom bindings:
public interface InboundChannels {
String TASKS = "domainTasksInboundChannel";
String EVENTS = "eventsInboundChannel";
#Input(TASKS)
SubscribableChannel tasks();
#Input(EVENTS)
SubscribableChannel events();
}
public interface OutboundChannels {
String TASKS = "domainTasksOutboundChannel";
String EVENTS = "eventsOutboundChannel";
#Output(TASKS)
MessageChannel tasks();
#Output(EVENTS)
MessageChannel events();
}
There are processors that consumes tasks and generate events:
#EnableBinding({InboundChannels.class, OutboundChannels.class})
public class TasksProcessor {
public TasksProcessor(
UserService userService,
#Qualifier(OutboundChannels.EVENTS) MessageChannel eventsChannel
) {
this.userService = userService;
this.eventsChannel = eventsChannel;
}
#StreamListener(value = TASKS, condition = "headers['" + TYPE + "']=='" + CREATE_USER + "'")
public void createUser(Message<User> message) {
final User user = message.getPayload();
userService.save(user)
.subscribe(created -> {
Message<User> successMessage = fromMessage(message, Events.USER_CREATED, created).build();
eventsChannel.send(successMessage);
});
}
}
Now we wanted to test it using spring-cloud-stream-test-support and its amazing features:
#DirtiesContext
#SpringBootTest
#RunWith(SpringRunner.class)
public class TasksProcessorTest {
private User user;
#Autowired
private InboundChannels inboundChannels;
#Autowired
private OutboundChannels outboundChannels;
#Autowired
private MessageCollector collector;
#Before
public void setup() {
user = new User(BigInteger.ONE, "test#teste.com");
}
#Test
public void createUserTest() {
final Message<User> msg = create(CREATE_USER, user).build();
outboundChannels.tasks().send(msg);
final Message<?> incomingEvent = collector.forChannel(inboundChannels.events()).poll();
final String type = (String) incomingEvent.getHeaders().get(TYPE);
assertThat(type).isEqualToIgnoringCase(USER_CREATED);
}
}
application.properties
##
# Spring AMQP configuration
##
spring.rabbitmq.host=rabbitmq
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
# Events channels
spring.cloud.stream.bindings.eventsOutboundChannel.destination=events
spring.cloud.stream.bindings.eventsInboundChannel.destination=events
spring.cloud.stream.bindings.domainTasksOutboundChannel.destination=domainTasks
spring.cloud.stream.bindings.domainTasksInboundChannel.destination=domainTasks
spring.cloud.stream.bindings.userTasksInboundChannel.group=domainServiceInstances
spring.cloud.stream.bindings.eventsInboundChannel.group=domainServiceInstances
But then we get this error:
java.lang.IllegalArgumentException: Channel [eventsInboundChannel] was not bound by class org.springframework.cloud.stream.test.binder.TestSupportBinder
What are we doing wrong?
In the .subscribe() you do eventsChannel.send(successMessage);, where that eventsChannel is from the OutboundChannels.EVENTS, but what you try to do in the test-case is like inboundChannels.events(). And it doesn't look like you really bind this channel anywhere.
I'm sure if you would use outboundChannels.events() instead, that would work for you.
I want to change data inside a Vaadin UI. The change is invoked by a a rest call. There, i somehow need a reference to the UI class to call its method´, e.g. changeValue(string value).
I'm using vaadin-spring-boot-starter 1.0.0
Is that somehow possible?
EDIT: Another question now:
I was trying to do that Server Push, mentioned by #Eric, inside of a View, so that the view will get updated on a Broadcast message. However, this is not working (no exceptions, nothing to debug, just no updates in the view). This is what i do in my View:
#UIScope
#SpringView(name = LoadWebsiteView.VIEW_NAME)
#Push
public class LoadWebsiteView extends VerticalLayout implements View, Broadcaster.BroadcastListener {
...
#Autowired
public LoadWebsiteView(ScraperMainUI scraperMainUi) {
this.scraperMainUi = scraperMainUi;
Broadcaster.register(this);
initControlPane();
}
#Override
public void receiveBroadcast(String message) {
scraperMainUi.access(new Runnable() {
#Override
public void run() {
urlTxtField.setValue(message);
}
});
}
and here is the simple stuff i do in my restcontroller:
Broadcaster.broadcast(text);
What you are looking for is Vaadin's Push feature and a way to send a message to a list of registered "clients" (in this case, the Vaadin UIs who need to known about the changes).
You can read about Vaadin Push here: Enabling Server Push and also in the article Advanced Push
The Vaadin push function allows your server to force updates to the client instead of waiting on the browser to request again.
The message component simply acts as a way to tell subscribed UIs that there is an update they need to action.
This said, I have a project that does about the same as multiple users are actioning items and there are Spring scheduled tasks that also can effect changes the user needs to know about.
Note, the below examples are based on the examples available in Enabling Server Push article.
Broadcaster.java - Acts as the mechanism that registers instances to receive broadcasts and provides a facility to send broadcasts. In the below example, I have I have a class that represents a message (BroadcastMessage) but you could simplify it of course.
public class Broadcaster implements Serializable {
private static final long serialVersionUID = 3540459607283346649L;
static ExecutorService executorService = Executors.newSingleThreadExecutor();
private static LinkedList<BroadcastListener> listeners = new LinkedList<BroadcastListener>();
public interface BroadcastListener {
void receiveBroadcast(BroadcastMessage message);
}
public static synchronized void register(BroadcastListener listener) {
listeners.add(listener);
}
public static synchronized void unregister(BroadcastListener listener) {
listeners.remove(listener);
}
public static synchronized void broadcast(final BroadcastMessage message) {
for (final BroadcastListener listener: listeners)
executorService.execute(new Runnable() {
#Override
public void run() {
listener.receiveBroadcast(message);
}
});
}
}
Here is the class I defined for my BroadcastMessage. The idea is to have a way to denote what kind of message I have and also some payload in the form of a Map
public class BroadcastMessage implements Serializable {
private static final long serialVersionUID = 5637577096751222106L;
public BroadcastMessageType messageType;
public Map<String, String> params;
public BroadcastMessage() {
}
public BroadcastMessage(BroadcastMessageType messageType) {
this.messageType = messageType;
this.params = new HashMap<String, String>();
}
public BroadcastMessage(BroadcastMessageType messageType, Map<String, String> params) {
this.messageType = messageType;
this.params = params;
}
public BroadcastMessageType getMessageType() {
return messageType;
}
public void setMessageType(BroadcastMessageType messageType) {
this.messageType = messageType;
}
public Map<String, String> getParams() {
return params;
}
public void setParams(Map<String, String> params) {
this.params = params;
}
}
This is an example Vaadin UI that wants to listen for Broadcasts. Note the #Push annotation. Without this, the client will only refresh when the browser decides to. #Push makes it immediate**
#SpringComponent
#UIScope
#Push
#SpringView(name=TaskListComponent.NAME)
public class TaskListComponent extends MyCustomComponent implements Broadcaster.BroadcastListener, View {
/** PRUNED DOWN, TO DEMONSTRATE THE KEY CODE **/
// Register this window when we enter it
#Override
public void enter(ViewChangeEvent event) {
Broadcaster.register(this);
}
// Must also unregister when the UI expires
#Override
public void detach() {
Broadcaster.unregister(this);
super.detach();
}
// Receive a broadcast
#Override
public void receiveBroadcast(BroadcastMessage message) {
getUI().access(new Runnable() {
#Override
public void run() {
// DO WHATEVER YOU NEED TO DO HERE.
// I CALLED INITIALIZE BUT IT COULD BE
// JUST YOU FIELD UPDATE
if ( message.getMessageType().equals(BroadcastMessageType.REFRESH_TASK_LIST) )
initialize();
}
});
}
}
To send a message from your rest interface:
Broadcaster.broadcast(
new BroadcastMessage(
BroadcastMessageType.AUTO_REFRESH_LIST
)
);
Hope this helps! :)
I am using Spring(boot) on my project and I access a JMS Queue (ActiveMQ) using :
#JmsListener(destination = "mydestinationQueue")
public void processMessage(String content) {
//do something
}
And it works perfectly but I need to be able to stop/pause/start this bean programatically (a REST call or something like that)
When I stop or pause this bean I want to be sure to have fully processed the current message.
any idea about that ?
thanks
Here is the solution I've found
#RestController
#RequestMapping("/jms")
public class JmsController {
#Autowired
ApplicationContext context;
#RequestMapping(value="/halt", method= RequestMethod.GET)
public #ResponseBody
String haltJmsListener() {
JmsListenerEndpointRegistry customRegistry =
context.getBean("jmsRegistry", JmsListenerEndpointRegistry.class);
customRegistry.stop();
return "Jms Listener Stopped";
}
#RequestMapping(value="/restart", method=RequestMethod.GET)
public #ResponseBody
String reStartJmsListener() {
JmsListenerEndpointRegistry customRegistry =
context.getBean("jmsRegistry", JmsListenerEndpointRegistry.class);
customRegistry.start();
return "Jms Listener restarted";
}
#RequestMapping(value="/stopApp", method=RequestMethod.GET)
public #ResponseBody
String stopApp() {
String[] args={};
SpringApplication.run(FacturationApplicationFrontDaemon.class, args).close();
return "stopped";
}
}
There's a bean of type JmsListenerEndpointRegistry (name org.springframework.jms.config.internalJmsListenerEndpointRegistry).
You can access the JMS listener containers from the registry (all or by name) and call stop() on the one(s) you want; the container will stop after any in-process messages complete their processing.
private void stopJMSListener() {
if(null == customRegistry){
customRegistry = context.getBean(JmsListenerEndpointRegistry.class);
}
customRegistry.stop();
}
private void startJMSListener() {
if(null == customRegistry){
JmsListenerEndpointRegistry customRegistry = context.getBean(JmsListenerEndpointRegistry.class);
}
customRegistry.start();
}