Instead using the EventStream instead ArchivedEventStream, when I run command alert(notification) message go to all connected socket except the original sender, how I also can send to original sender.
Here is my model and controller, using WebSocket
EventModel
public class EventModel {
// ~~~~~~~~~ Let's chat!
final EventStream<EventModel.Event> events = new EventStream<EventModel.Event>(100);
/**
* Get the event
*/
public EventStream<EventModel.Event> getEventStream() {
return events;
}
/**
* A user say something on the room
*/
public void _alert(Notification notification){
if(notification == null) return;
events.publish(new EventModel.NotificationEvent(notification));
}
// ~~~~~~~~~ Events
public static abstract class Event {
final public String type;
final public Long timestamp;
public boolean sended;
public Event(String type) {
this.sended = false;
this.type = type;
this.timestamp = System.currentTimeMillis();
}
}
public static class NotificationEvent extends EventModel.Event{
public final Notification notification;
public NotificationEvent(Notification notification) {
super("notification");
this.notification = notification;
}
public User getReceiver(){
return notification.receiver;
}
}
// EventModel factory
static EventModel instance = null;
public static EventModel get() {
if(instance == null) {
instance = new EventModel();
}
return instance;
}
//Alert notification
public static void alert(Notification notification){
get()._alert(notification);
}
}
And here is controller
public class MyWebSocket extends RootController {
public static class WebSocket extends WebSocketController {
public static void echo(Long userId) {
//Security
User user = User.findById(userId);
EventModel eventCentre = EventModel.get();
// Socket connected, join the chat room
EventStream<EventModel.Event> eventStrean = eventCentre.getEventStream();
// Loop while the socket is open
while(inbound.isOpen()) {
// Wait for an event (either something coming on the inbound socket channel, or ChatRoom messages)
Either<WebSocketEvent,EventModel.Event> e = await(Promise.waitEither(
inbound.nextEvent(),
eventStrean.nextEvent()
));
//Handle if get any notification
for(EventModel.NotificationEvent event: ClassOf(EventModel.NotificationEvent.class).match(e._2)) {
if(!event.getReceiver().equals(user)) continue;
outbound.send(event.notification.toJson());
}
// Case: The socket has been closed
for(WebSocketClose closed: SocketClosed.match(e._1)) {
disconnect();
}
}
}
}
}
To send to the original sender as well remove or comment out this line:
if(!event.getReceiver().equals(user)) continue;
one small issue to be aware of
https://github.com/playframework/play1/pull/709
Related
I have a scenario, which process armeria request, and dispatch some event to guava's EventBus. the problem is I loss the context while process the event in the EventBus handler.
I want to know is there any way to let the event processor access ServiceRequestContext.
class EventListener {
#Subscribe
public void process(SomeCustomizedClass event) {
final ServiceRequestContext context = ServiceRequestContext.currentOrNull();
log.info("process ServiceRequestContext context={}", context);
}
}
register the event handler.
EventBus eventBus = new AsyncEventBus(ThreadPoolTaskExecutor());
eventBus.register(new EventListener());
here is my Armeria service
#Slf4j
public class NameAuthRestApi {
final NameAuthService nameAuthService;
#Post("/auth")
#ProducesJson
public Mono<RealNameAuthResp> auth(RealNameAuthReq req) {
return nameAuthService.auth(NameAuthConverter.CONVERTER.toDto(req))
.handle((result, sink) -> {
if (result.isSuccess()) {
// I post an event here, but the event process couldn't access the ServiceRequestContext
// that's would be the problem.
eventBus.post(new SomeCustomizedClass(result));
final RealNameAuthResp realNameAuthResp = new RealNameAuthResp();
realNameAuthResp.setTradeNo(result.getTradeNo());
realNameAuthResp.setSuccess(true);
sink.next(realNameAuthResp);
sink.complete();
} else {
sink.error(new SystemException(ErrorCode.API_ERROR, result.errors()));
}
});
}
}
You need to do:
public Mono<RealNameAuthResp> auth(ServiceRequestContxt ctx, RealNameAuthReq req) {
// Executed by an EventLoop 1.
// This thread has the ctx in its thread local.
return nameAuthService.auth(NameAuthConverter.CONVERTER.toDto(req))
.handle((result, sink) -> {
// Executed by another EventLoop 2.
// But this doens't.
try (SafeCloseable ignord = ctx.push()) {
if (result.isSuccess()) {
...
} else {
...
}
}
});
}
The problem is that the handle method is executed by another thread that does not have the ctx in its thread local. So, you should manually set the ctx.
You can achieve the same effect using xAsync method with the ctx.eventLoop():
public Mono<RealNameAuthResp> auth(ServiceRequestContxt ctx, RealNameAuthReq req) {
return nameAuthService.auth(NameAuthConverter.CONVERTER.toDto(req))
.handleAsync((result, sink) -> {
if (result.isSuccess()) {
...
} else {
...
}
}, ctx.eventLoop());
}
We have two ways to solve this:
First, use the executor which has the ctx:
ctx.eventLoop().submit(new Task(new Event("eone")));
// If it's blocking task, then we must use ctx.blockingTaskExecutor().
Or, propagate the ctx manually:
#Slf4j
public static class Task implements Runnable {
private final Event event;
private final ServiceRequestContext ctx;
Task(Event event) {
this.event = event;
ctx = ServiceRequestContext.current();
}
#Override
public void run() {
try (SafeCloseable ignored = ctx.push()) {
...
}
}
}
#minwoox, to simplify, my code would be looks like this
public class NameAuthRestApi {
JobExecutor executor = new JobExecutor();
#Post("/code")
public HttpResponse authCode(ServiceRequestContext ctx) {
try (SafeCloseable ignore = ctx.push()) {
executor.submit(new Task(new Event("eone")));
}
return HttpResponse.of("OK");
}
#Getter
#AllArgsConstructor
public static class Event {
private String name;
}
#RequiredArgsConstructor
#Slf4j
public static class Task implements Runnable {
final Event event;
#Override
public void run() {
// couldn't access ServiceRequestContext here
ServiceRequestContext ctx = ServiceRequestContext.currentOrNull();
log.info("ctx={}, event={}", ctx, event);
}
}
public static class JobExecutor {
ExecutorService executorService = Executors.newFixedThreadPool(2);
public void submit(Task task) {
executorService.submit(task);
}
}
}
I have a class which is a subclass of ArrayAdpater, and I'm trying to make it parcable.
I keep getting this error
"Error:(21, 36) error: Parceler: Unable to find read/write generator for type android.content.Context for android.content.Context context"
Here is the class:
#org.parceler.Parcel
public class conversation_pager extends ArrayAdapter<String> {
private final ArrayList<String> messages;
private Context context;
#ParcelConstructor
public conversation_pager(Context context) {
super(context, -1);
// Initilize our variables
this.context = context;
this.messages = null;
}
public void addMessage(String user, String message) {
// Calm the linter down about a NUllPointerException.
if (messages == null) {
return;
}
// Add the message.
messages.add("<%s> %s".format(user, message));
notifyDataSetChanged();
}
};
I'm trying to avoid using a static context variable.
You shouldn't pass the context via a Parcelable. You'll need to rework your #Parcel object to only hold data and pass in the context where needed. Possibly like the following?:
public class ConversationPager extends ArrayAdapter<String> {
private final List<String> messages;
private fianl Context context;
public ConversationPager(Context context, List<Messages> messages) {
super(context, -1);
// Initilize our variables
this.context = context;
this.messsages = messages;
}
public ConversationPager(Context contex) {
this(context, new ArrayList<String>());
}
public void addMessage(String user, String message) {
// Calm the linter down about a NUllPointerException.
if (messages == null) {
return;
}
// Add the message.
messages.add("<%s> %s".format(user, message));
notifyDataSetChanged();
}
public List<String> getMessages() {
return messages;
}
};
Then you can wrap/unwrap the messages directly:
ConversationPager pager = new ConversationPager(context);
// Add some messages
// Parcel messages directly
Parcels.wrap(pager.getMessages();
// Then unwrap in the receiving context:
List<String> messages = Parcels.unwrap(incomingParcelableExtra);
ConversationPager
pager = new ConversationPager(context, messages);
Can someone please show me an example on how to establish the connection to the wss:// address with specific Authorization header, using okhttp3 okhttp-ws library?
All I have is the url of WS server and Authorization string token.
Later, I must be able to send request to that connection, listen to upcoming data from WS server and than close connection. I have a difficulties with this new to me WS world, always been working only with REST (with okhttp3 too)
So generally this sample is most of what you need
https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/WebSocketEcho.java
But you will have two changes
Use wss instead of ws in your URL
Call request.addHeader to add your token
request.addHeader("Authorization", "Bearer " + token)
I know this is an old question, but when I try to use websocket with okhttp3 there are a lot of options that I want and it was not in the library. So I create a class that handle WS connection with extra functionalities. I hope it will help some body. Gist link
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.ProtocolException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okhttp3.internal.ws.RealWebSocket;
import okhttp3.logging.HttpLoggingInterceptor;
import okio.ByteString;
/**
* Websocket class based on OkHttp3 with {event->data} message format to make your life easier.
*
* #author Ali Yusuf
* #since 3/13/17
*/
public class Socket {
private final static String TAG = Socket.class.getSimpleName();
private final static String CLOSE_REASON = "End of session";
private final static int MAX_COLLISION = 7;
public final static String EVENT_OPEN = "open";
public final static String EVENT_RECONNECT_ATTEMPT = "reconnecting";
public final static String EVENT_CLOSED = "closed";
/**
* Main socket states
*/
public enum State {
CLOSED, CLOSING, CONNECT_ERROR, RECONNECT_ATTEMPT, RECONNECTING, OPENING, OPEN
}
private static HttpLoggingInterceptor logging =
new HttpLoggingInterceptor()
.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.HEADERS : HttpLoggingInterceptor.Level.NONE);
private static OkHttpClient.Builder httpClient =
new OkHttpClient.Builder()
.addInterceptor(logging);
public static class Builder {
private Request.Builder request;
private Builder(Request.Builder request) {
this.request = request;
}
public static Builder with(#NonNull String url) {
// Silently replace web socket URLs with HTTP URLs.
if (!url.regionMatches(true, 0, "ws:", 0, 3) && !url.regionMatches(true, 0, "wss:", 0, 4))
throw new IllegalArgumentException("web socket url must start with ws or wss, passed url is " + url);
return new Builder(new Request.Builder().url(url));
}
public Builder setPingInterval(long interval, #NonNull TimeUnit unit){
httpClient.pingInterval(interval, unit);
return this;
}
public Builder addHeader(#NonNull String name, #NonNull String value) {
request.addHeader(name, value);
return this;
}
public Socket build() {
return new Socket(request.build());
}
}
/**
* Websocket state
*/
private static State state;
/**
* Websocket main request
*/
private static Request request;
/**
* Websocket connection
*/
private static RealWebSocket realWebSocket;
/**
* Reconnection post delayed handler
*/
private static Handler delayedReconnection;
/**
* Websocket events listeners
*/
private static Map<String,OnEventListener> eventListener;
/**
* Websocket events new message listeners
*/
private static Map<String,OnEventResponseListener> eventResponseListener;
/**
* Message list tobe send onEvent open {#link State#OPEN} connection state
*/
private static Map<String,String> onOpenMessageQueue = new HashMap<>();
/**
* Websocket state change listener
*/
private static OnStateChangeListener onChangeStateListener;
/**
* Websocket new message listener
*/
private static OnMessageListener messageListener;
/**
* Number of reconnection attempts
*/
private static int reconnectionAttempts;
private static boolean skipOnFailure;
private Socket(Request request) {
Socket.request = request;
state = State.CLOSED;
eventListener = new HashMap<>();
eventResponseListener = new HashMap<>();
delayedReconnection = new Handler(Looper.getMainLooper());
skipOnFailure = false;
}
/**
* Start socket connection if i's not already started
*/
public Socket connect() {
if (httpClient == null) {
throw new IllegalStateException("Make sure to use Socket.Builder before using Socket#connect.");
}
if (realWebSocket == null) {
realWebSocket = (RealWebSocket) httpClient.build().newWebSocket(request, webSocketListener);
changeState(State.OPENING);
} else if (state == State.CLOSED) {
realWebSocket.connect(httpClient.build());
changeState(State.OPENING);
}
return this;
}
/**
* Set listener which fired every time message received with contained data.
*
* #param listener message on arrive listener
*/
public Socket onEvent(#NonNull String event, #NonNull OnEventListener listener){
eventListener.put(event,listener);
return this;
}
/**
* Set listener which fired every time message received with contained data.
*
* #param listener message on arrive listener
*/
public Socket onEventResponse(#NonNull String event, #NonNull OnEventResponseListener listener){
eventResponseListener.put(event,listener);
return this;
}
/**
* Send message in {event->data} format
*
* #param event event name that you want sent message to
* #param data message data in JSON format
* #return true if the message send/on socket send quest; false otherwise
*/
public boolean send(#NonNull String event, #NonNull String data){
try {
JSONObject text = new JSONObject();
text.put("event", event);
text.put("data", new JSONObject(data));
Log.v(TAG,"Try to send data "+text.toString());
return realWebSocket.send(text.toString());
} catch (JSONException e) {
Log.e(TAG,"Try to send data with wrong JSON format, data: "+data);
}
return false;
}
/**
* Set state listener which fired every time {#link Socket#state} changed.
*
* #param listener state change listener
*/
public Socket setOnChangeStateListener(#NonNull OnStateChangeListener listener) {
onChangeStateListener = listener;
return this;
}
/**
* Message listener will be called in any message received even if it's not
* in a {event -> data} format.
*
* #param listener message listener
*/
public Socket setMessageListener(#NonNull OnMessageListener listener) {
messageListener = listener;
return this;
}
public void removeEventListener(#NonNull String event) {
eventListener.remove(event);
onOpenMessageQueue.remove(event);
}
/**
* Clear all socket listeners in one line
*/
public void clearListeners() {
eventListener.clear();
messageListener = null;
onChangeStateListener = null;
}
/**
* Send normal close request to the host
*/
public void close() {
if (realWebSocket != null) {
realWebSocket.close(1000, CLOSE_REASON);
}
}
/**
* Send close request to the host
*/
public void close(int code, #NonNull String reason) {
if (realWebSocket != null) {
realWebSocket.close(code, reason);
}
}
/**
* Terminate the socket connection permanently
*/
public void terminate() {
skipOnFailure = true; // skip onFailure callback
if (realWebSocket != null) {
realWebSocket.cancel(); // close connection
realWebSocket = null; // clear socket object
}
}
/**
* Add message in a queue if the socket not open and send them
* if the socket opened
*
* #param event event name that you want sent message to
* #param data message data in JSON format
*/
public void sendOnOpen(#NonNull String event, #NonNull String data) {
if (state != State.OPEN)
onOpenMessageQueue.put(event,data);
else
send(event,data);
}
/**
* Retrieve current socket connection state {#link State}
*/
public State getState() {
return state;
}
/**
* Change current state and call listener method with new state
* {#link OnStateChangeListener#onChange(Socket, State)}
* #param newState new state
*/
private void changeState(State newState) {
state = newState;
if (onChangeStateListener != null) {
onChangeStateListener.onChange(Socket.this, state);
}
}
/**
* Try to reconnect to the websocket after delay time using <i>Exponential backoff</i> method.
* #see
*/
private void reconnect() {
if (state != State.CONNECT_ERROR) // connection not closed !!
return;
changeState(State.RECONNECT_ATTEMPT);
if (realWebSocket != null) {
// Cancel websocket connection
realWebSocket.cancel();
// Clear websocket object
realWebSocket = null;
}
if (eventListener.get(EVENT_RECONNECT_ATTEMPT) != null) {
eventListener.get(EVENT_RECONNECT_ATTEMPT).onMessage(Socket.this, EVENT_RECONNECT_ATTEMPT);
}
// Calculate delay time
int collision = reconnectionAttempts > MAX_COLLISION ? MAX_COLLISION : reconnectionAttempts;
long delayTime = Math.round((Math.pow(2, collision)-1)/2) * 1000;
// Remove any pending posts of callbacks
delayedReconnection.removeCallbacksAndMessages(null);
// Start new post delay
delayedReconnection.postDelayed(new Runnable() {
#Override
public void run() {
changeState(State.RECONNECTING);
reconnectionAttempts++; // Increment connections attempts
connect(); // Establish new connection
}
}, delayTime);
}
private WebSocketListener webSocketListener = new WebSocketListener() {
#Override
public void onOpen(WebSocket webSocket, Response response) {
Log.v(TAG,"Socket has been opened successfully.");
// reset connections attempts counter
reconnectionAttempts = 0;
// fire open event listener
if (eventListener.get(EVENT_OPEN) != null) {
eventListener.get(EVENT_OPEN).onMessage(Socket.this, EVENT_OPEN);
}
// Send data in queue
for (String event : onOpenMessageQueue.keySet()) {
send(event, onOpenMessageQueue.get(event));
}
// clear queue
onOpenMessageQueue.clear();
changeState(State.OPEN);
}
/**
* Accept only Json data with format:
* <b> {"event":"event name","data":{some data ...}} </b>
*/
#Override
public void onMessage(WebSocket webSocket, String text) {
// print received message in log
Log.v(TAG, "New Message received "+text);
// call message listener
if (messageListener != null)
messageListener.onMessage(Socket.this, text);
try {
// Parse message text
JSONObject response = new JSONObject(text);
String event = response.getString("event");
JSONObject data = response.getJSONObject("data");
// call event listener with received data
if (eventResponseListener.get(event) != null) {
eventResponseListener.get(event).onMessage(Socket.this, event, data);
}
// call event listener
if (eventListener.get(event) != null) {
eventListener.get(event).onMessage(Socket.this, event);
}
} catch (JSONException e) {
// Message text not in JSON format or don't have {event}|{data} object
Log.e(TAG, "Unknown message format.");
}
}
#Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
// TODO: some action
}
#Override
public void onClosing(WebSocket webSocket, int code, String reason) {
Log.v(TAG,"Close request from server with reason '"+reason+"'");
changeState(State.CLOSING);
webSocket.close(1000,reason);
}
#Override
public void onClosed(WebSocket webSocket, int code, String reason) {
Log.v(TAG,"Socket connection closed with reason '"+reason+"'");
changeState(State.CLOSED);
if (eventListener.get(EVENT_CLOSED) != null) {
eventListener.get(EVENT_CLOSED).onMessage(Socket.this, EVENT_CLOSED);
}
}
/**
* This method call if:
* - Fail to verify websocket GET request => Throwable {#link ProtocolException}
* - Can't establish websocket connection after upgrade GET request => response null, Throwable {#link Exception}
* - First GET request had been failed => response null, Throwable {#link java.io.IOException}
* - Fail to send Ping => response null, Throwable {#link java.io.IOException}
* - Fail to send data frame => response null, Throwable {#link java.io.IOException}
* - Fail to read data frame => response null, Throwable {#link java.io.IOException}
*/
#Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
if (!skipOnFailure) {
skipOnFailure = false; // reset flag
Log.v(TAG, "Socket connection fail, try to reconnect. (" + reconnectionAttempts + ")");
changeState(State.CONNECT_ERROR);
reconnect();
}
}
};
public abstract static class OnMessageListener {
public abstract void onMessage (String data);
/**
* Method called from socket to execute listener implemented in
* {#link #onMessage(String)} on main thread
*
* #param socket Socket that receive the message
* #param data Data string received
*/
private void onMessage (Socket socket, final String data) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
onMessage(data);
}
});
}
}
public abstract static class OnEventListener {
public abstract void onMessage (String event);
private void onMessage (Socket socket, final String event) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
onMessage(event);
}
});
}
}
public abstract static class OnEventResponseListener extends OnEventListener {
/**
* Method need to override in listener usage
*/
public abstract void onMessage (String event, String data);
/**
* Just override the inherited method
*/
#Override
public void onMessage(String event) {}
/**
* Method called from socket to execute listener implemented in
* {#link #onMessage(String, String)} on main thread
*
* #param socket Socket that receive the message
* #param event Message received event
* #param data Data received in the message
*/
private void onMessage (Socket socket, final String event, final JSONObject data) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
onMessage(event, data.toString());
onMessage(event);
}
});
}
}
public abstract static class OnStateChangeListener {
/**
* Method need to override in listener usage
*/
public abstract void onChange (State status);
/**
* Method called from socket to execute listener implemented in
* {#link #onChange(State)} on main thread
*
* #param socket Socket that receive the message
* #param status new status
*/
private void onChange (Socket socket, final State status){
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
onChange(status);
}
});
}
}
}
Usage example:
Socket socket = Socket.Builder.with(WEBSOCKET_BASE_URL).build().connect();
socket.onEvent(Socket.EVENT_OPEN, socketOpenListener);
socket.onEvent(Socket.EVENT_RECONNECT_ATTEMPT, .....);
socket.onEvent(Socket.EVENT_CLOSED, .....);
socket.onEventResponse("Some event", socketPairListener);
socket.send("Some event", "{"some data":"in JSON format"}");
socket.sendOnOpen("Some event", "{"some data":"in JSON format"}");
I'm trying to test the start method of an Activity that uses RequestFactory.
I manage to test RF calls invoking directly the service using this article example, but I'm missing something mocking RF calls called from the tested activity.
It's more clear with the code.
EDIT : more specific
What I really want to know, is how to replace the response of a Receiver method (onSuccess,onFailure...) called in an Activity? This way I would be able to test the code inside the receiver method.
So basically here is my activity :
public class MyActivity extends AbstractActivity implements MyView.Presenter {
private List<MyEntityProxy> entities;
private MyView view;
private ClientFactory cf;
private EntityRequest entityRequest;
private AppRequestFactory rf;
#Inject
public ClientsListActivity(ClientsListViewEditor view, ClientFactory clientFactory) {
this.view = view;
this.clientFactory = clientFactory;
rf = clientFactory.getRequestFactory();
}
#Override
public void start(final AcceptsOneWidget panel, EventBus eventBus) {
view.setPresenter(this);
refreshEntities();
}
public void refreshEntities(){
entityRequest = rf.entityRequest();
entityRequest.getAll().with("opt1,"opt2").fire(new Receiver<List<MyEntityProxy>>() {
#Override
public void onSuccess(List<MyEntityProxy> response) {
entities = response;
entityRequest = requestFactory.clientRequest();
}
});
}
public List<MyEntityProxy> getEntities(){
return entities;
}
}
To test it in JUnit I use GwtMockito, so here is the test class MyActivityTest :
#RunWith(GwtMockitoTestRunner.class)
public class ClientListActivityTest{
private MyActivity activity;
private EventBus eventBus;
private AppRequestFactory rf;
#GwtMock
private ClientFactory cf;
#GwtMock
private MyView;
#GwtMock
private AcceptsOneWidget panel;
#Before
public void setUp(){
eventBus = new SimpleEventBus();
rf = RequestFactoryHelper.create(AppRequestFactory.class);
cf = new ClientFactory(eventBus,rf);
activity = new MyActivity(view,cf);
}
#Test
public void testStartActivity(){
List<EntityProxy> result = new ArrayList<EntityProxy>();
EntityProxy expectedClient = mock(EntityProxy.class);
expectedEntity.setNom("Client 1");
EntityProxy expectedClient2 = mock(EntityProxy.class);
expectedEntity.setNom("Client 2");
result.add(expectedEntity);
result.add(expectedEntity2);
//Here I have to change the requestFactory Call, so I try that but without success :
Request<?> req = mock(Request.class);
doReturn(req).when(mock(MyEntityRequest.class)).getAll();
doAnswer(RequestFactoryHelper.ok(result)).when(req).fire(any(Receiver.class));
activity.start(panel, eventBus);
assertEquals(activity.getEntities().size(),2); //This Test fails size = 0
}
}
My RequestFactoryHelper (inspired from here ) :
public class RequestFactoryHelper {
private static class MockServiceLocator implements ServiceLocator {
private final Map<Class<?>, Object> services = new HashMap<Class<?>, Object>();
#Override
public Object getInstance( Class<?> clazz ) {
// Make sure to return always the same mocked instance for each requested type
Object result = services.get( clazz );
if (result == null) {
result = mock( clazz );
services.put( clazz, result );
}
return result;
}
}
private static class MockServiceDecorator extends ServiceLayerDecorator {
#Override
public <T extends ServiceLocator> T createServiceLocator( Class<T> clazz ) {
return (T) serviceLocator;
}
}
private static MockServiceLocator serviceLocator = new MockServiceLocator();
private static ServiceLayer serviceLayer = ServiceLayer.create( new MockServiceDecorator() );
/**
* Creates a {#link RequestFactory}.
*/
public static <T extends RequestFactory> T create( Class<T> requestFactoryClass ) {
SimpleRequestProcessor processor = new SimpleRequestProcessor( serviceLayer );
T factory = RequestFactorySource.create( requestFactoryClass );
factory.initialize( new SimpleEventBus(), new InProcessRequestTransport( processor ) );
return factory;
}
/**
* Returns the same service instance as used by the RequestFactory internals.
*/
public static <T> T getService( Class<T> serviceClass ) {
T result = (T) serviceLocator.getInstance( serviceClass );
reset( result ); // reset mock to avoid side effects when used in multiple tests
return result;
}
/**
* Returns the value passed to {#link Receiver#onSuccess(Object)}
*/
public static <T> T captureResult( Receiver<T> receiver ) {
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass( Object.class );
verify( receiver ).onSuccess( (T) captor.capture() );
return (T) captor.getValue();
}
public static <T> Answer<T> ok(final T result) {
return new Answer<T>() {
#Override
public T answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
Object _receiver = args[args.length - 1];
Receiver<T> receiver = (Receiver<T>)_receiver;
receiver.onSuccess(result);
return null;
}
};
}
}
This is how I tested the Receiver method "onSuccess". I created a custom Answer for Mockito.doAnswer.
The code to test.
public void myMethod(String arg1, String arg2) {
requestFactory.adminRequest().someMethod(arg1, arg2).fire(new Receiver<Void>() {
#Override
public void onSuccess(Void response) {
placeController.goTo(new MyPlace());
}
});
}
The test.
#Test
public void testMyMethod() {
String arg1 = "arg1";
String arg2 = "arg2";
when(requestFactory.adminRequest()).thenReturn(adminRequest);
when(adminRequest.someMethod(arg1, arg2)).thenReturn(request);
doAnswer(new Answer<Void>() {
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
Receiver<Void> receiver = (Receiver<Void>) args[0];
receiver.onSuccess(null);
return null;
}
}).when(request).fire(any(Receiver.class));
myActivity.myMethod(arg1, arg2);
verify(adminRequest).someMethod(arg1, arg2);
verify(request).fire(any(Receiver.class));
verify(placeController).goTo(any(myPlace.class));
}
requestFactory, adminRequest, request and placeController are all mocks.
I am just starting with Akka and have created a test application. In it I create a bunch of actors who create a scheduler to generate a heartbeat event. Upon another type of event, I cancel the scheduler with heartbeat.cancel();, but I'd like to restart it when another event occurs. If I recreate the scheduler I see that the memory consumption increases continuously.
The question then would be either how do I resume the scheduler or how do I dispose the scheduler properly.
This is the code for that Actor
public class Device extends UntypedActor {
enum CommunicationStatus{
OK,
FAIL,
UNKNOWN
}
private static class Heartbeat {
}
public final String deviceId;
private CommunicationStatus commStatus;
private Cancellable heartBeatScheduler;
public Device(String Id)
{
deviceId = Id;
commStatus = CommunicationStatus.UNKNOWN;
}
#Override
public void preStart() {
getContext().system().eventStream().subscribe(getSelf(), DeviceCommunicationStatusUpdated.class);
startHeartbeat();
}
#Override
public void postStop() {
stopHeartBeat();
}
private void startHeartbeat() {
LoggingAdapter log = Logging.getLogger(getContext().system(), this);
log.info("Starting heartbeat");
heartBeatScheduler = getContext().system().scheduler().
schedule(Duration.Zero(),
Duration.create(1, TimeUnit.SECONDS),
getContext().self(),
new Heartbeat(),
getContext().system().dispatcher(),
ActorRef.noSender());
}
private void stopHeartBeat() {
if(!heartBeatScheduler.isCancelled()) {
LoggingAdapter log = Logging.getLogger(getContext().system(), this);
log.info("Stopping heartbeat");
heartBeatScheduler.cancel();
}
}
public String getDeviceId() {
return deviceId;
}
public CommunicationStatus getCommunicationStatus(){
return commStatus;
}
#Override
public void onReceive(Object message) throws Exception {
LoggingAdapter log = Logging.getLogger(getContext().system(), this);
if(message instanceof Heartbeat){
log.info("Pum, pum");
}
else if (message instanceof DeviceCommunicationStatusUpdated){
DeviceCommunicationStatusUpdated event = (DeviceCommunicationStatusUpdated) message;
if(event.deviceId == this.deviceId){
log.info("Received communication status update. '{}' is now {}", deviceId, event.state);
this.commStatus =
event.state == DeviceCommunicationStatusUpdated.State.OK ?
CommunicationStatus.OK : CommunicationStatus.FAIL;
if(commStatus == CommunicationStatus.OK && heartBeatScheduler.isCancelled()){
startHeartbeat();
}
else {
stopHeartBeat();
}
}
}
else unhandled(message);
}
}
Finally there is no leak, it's just that I'm new to Java and was impatient with the garbage collection. In any case, I would like to know about the resetting / restarting of a scheduler.