concurrency problems in interceptor tracking MDC - spring

I have the following interceptor that tracks request/response based on saving and restoring some vars stored in MDC context for every request.
public class LoggingInterceptor implements DeferredResultProcessingInterceptor {
private final HelloSeeYouLogger helloSeeYouLogger;
private static final String X_UOW = "X-UOW";
private static final String X_REQUEST_ID = "X-RequestId";
private Map<String, String> context;
public LoggingInterceptor(HelloSeeYouLogger helloSeeYouLogger) {
this.helloSeeYouLogger = helloSeeYouLogger;
}
#Override
public <T> void beforeConcurrentHandling(NativeWebRequest request, DeferredResult<T> deferredResult) {
addUowAndRequestIdToMDC(request.getHeader(X_UOW), request.getHeader(X_REQUEST_ID));
final String uri = getUri((HttpServletRequest) request.getNativeRequest());
helloSeeYouLogger.logHelloThere(uri);
context = MDC.getCopyOfContextMap();
}
#Override
public <T> void afterCompletion(NativeWebRequest request, DeferredResult<T> deferredResult) {
if (context != null) {
MDC.setContextMap(context);
}
final String uri = getUri((HttpServletRequest) request.getNativeRequest());
String body = getBody((HttpServletRequest) request.getNativeRequest());
if (!StringUtils.isEmpty(body)) {
body = replaceMoreThanOneSpacesWithOneSpace(hideCreditCardNumber(body));
}
helloSeeYouLogger.logSeeYou(uri, body);
clearUowAndRequestIdFromMDC();
}
public static void addUowAndRequestIdToMDC(final String uow, final String requestId) {
//This NewRelic stuff shouldn't be here as it is used for distributed tracing and not logging.
// However it helps to levarage requests tracking from a metric service such as new relic to a log aggregation service
//such as ELK.
NewRelic.addCustomParameter(UOW, uow);
NewRelic.addCustomParameter(REQUEST_ID, requestId);
MDC.put(UOW, uow);
MDC.put(REQUEST_ID, requestId);
}
public static void clearUowAndRequestIdFromMDC() {
if (MDC.get(UOW) != null) {
MDC.remove(UOW);
}
if (MDC.get(REQUEST_ID) != null) {
MDC.remove(REQUEST_ID);
}
}
I think i mn going to have concurrency problems as context is an instance variable and when running multiple concurrent thread saving and restoring MDC context will result in wrong results. Also using synchronize keyword will add performance problems.
I was wondering if there is a better approach to track MDC context when a spring controller returns a DeferredResult.
Thanks

You can use try using HandlerInterceptorAdapter instead
Check: https://www.logicbig.com/how-to/code-snippets/jcode-spring-mvc-deferredresultprocessinginterceptor.html

Related

Dynamic API client with dynamic custom header (Microprofile, Quarkus)

I want to create a custom Client API (on a non-Java RS environment). The API needs to have a custom base URL and a custom Header.
I achieve the custom base URL with the following and it works like a charm
RestClientBuilder.newBuilder()
.baseUri({{myAPUURI}})
.build(myAPI.class);
However, I could not find any solution to allow custom headers tightly coupled with the generated API. The only working solution I can do is to have a static variable in implementing the ClientHeadersFactory.
public class ApiHeader implements ClientHeadersFactory {
public static String userToken;
#Override
public MultivaluedMap<String, String> update(
MultivaluedMap<String, String> incomingHeaders,
MultivaluedMap<String, String> clientOutgoingHeaders
) {
MultivaluedMap<String, String> map = new MultivaluedHashMap<>();
map.add("authorization", userToken);
return map;
}
}
However, I have multiple instances of the rest client operating simultaneously, hence this solution would not be thread-safe. How could I reliably inject the token into the Rest client?
You can do something like:
RestClientBuilder.newBuilder()
.baseUri({{myAPUURI}})
.register(new CustomHeaderProvider("foo", "bar"))
.build(myAPI.class);
where CustomHeaderProvider looks like this:
public class CustomHeaderProvider implements ClientRequestFilter {
private final String name;
private final String value;
public CustomHeaderProvider(String name, String value) {
this.name = name;
this.value = value;
}
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
requestContext.getHeaders().add(name, value);
}
}

