Atmosphere + Jersey: How do I have multiple broadcasters? - java

I have a working Jersey/Atmosphere/Guice application which has two Atmosphere Resources. The first is pretty much a clone of the example chat application:
#Path("/chat")
#AtmosphereService(broadcaster = JerseyBroadcaster.class, path = "/chat")
public class ChatResource {
#Suspend(contentType = "application/json")
#GET
public String suspend() {
return "";
}
#Broadcast(writeEntity = false)
#POST
#Produces("application/json")
public Response broadcast(Message message) {
return new Response(message.author, message.message);
}
}
The second is a test notification resource which will be sent server-side events:
#Path("/notifications")
#AtmosphereService(broadcaster = JerseyBroadcaster.class, path = "/notifications")
public class NotificationsResource {
#Suspend(contentType = "application/json")
#GET
public String suspend() {
return "";
}
}
Everything is wired up correctly and works fine. However in order for me to send a server side event I issue:
MetaBroadcaster.getDefault().broadcastTo("/*", new Response(...));
Clearly, this will send the broadcast message to both resources. What I want to do is send the server side events only to the notifications resource:
MetaBroadcaster.getDefault().broadcastTo("/notifications", new NotificationResponse(...));
However, that doesn't work. I always receive the following error:
org.atmosphere.cpr.MetaBroadcaster - No Broadcaster matches /notifications.
That's because there is only one broadcaster registered; the JerseyBroadcaster on /*.
The question is: how do I make it so that these two resources have different broadcasters with different IDs/Names?

In the resource, suspend using the channel you want (the 'true' parameter to lookup() forces the channel to be created if it doesn't exist):
#Suspend( contentType = MediaType.APPLICATION_JSON, period = MAX_SUSPEND_MSEC )
#GET
public Broadcastable suspend( #Context final BroadcasterFactory factory )
{
return new Broadcastable( factory.lookup( MY_CHANNEL, true ) );
}
In the other code, which can be pretty much anywhere, broadcast to that channel:
Broadcaster broadcaster = BroadcasterFactory.getDefault().lookup( MY_CHANNEL );
if( broadcaster != null ) {
broadcaster.broadcast( message );
}
If you're going to be broadcasting from a resource method, you can annotate it instead (as shown in ChatResource's broadcast() method).

Just inject Broadcaster using the #PathParam annotation:
private
#PathParam("topic")
Broadcaster topic;
You can also use the #Context annotation. Hope that help.
-- Jeanfrancois

Related

Unable to receive the UDP response from device

I'm trying to send the UDP request and receive the response. Spring Integration has the appropriate instruments for such kind of task: UnicastSendingMessageHandler and UnicastReceivingChannelAdapter. I configured it in the following way
#Bean
public MessageChannel requestChannel() {
return new DirectChannel();
}
#Bean
#ServiceActivator(inputChannel = "requestChannel")
public UnicastSendingMessageHandler unicastSendingMessageHandler() {
UnicastSendingMessageHandler unicastSendingMessageHandler = new UnicastSendingMessageHandler("239.255.255.250", 1982);
return unicastSendingMessageHandler;
}
#Bean
public UnicastReceivingChannelAdapter unicastReceivingChannelAdapter() {
UnicastReceivingChannelAdapter unicastReceivingChannelAdapter = new UnicastReceivingChannelAdapter(8080);
unicastReceivingChannelAdapter.setOutputChannelName("nullChannel");
return unicastReceivingChannelAdapter;
}
How I send a message (I'm using sendDiscoveryMessage() wherever I want):
#Service
public class DiscoveryService {
private static final String DISCOVERY_MESSAGE = "M-SEARCH * HTTP/1.1\r\n"
+ "HOST: 239.255.255.250:1982\r\n"
+ "MAN: \"ssdp:discover\"\r\n"
+ "ST: wifi_bulb";
private final MessageChannel requestChannel;
public DiscoveryService(final MessageChannel requestChannel) {
this.requestChannel = requestChannel;
}
public void sendDiscoveryMessage() {
requestChannel.send(new GenericMessage<>(DISCOVERY_MESSAGE));
}
}
At this point, I can check the packets via WireShark and ensure that Datagram was sent and the appropriate response was sent too.
The only question is how to receive this response. As far as I understand reading the documentation, I need the method annotated with #ServiceActivator. But I don't understand where (which channel) I should receive the response (in order to correctly specify #ServiceActivator(inputChannel="")). Also, I'm not sure about #ServiceActivator(inputChannel = "requestChannel") I put for UnicastSendingMessageHandler bean.
I tried to create the following method(assuming that the response will come to the same channel):
#ServiceActivator(inputChannel = "requestChannel")
public void receiveResponse(Message<String> response) {
System.out.println(response);
}
but it actually intercepts my own request message (seems logical to me, because I send the request to requestChannel).
So I don't understand how many channels I need (maybe I need 1 for request and 1 for response) and how to create #ServiceActivator to catch the response.
unicastReceivingChannelAdapter.setOutputChannelName("nullChannel");
You are sending the result to nullChannel which is like /dev/null on Unix; you are discarding it.
Use #ServiceActivator(inputChannel = "replyChannel") and
unicastReceivingChannelAdapter.setOutputChannelName("replyChannel");

How to send response before actions in spring mvc

Say that my spring controller function receives a large amount of data.
I want to return 200 OK, given that the data is structured right, and after that I want to perform the processing, which might take a while.
To my understanding the only way to send response is by return command. But I don't want to end the function on response send.
Are there other ways to send response to client at the middle of the function?
Creating a new thread run is obvious but other languages (JS) let you handle it more elegantly.
#RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method = RequestMethod.POST)
public ResponseEntity<String> doSomething(#RequestBody List<Message> messages) {
HttpStatus code = (messages!=null && !messages.isEmpty()) ? HttpStatus.OK
: HttpStatus.NOT_FOUND;
return new ResponseEntity<String>(res, code);
// how do I add code here??
}
You can of course do processing after sending the response. The more general way would be to use the afterCompletion method of a HandlerInterceptor. By construction, it will be executed after the response have been sent to client, but it forces you to split you logic in 2 components the before part in controller, and the after part in the interceptor.
The alternative way is to forget Spring MVC machinery and manually commit the response in the controller:
#RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method = RequestMethod.POST)
public void doSomething(#RequestBody List<Message> messages, HttpServletResponse response) {
int code = (messages!=null && !messages.isEmpty()) ? HttpServletResponse.SC_OK
: HttpServletResponse.SC_NOT_FOUND;
if (code != HttpServletResponse.SC_OK) {
response.sendError(code, res);
return;
}
java.io.PrintWriter wr = response.getWriter();
response.setStatus(code);
wr.print(res);
wr.flush();
wr.close();
// Now it it time to do the long processing
...
}
Note the void return code to notify Spring that the response have been committed in the controller.
As a side advantage, the processing still occurs in the same thread, so you have full access to session scoped attributes or any other thread local variables used by Spring MVC or Spring Security...
You can use #Async
#RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method =
RequestMethod.POST)
public ResponseEntity<String> doSomething(#RequestBody List<Message>
messages) {
do();
HttpStatus code = (messages!=null && !messages.isEmpty()) ? HttpStatus.OK
: HttpStatus.NOT_FOUND;
return new ResponseEntity<String>(res, code);
}
#Async
void do(){
//your code
}
this work in java 8
I guess you mau use the async mechanism of spring
Async methods have been introduced in servlet 3.0 and Spring offers some support to them
Basically... you make a request; the request is handled by the server and then, in background, a new thread manages the requesta data
Here a useful link (at least i hope :) ) http://spring.io/blog/2012/05/10/spring-mvc-3-2-preview-making-a-controller-method-asynchronous/
You should use the HandlerInterceptor. But the code get a little bit more complex than expected. So, here's a code suggestion to make it simpler by putting the whole solution in a single class:
#RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method = RequestMethod.POST)
public ResponseEntity<String> doSomething(#RequestBody List<Message> messages) {
HttpStatus code = (messages!=null && !messages.isEmpty()) ? HttpStatus.OK
: HttpStatus.NOT_FOUND;
result.set(res); // Save the object to be used after response
return new ResponseEntity<String>(res, code);
}
private static final ThreadLocal<String> result = new ThreadLocal<String>();
#Bean
public HandlerInterceptor interceptor() {
return new HandlerInterceptor() {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// Get the saved object and clean for the next request
String res = result.get();
result.set(null);
// TODO Your code to be executed after response.
}
};
}