more than one 'primary' service instance suppliers found during load balancing (spring boot/cloud)

I'm currently updating from Spring boot 2.2.x to 2.6.x + legacy code, it's a big jump so there were multiple changes. I'm now running into a problem with load balancing through an api-gateway. I'll apologize in advance for the wall of code to come. I will put the point of failure at the bottom.
When I send in an API request, I get the following error:
more than one 'primary' bean found among candidates: [zookeeperDiscoveryClientServiceInstanceListSupplier, serviceInstanceListSupplier, retryAwareDiscoveryClientServiceInstanceListSupplier]
it seems that the zookeeperDiscovery and retryAware suppliers are loaded through the default serviceInsatnceListSupplier, which has #Primary over it. I thought would take precedence over the other ones. I assume I must be doing something wrong due changes in the newer version, here are the relevant code in question:
#Configuration
#LoadBalancerClients(defaultConfiguration = ClientConfiguration.class)
public class WebClientConfiguration {
#Bean
#Qualifier("microserviceWebClient")
#ConditionalOnMissingBean(name = "microserviceWebClient")
public WebClient microserviceWebClient(#Qualifier("microserviceWebClientBuilder") WebClient.Builder builder) {
return builder.build();
}
#Bean
#Qualifier("microserviceWebClientBuilder")
#ConditionalOnMissingBean(name = "microserviceWebClientBuilder")
#LoadBalanced
public WebClient.Builder microserviceWebClientBuilder() {
return WebClient.builder();
}
#Bean
#Primary
public ReactorLoadBalancerExchangeFilterFunction reactorLoadBalancerExchangeFilterFunction(
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
//the transformer is currently null, there wasn't a transformer before the upgrade
return new CustomExchangeFilterFunction(loadBalancerFactory, transformer);
}
}
There are also some Feign Client related configs here which I will omit, since it's not (or shouldn't be) playing a role in this problem:
public class ClientConfiguration {
/**
* The property key within the feign clients configuration context for the feign client name.
*/
public static final String FEIGN_CLIENT_NAME_PROPERTY = "feign.client.name";
public ClientConfiguration() {
}
//Creates a new BiPredicate for shouldClose. This will be used to determine if HTTP Connections should be automatically closed or not.
#Bean
#ConditionalOnMissingBean
public BiPredicate<Response, Type> shouldClose() {
return (Response response, Type type) -> {
if(type instanceof Class) {
Class<?> currentClass = (Class<?>) type;
return (null == AnnotationUtils.getAnnotation(currentClass, EnableResponseStream.class));
}
return true;
};
}
//Creates a Custom Decoder
#Bean
public Decoder createCustomDecoder(
ObjectFactory<HttpMessageConverters> converters, BiPredicate<Response, Type> shouldClose
) {
return new CustomDecoder(converters, shouldClose);
}
#Bean
#Qualifier("loadBalancerName")
public String loadBalancerName(PropertyResolver propertyResolver) {
String name = propertyResolver.getProperty(FEIGN_CLIENT_NAME_PROPERTY);
if(StringUtils.hasText(name)) {
// we are in a feign context
return name;
}
// we are in a LoadBalancerClientFactory context
name = propertyResolver.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
Assert.notNull(name, "Could not find a load balancer name within the configuration context!");
return name;
}
#Bean
public ReactorServiceInstanceLoadBalancer reactorServiceInstanceLoadBalancer(
BeanFactory beanFactory, #Qualifier("loadBalancerName") String loadBalancerName
) {
return new CustomRoundRobinLoadBalancer(
beanFactory.getBeanProvider(ServiceInstanceListSupplier.class),
loadBalancerName
);
}
#Bean
#Primary
public ServiceInstanceListSupplier serviceInstanceListSupplier(
#Qualifier(
"filter"
) Predicate<ServiceInstance> filter, DiscoveryClient discoveryClient, Environment environment, #Qualifier(
"loadBalancerName"
) String loadBalancerName
) {
// add service name to environment if necessary
if(environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME) == null) {
StandardEnvironment wrapped = new StandardEnvironment();
if(environment instanceof ConfigurableEnvironment) {
((ConfigurableEnvironment) environment).getPropertySources()
.forEach(s -> wrapped.getPropertySources().addLast(s));
}
Map<String, Object> additionalProperties = new HashMap<>();
additionalProperties.put(LoadBalancerClientFactory.PROPERTY_NAME, loadBalancerName);
wrapped.getPropertySources().addLast(new MapPropertySource(loadBalancerName, additionalProperties));
environment = wrapped;
}
return new FilteringInstanceListSupplier(filter, discoveryClient, environment);
}
}
There was a change in the ExchangeFilter constructor, but as far as I can tell, it accepts that empty transformer,I don't know if it's supposed to:
public class CustomExchangeFilterFunction extends ReactorLoadBalancerExchangeFilterFunction {
private static final ThreadLocal<ClientRequest> REQUEST_HOLDER = new ThreadLocal<>();
//I think it's wrong but I don't know what to do here
private static List<LoadBalancerClientRequestTransformer> transformersList;
private final Factory<ServiceInstance> loadBalancerFactory;
public CustomExchangeFilterFunction (Factory<ServiceInstance> loadBalancerFactory) {
this(loadBalancerFactory);
///according to docs, but I don't know where and if I need to use this
#Bean
public LoadBalancerClientRequestTransformer transformer() {
return new LoadBalancerClientRequestTransformer() {
#Override
public ClientRequest transformRequest(ClientRequest request, ServiceInstance instance) {
return ClientRequest.from(request)
.header(instance.getInstanceId())
.build();
}
};
}
public CustomExchangeFilterFunction (Factory<ServiceInstance> loadBalancerFactory, List<LoadBalancerClientRequestTransformer> transformersList) {
super(loadBalancerFactory, transformersList); //the changed constructor
this.loadBalancerFactory = loadBalancerFactory;;
}
#Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
// put the current request into the thread context - ugly, but couldn't find a better way to access the request within
// the choose method without reimplementing nearly everything
REQUEST_HOLDER.set(request);
try {
return super.filter(request, next);
} finally {
REQUEST_HOLDER.remove();
}
}
//used to be an override, but the function has changed
//code execution doesn't even get this far yet
protected Mono<Response<ServiceInstance>> choose(String serviceId) {
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerFactory.getInstance(serviceId);
if(loadBalancer == null) {
return Mono.just(new EmptyResponse());
}
ClientRequest request = REQUEST_HOLDER.get();
// this might be null, if the underlying implementation changed and this method is no longer executed in the same
// thread
// as the filter method
Assert.notNull(request, "request must not be null, underlying implementation seems to have changed");
return choose(loadBalancer, filter);
}
protected Mono<Response<ServiceInstance>> choose(
ReactiveLoadBalancer<ServiceInstance> loadBalancer,
Predicate<ServiceInstance> filter
) {
return Mono.from(loadBalancer.choose(new DefaultRequest<>(filter)));
}
}
There were pretty big changes in the CustomExchangeFilterFunction, but the current execution doesn't even get there. It fails here, in .getIfAvailable(...):
public class CustomRoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final int DEFAULT_SEED_POSITION = 1000;
private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private final String serviceId;
private final int seedPosition;
private final AtomicInteger position;
private final Map<String, AtomicInteger> positionsForVersions = new HashMap<>();
public CustomRoundRobinLoadBalancer (
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId
) {
this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(DEFAULT_SEED_POSITION));
}
public CustomRoundRobinLoadBalancer (
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId,
int seedPosition
) {
Assert.notNull(serviceInstanceListSupplierProvider, "serviceInstanceListSupplierProvider must not be null");
Assert.notNull(serviceId, "serviceId must not be null");
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.serviceId = serviceId;
this.seedPosition = seedPosition;
this.position = new AtomicInteger(seedPosition);
}
#Override
// we have no choice but to use the raw type Request here, because this method overrides another one with this signature
public Mono<Response<ServiceInstance>> choose(#SuppressWarnings("rawtypes") Request request) {
//fails here!
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get().next().map((List<ServiceInstance> instances) -> getInstanceResponse(instances, request));
}
}
Edit: after some deeper stacktracing, it seems that it does go into the CustomFilterFunction and invokes the constructor with super(loadBalancerFactory, transformer)
I found the problem or a workaround. I was using #LoadBalancerClients because I thought it would just set the same config for all clients that way (even if I technically only have one atm). I changed it to ##LoadBalancerClient and it suddenly worked. I don't quite understand why this made a difference but it did!

Get ResourceResolver From ResourceResolverFactory, but the ResourceResolver is not able to get Resource by given path

Given siutation like this, author click publish (activate) a page
Then I have following listener to handleEvent
public class ArticleContentActivationEventHandler implements EventHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ArticleContentActivationEventHandler.class);
private static final String ARTICLE_PAGE_TEMPLATE = "/conf/myproject/settings/wcm/templates/article-template";
#Reference
private ResourceResolverService resourceResolverService;
#Override
public void handleEvent(Event event) {
ResourceResolver resourceResolver = null;
try {
resourceResolver = resourceResolverService.getServiceResourceResolver();
String resourcePath = getResourcePath(event);
Resource resource = resourceResolver.getResource(resourcePath);
//the resource is null, the resource path is /content/myproject/us/en,
//resourceResolver is not able to resolve it somehow
for (Iterator<Resource> it = resourceResolver.getResource("/content/myproject/us/en").getChildren().iterator(); it.hasNext(); ) {
Resource r = it.next();
String s = r.getPath();
String t = r.getResourceType();
}
if (resource.isResourceType("cq:Page")) {
Resource jcr_content = resource.getChild("jcr:content");
ValueMap vm = jcr_content.getValueMap();
String template = null;
if (vm.containsKey("cq:template")) {
template = PropertiesUtil.toString(vm.get("cq:template"), "");
}
and below is the interface:
public interface ResourceResolverService {
ResourceResolver getServiceResourceResolver() throws LoginException;
void closeResourceResolver(ResourceResolver resourceResolver);
}
and the impl class:
#Component(service = ResourceResolverService.class, immediate = true)
public class ResourceResolverServiceImpl implements ResourceResolverService {
#Reference
private ResourceResolverFactory resourceResolverFactory;
Logger logger = LoggerFactory.getLogger(ResourceResolverServiceImpl.class);
#Activate
protected void activate() {
logger.info("*** Activating Service ResourceResolverServiceImpl");
}
#Override
public ResourceResolver getServiceResourceResolver() throws LoginException {
final Map<String, Object> param = Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) "getResourceResolver");
//the ResourceResolver returned here may got some issue?
return resourceResolverFactory.getServiceResourceResolver(param);
}
#Override
public void closeResourceResolver(ResourceResolver resourceResolver) {
resourceResolver.close();
}
}
I do have the osgi config setup by following tutorial http://www.aemcq5tutorials.com/tutorials/resourceresolver-from-resourceresolverfactory/
But in my actually even handler class, it never successfully resolved resource resourceResolver is not able to resolve /content/myproject/us/en , resourceResolver keep give me null value
Could anyone experienced this suggest me some code sample to resolve my issue? thanks
Check the User mapping configuration, the bundle id used in the configuration should match to your project core artifact id.
For example: in the configuration
bundleId = com.day.cq.wcm.cq-msm-core
Alternatively you can try below code instead
Map<String, Object> param = new HashMap<String, Object>();
param.put(ResourceResolverFactory.SUBSERVICE, "service-user");
ResourceResolver resolver = resourceResolverFactory.getServiceResourceResolver(param);

Cannot Write Data to ElasticSearch with AbstractReactiveElasticsearchConfiguration

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).

Vaadin: get reference of UI to change data

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! :)

Resources