post service is not calling throwing 400 (Bad Request)

Hi friends I am using Angularjs and rest-servies but when I am calling rest services from service.js file something is goning wrong and it is throwing 400(bad request )
main.js
garantiesService.getTarifs($scope.recap.ageDirigeant,$scope.selectedCompany.zipcode)
.success(function(){
console.log('in success');
})
service.js
healthApp.factory('garantiesService', ['$http', function($http) {
var service = {
getTarifs: function(age,zipcode)
{
console.log("age : "+age);
console.log("zipcode : "+zipcode);
var directorHealthInsuranceInfo = {};
directorHealthInsuranceInfo.age=age;
directorHealthInsuranceInfo.department=zipcode;
return $http.post('rest-service/quotes/health /director',directorHealthInsuranceInfo);
}
};
return service;
HealthInsuranceController.java
#Controller
public class HealthInsuranceQuoteResource {
#RequestMapping("quotes/health/director")
#ResponseBody
public String quoteDirector(#RequestBody DirectorHealthInsuranceInfo info) {
System.out.println("------HealthInsuranceQuoteResult------");
return "hi";
}
DirectorHealthInsuranceInfo.java
#Value
public class DirectorHealthInsuranceInfo {
private String department;
private int age;
}
when I am sending the request it is throwing Bad Request 400 error.
I see that there is a space in the url you supplied to the http.post method.
"rest-service/quotes/health /director"
I don't know if that is causing it.
But I also see that you POST your request to the service. Are you sure that your endpoint has been set up for POST requests?
I would recommend creating a basic endpoint that you call with a GET request, and no parameters. Just to root out the problem.

POST JSON from Javascript client to Jersey resource in Atmosphere framework

I have been Googling and trying to get this to work for hours...The problem is the server is not receiving data as JSON but as text. This is the POJO
package my.package;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class TestConfig {
private String firmID;
private String traderID;
private String userID;
public TestConfig() {};
...
}
A Javascript client which contains:
function callbackForTest(response) {
console.log("Call to callbackForTest");
if (response.state == "opening" && response.status == 200) {
//push request data
if (connectedEndpoint[0] == null) {
console.log("[DEBUG] Connected endpoint for " + value + "is null!");
//disable button
$(value).attr('disabled','');
$.atmosphere.unsubscribe();
return false;
}
// push ( POST )
connectedEndpoint[0].push(JSON.stringify(
{
operation : "RUN",
firmID : $('#firmID').val(),
userID : $('#userID').val(),
traderID : $('#traderID').val(),
protocol : $('#protocol').val(),
group1 :
}
));
}
}
function subscribeUrl(jobName, call, transport) {
var location = subscribePath + jobName.id;
return subscribeAtmosphere(location, call, transport);
}
function globalCallback(response) {
if (response.state != "messageReceived") {
return;
}
}
function subscribeAtmosphere(location, call, transport) {
var rq = $.atmosphere.subscribe(location, globalCallback, $.atmosphere.request = {
logLevel : 'debug',
transport : transport,
enableProtocol: true,
callback : call,
contentType : 'application/json'
});
return rq;
}
function sendMessage(connectedEndpoint, jobName) {
var phrase = $('#msg-' + jobName).val();
connectedEndpoint.push({data: "message=" + phrase});
}
// Run Test handlers
$("input[name='runButtons']").each(function(index, value){
$(value).click(function(){
//disable button
$(value).attr('disabled','disabled');
// connect (GET)
connectedEndpoint[index] = subscribeUrl(value, callbackForTest, transport);
});
});
I have included the libs shown in this screenshot:
LIBS
And this is my web.xml (part of it)
com.sun.jersey.api.json.POJOMappingFeature
true
The Jersey resource
#Path("/subscribe/{topic}")
#Produces({MediaType.APPLICATION_JSON, "text/html;charset=ISO-8859-1", MediaType.TEXT_PLAIN})
public class Subscriber {
private static final Logger LOG = Logger.getLogger(Subscriber.class);
#PathParam("topic")
private Broadcaster topic;
#GET
public SuspendResponse<String> subscribe() {
LOG.debug("GET - OnSubscribe to topic");
SuspendResponse<String> sr = new SuspendResponse.SuspendResponseBuilder<String>().broadcaster(topic).outputComments(true)
.addListener(new EventsLogger()).build();
return sr;
}
#POST
#Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN, MediaType.TEXT_HTML})
#Broadcast
public Broadcastable publish( TestConfig t) {
LOG.debug("POST");
String s = t.getFirmID();
return new Broadcastable(s, "", topic);
}
I can subscribe OK. When I try to push to the server, I get this exception:
A message body reader for Java class com.mx.sailcertifier.TestConfig, and Java type class com.mx.sailcertifier.TestConfig, and MIME media type text/plain was not found.
Why is it sending plain text if I set the content type to application/json? What is the correct way to get the Jersey resource to read the JSON?
I finally got this working with two changes:
After looking at the sample here, I added this init-param to the AtmosphereServlet in the web.xml to resolve the text/plain problem in Tomcat:
<init-param>
<param-name>org.atmosphere.websocket.messageContentType</param-name>
<param-value>application/json</param-value>
</init-param>
I didn't see this anywhere documented in the Atmosphere docs. It would have saved a lot of time had it been, but documentation-wise the API is unfortunately disorganized and lacking.
Also, I needed to use the jersey-bundle jar make sure that everything Jersey related is included, including as the jersey-json.jar. After that, it worked! Hope this helps someone else who may have been stuck with the same or similar problem.

how to commit or flush the rest response in the middle of process

I'm new to both java and jersey. Now I want to use the jersey to realize a REST services with extra processing after sending the response (specifically, sleep a fix amount of seconds and then fire a different REST request in the same servlet context, so it's unlike a REST proxy). I had googled for a while but all seems take it for granted that implicitly flushing the response at the end of method. Here are the current codes with JAXB enabled I'm struggling to work on.
#Path("/chat")
public class LoadSimulator {
#Context private UriInfo uriInfo;
#Path("/outbound/{senderAddress}/requests")
#POST
#Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response createOutboundSMSMessage(OutboundSMSMessageRequest inSmsReq, #PathParam("senderAddress") String senderAddress) throws JAXBException {
String requestId = UUID.randomUUID().toString();
URI uri = uriInfo.getAbsolutePathBuilder().path(requestId).build();
ObjectFactory factory = new ObjectFactory();
ResourceReference resourceReference = new ResourceReference();
resourceReference.setResourceURL(uri.toString());
JAXBElement<ResourceReference> inSmsResponse = factory.createResourceReference(resourceReference);
return Response.created(uri).entity(inSmsResponse).build();
//// want to flush or commit the response explicitly like:
// out.flush();
// out.close();
//// Then sleep for a few second and fire a new REST request
// sleep(5);
// ....
// ClientConfig config = new DefaultClientConfig();
// String response = r.path("translate").queryParams(params).get(String.class);
}
}
If you could do what you're trying to do, you would exhaust the resources on your server because every request would take X seconds and you have a finite amount of threads available before the box cries uncle.
Without commenting on why you'd want to do this; If you used the #Singleton annotation for your LoadSimulator you could set up a thread that listens on a (concurrent) queue in #PostConstruct public void init() - that gets called when your servlet starts up.
#Singleton
#Path("/chat")
public class LoadSimulator {
private Thread restCaller;
private ConcurrentLinkedQueue<MyInfo> queue = new ConcurrentLinkedQueue<MyInfo>();
...
#PostConstruct public void init()
{
restCaller = new Thread(new MyRunnable(queue));
restCaller.start();
}
...
Then in your REST call, you'd put whatever information is needed to make the second REST call on that queue, and have the aforementioned thread pulling it off and making queries.

Categories

Resources