I'm creating an user event system using JDK 9 Flow API, so I have a room (which implements Flow.Subscriber<Notification>), it may have many users and each user can offer (dispatch) updates at any time.
When a user enters the room, I subscribe the updates on the room user.subscribe(this). But there is no unsubscribe, how can I unsubscribe the user when he leaves the room?
public abstract class Room implements Flow.Subscriber<Notification> {
private Flow.Subscription subscription;
public void addUser(User user) {
user.subscribe(this);
}
public void removeUser(User user) {
// How can I unsubscribe the user?
}
#Override
public void onSubscribe(final Flow.Subscription subscription) {
this.subscription = subscription;
subscription.request(1);
}
#Override
public void onError(final Throwable throwable) {
// ...
}
#Override
public void onNext(final Notification notification) {
// ...
subscription.request(1);
}
#Override
public void onComplete() {
// User left
}
}
User class:
public class User extends SubmissionPublisher<Notification> {
....
public int offer(Notification item) {
return super.offer(item, (sub, msg) -> false);
}
}
I have implemented a console application using Spring and WebSockets. The application works fine if one or more participants are connected to the base method which is anotated like this.
#MessageMapping("/chat")
#SendTo("/topic/messages")
I will copy the configuration and the implementation which i have made o be more clear.
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
}
#Controller
public class ChatController {
#MessageMapping("/chat")
#SendTo("/topic/messages")
public OutputMessage send(#Payload Message message) {
return new OutputMessage(message.getFrom(), message.getText());
}
#MessageMapping("/chat/{room}")
#SendTo("/topic/messages/{room}")
public OutputMessage enableChatRooms(#DestinationVariable String room, #Payload Message message) {
return new OutputMessage(message.getFrom(), message.getText());
}
}
#Service
public class SessionHandlerService extends StompSessionHandlerAdapter {
private String nickName;
public SessionHandlerService() {
this.nickName = "user";
}
private void sendJsonMessage(StompSession session) {
ClientMessage msg = new ClientMessage(nickName, " new user has logged in.");
session.send("/app/chat", msg);
}
#Override
public Type getPayloadType(StompHeaders headers) {
return ServerMessage.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
System.err.println(payload.toString());
}
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
session.subscribe("/topic/messages", new SessionHandlerService());
sendJsonMessage(session);
}
}
The problem which i face is that when i subscribe to /topic/messages and session.send("/app/chat", msg);everything works fine. But if i choose something like session.send("/app/chat/room1", msg); and /topic/messages/room1 the participans can not see each other messages like they are in different chat rooms.
I want to display progressDialog while observable is downloading file , and when it's done want to send file to subscriber.
I tried to make my custom subscriber by extends from Subscriber for example:
public abstract class MySubscriber<T> extends Subscriber {
abstract void onMessage(String message);
abstract void onDownloaded(File file);
}
and tried to subscribe with it:
`
MySubscriber mySubscriber = new MySubscriber() {
#Override
public void onMessage(String message) {
progessDialog.setMessage(message);
}
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Object o) {
}
};
observable.subscribe(mySubscriber);
observable is :
observable = Observable.create(new Observable.OnSubscribe<Void>() {
#Override
public void call(Subscriber<Void> subscriber) {
//file downloading code...
if (subscriber instanceof MySubscriber){
((MySubscriber) subscriber).onMessage("100%");
((MySubscriber) subscriber).onDownloaded(file);
}else{
Log.e(TAG,"subscriber is not instance of MySubscriber")
}
}
And answer is "subscriber is not instance of MySubscriber"
The reason for subscriber not being of type MySubscriber is because the instance you pass is eventually wrapped by subscribe() in SafeSubscriber:
private static <T> Subscription subscribe(Subscriber<? super T> subscriber, Observable<T> observable) {
...
if(!(subscriber instanceof SafeSubscriber)) {
subscriber = new SafeSubscriber((Subscriber)subscriber);
}
...
}
}
If you want to keep using your approach, you can cast subscriber to SafeSubscriber and call SafeSubscriber#getActual() on it to get your instance of MySubscriber.
In your case:
Observable.create(new Observable.OnSubscribe<Void>() {
#Override
public void call(Subscriber<? super Void> subscriber) {
Subscriber yourSubscriber = ((SafeSubscriber) subscriber).getActual();
((MySubscriber) yourSubscriber).onMessage("100%");
((MySubscriber) yourSubscriber).onDownloaded(file);
}
});
I have some methods in a class like this:
#Override
public void sendRemoteRecord(String token, int channelId, int eventId, final ServiceCallback<RemoteRecordResponse> callback) {
epgServicesApiManager.sendRemoteRecord(token, channelId, eventId)
.observeOn(scheduler)
.subscribe(new Action1<RemoteRecordResponse>() {
#Override
public void call(RemoteRecordResponse model) {
if (callback != null)
callback.onSuccess(model);
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
if (callback != null)
callback.onError();
}
});
}
#Override
public void activateRemoteRecord(String token, String cardNumber, final ServiceCallback<RemoteRecordActivateResponse> callback) {
epgServicesApiManager.activateRemoteRecord(token, cardNumber)
.observeOn(scheduler)
.subscribe(new Action1<RemoteRecordActivateResponse>() {
#Override
public void call(RemoteRecordActivateResponse remoteRecordActivateResponse) {
if (callback != null)
callback.onSuccess(remoteRecordActivateResponse);
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
if (callback != null)
callback.onError();
}
});
}
Is it possible to remove the duplication around the code after the observeOn() line?
The annoying part is making sure I do the null check on the callback before using it.
At present, I know of seven distinct methods I need in this class and possibly more.
Unfortunately, in Java 1.7 there is no way to fix this without increasing the amount of code. You can reduce the amount of code needed locally, by introducing some helper classes.
One solution is to move your anonymous inner classes to top-level classes. From there you can introduce a dummy callback and some null-checking work an an abstract class.
It may end up looking something like this (horizontal rules are used to highlight that these classes are in separate files).
This is a dummy callback class, it does exactly nothing, but is safe to call against. This will replace the null values.
public class NullServiceCallBack<T> implements ServiceCallBack<T> {
#Override
public void onSuccess(T target) {}
#Override
public void onError() {}
}
This is an abstract class that handles the validation, converting null values to instances of NullServiceCallback:
public abstract class CallBackAction<T> implements Action1<T> {
private final ServiceCallBack<T> Callback;
public CallBackAction(ServiceCallBack<T> callback) {
this.Callback = (null != callback) ? callback : new NullServiceCallBack<>();
}
protected ServiceCallBack<T> getCallback() {
return Callback;
}
}
This is the concrete class you'll use for success.
public class SuccessCallbackAction<T> extends CallBackAction<T> {
public SuccessCallbackAction(ServiceCallBack<T> callback) {
super(callback);
}
#Override
public void call(T target) {
getCallback().onSuccess(target);
}
}
This is the concrete class for errors. This doesn't do anything with the arguments to call, so we can make this implement for Object once and be done with it.
public class ErrorCallbackAction extends CallBackAction<Object> {
public ErrorCallbackAction(ServiceCallBack<Object> callback) {
super(callback);
}
#Override
public void call(Throwable target) {
getCallback().onError();
}
}
So in the end, your example above should look something like this:
#Override
public void sendRemoteRecord(String token, int channelId, int eventId, final ServiceCallback<RemoteRecordResponse> callback) {
epgServicesApiManager.sendRemoteRecord(token, channelId, eventId)
.observeOn(scheduler)
.subscribe(new SuccessCallbackAction<RemoteRecordResponse>(callback),
new ErrorCallbackAction(callback));
}
#Override
public void activateRemoteRecord(String token, String cardNumber, final ServiceCallback<RemoteRecordActivateResponse> callback) {
epgServicesApiManager.activateRemoteRecord(token, cardNumber)
.observeOn(scheduler)
.subscribe(new SuccessCallbackAction<RemoteRecordActivateResponse>(callback),
new ErrorCallbackAction(callback));
}
Locally, we've reduced the amount of code, and made the intent a little more clear. Globally, we've increased the complexity with the addition of 4 new classes. Whether this is worth it depends on the context your code lives in, and is your call.
Introduce a dummy callback that does nothing, then do safeCallback().onSuccess() or safeCallback().onError()
Also, you can do this:
class SuccessCallback<T> extends Action1<T>() {
#Override
public void call(T value) {
safeCallback().onSuccess(value);
}
}
class ErrorCallback extends Action1<Throwable>() {
#Override
public void call(T value) {
safeCallback().onError();
}
}
then...
subscribe(new SuccessCallback<RemoteRecordActivateResponse>(), new ErrorCallback());
Does this work?
So I really need some helpful hints with an issue I am having with my GWTP application, perhaps one of you have ran into this problem before. So I am using GWTP 1.1 with:
com.gwtplatform.mvp.Mvp (Not w/ entry point)
com.gwtplatform.dispatch.Dispatch with
com.google.gwt.uibinder.UiBinder
The issue I am having is that when ever I have a client sided error (can be almost anywhere in my client side, not sure where the boundary is) I receive a very cryptic message to do with GIN rather than a message that will help me resolve the issue. Seems like something to do with GWTP Proxying, here is the typical message that I get: http://pastebin.com/YgxPbkru the real issue isn't what the error message is presenting to me. The OpenIcidentPresenter extends IncidentPresenter which is a Presenter I have made to manage lifecycles of an Incident (in this case) this extends another presenter that I have made called a RequestPresenter, this is a Presenter that allows the user to Request PresenterWidget classes into it (into any given slot for that presenters lifecycle), this extends another presenter called RichPresenter, which just has things that almost all my Presenters require like page load indication and certain page locking etc, etc. Here are these classes:
OpenIncidentPresenter
public class OpenIncidentPresenter extends IncidentPresenter<OpenIncidentPresenter.MyView,
OpenIncidentPresenter.MyProxy> implements ViewUiHandlers, HasRequestedWidgets, NewIncidentHandler,
ChangeSectionHandler, ConfigureHandler {
public interface MyView extends View, HasUiHandlers<ViewUiHandlers> {
... Snip ...
}
#ProxyCodeSplit
#NameToken(NameTokens.open)
//#UseGatekeeper(LoginGatekeeper.class)
public interface MyProxy extends ProxyPlace<OpenIncidentPresenter> {
}
private static Logger logger = Logger.getLogger(OpenIncidentPresenter.class.getName());
Process process;
SectionTuple currentSection = new SectionTuple();
Map<Integer, SectionTuple> sections = new HashMap<Integer, SectionTuple>();
List<AccordionSection> sectionWigets = new ArrayList<AccordionSection>();
List<Activity> cachedActivities = new ArrayList<Activity>();
List<Authority> cachedAuthorities = new ArrayList<Authority>();
List<Severity> cachedSeverities = new ArrayList<Severity>();
List<Location> cachedLocations = new ArrayList<Location>();
List<Site> cachedSites = new ArrayList<Site>();
List<Area> cachedAreas = new ArrayList<Area>();
Severity currentSeverity;
boolean configured = false;
boolean changedConsequences = false;
final ImageResources imageResources;
RegisteredRequestWidget<ActionBarPresenterWidget> actionBarReg;
RegisteredRequestWidget<ProgressBarPresenterWidget> progressBarReg;
#Inject
public OpenIncidentPresenter(final EventBus eventBus, final MyView view,
final MyProxy proxy, final DispatchAsync dispatch,
final PlaceManager placeManager, ImageResources imageResources) {
super(eventBus, view, proxy, dispatch, placeManager);
this.imageResources = imageResources;
getView().setUiHandlers(this);
logger.log(Level.INFO, "Constructed OpenIncidentPresenter");
}
#Override
protected void revealInParent() {
RevealContentEvent.fire(this, ApplicationPresenter.SLOT_MIDDLE, this);
}
... Snip ...
}
IncidentPresenter
public abstract class IncidentPresenter<T extends View,
H extends Proxy<?>> extends RequestPresenter<T, H> implements HasIncident {
Logger logger = Logger.getLogger(IncidentPresenter.class.getName());
public static final String INCIDENT_COOKIE = "incidentId";
public interface LoadCallback {
void onFinished();
}
protected Incident incident;
protected DispatchAsync dispatch;
private boolean loaded = false;
public IncidentPresenter(EventBus eventBus, T view, H proxy,
DispatchAsync dispatch, PlaceManager placeManager) {
super(eventBus, view, proxy, placeManager, true);
this.dispatch = dispatch;
}
public abstract void onFailureToLoadIncident(Integer incidentId);
public abstract void onLoadedIncident(Incident incident);
#Override
public void loadIncident(final Integer id) {
loadIncident(id, null);
}
#Override
public void loadIncident(final Integer id, final LoadCallback callback) {
.. Snip ..
}
/**
* Process the incident dependencies
*/
protected void loadDependencies(final Incident incident, final LoadCallback callback) {
.. Snip ..
}
#Override
public Incident getIncident() {
return incident;
}
#Override
public void setIncident(Incident incident) {
this.incident = incident;
if(hasIncident()) {
String incidentCookie = Cookies.getCookie("incidentId");
if(incidentCookie == null || !incidentCookie.equals(String.valueOf(
incident.getId()))) {
// Set the cookie to the new incident id
Cookies.setCookie("incidentId", String.valueOf(incident.getId()));
logger.log(Level.INFO, "Set incident " + String.valueOf(incident.getId())
+ " to the cookie session");
}
} else {
Cookies.removeCookie("incidentId");
logger.log(Level.INFO, "Set incident to null incident object, cleared " +
"the cookie session");
}
}
#Override
public boolean isIncidentLoaded() {
return hasIncident() && loaded;
}
#Override
public boolean hasIncident() {
return IncidentUtils.isValid(incident);
}
#Override
public void resetIncident() {
setIncident(null);
loaded = false;
}
#Override
public DispatchAsync getDispatch() {
return dispatch;
}
public String getIncidentCookie() {
return Cookies.getCookie(INCIDENT_COOKIE);
}
}
RequestPresenter
public abstract class RequestPresenter<T extends View, H extends Proxy<?>>
extends RichPresenter<T, H> implements HasRequestedWidgets {
RequestWidgetManager requestManager = new RequestWidgetManager(this);
public RequestPresenter(EventBus eventBus, T view, H proxy,
PlaceManager placeManager) {
this(eventBus, view, proxy, placeManager, false);
}
public RequestPresenter(EventBus eventBus, T view, H proxy,
PlaceManager placeManager, boolean leaveConfirmation) {
super(eventBus, view, proxy, placeManager, leaveConfirmation);
}
#Override
public void prepareFromRequest(PlaceRequest request) {
prepareFromRequest(request, null);
}
/**
* Alternative to {#link RequestPresenter#prepareFromRequest(PlaceRequest)} that
* will allow you to register the {#link FinalCallback} in case you have dependency
* on the request widgets.
* #param request
* #param callback
*/
public void prepareFromRequest(PlaceRequest request, FinalCallback callback) {
super.prepareFromRequest(request);
executeAfterRequesting(callback);
requestWidgets();
}
#Override
protected void onBind() {
super.onBind();
registerRequestWidgets();
}
#Override
protected void onUnbind() {
super.onUnbind();
unregisterRequestWidgets();
}
#Override
protected void onHide() {
super.onHide();
dismissWidgets();
}
#Override
protected void onReveal() {
super.onReveal();
requestWidgets();
}
private void requestWidgets() {
requestManager.requestAll();
onRequestWidgets();
}
private void dismissWidgets() {
requestManager.dismissAll();
onDismissWidgets();
}
public void unregisterRequestWidgets() {
requestManager.unregisterAllWidgets();
}
#Override
public abstract void registerRequestWidgets();
public void onRequestWidgets() {
// Do nothing by default
}
public void onDismissWidgets() {
// Do nothing by default
}
protected RequestWidgetManager getRequestManager() {
return requestManager;
}
/**
* This will execute the callback method when the final request is made on loading.<br>
* This must be set before super.onReveal or super.prepareFromRequest are called.
* Or use {#link RequestPresenter#prepareFromRequest(PlaceRequest, FinalCallback)} to
* set the final callback.
* #param callback
*/
public void executeAfterRequesting(FinalCallback callback) {
requestManager.setFinalCallback(callback);
}
public <P extends PresenterWidget<?>> RequestedWidget<P> getRequestedWidget(
RegisteredRequestWidget<P> registry) {
return requestManager.get(registry);
}
public <P extends PresenterWidget<?>> RegisteredRequestWidget<P> registerRequestWidget(
HasHandlers handler, Class<P> clazz, Object slot, boolean clearSlot) {
return registerRequestWidget(handler, clazz, slot, clearSlot, null);
}
public <P extends PresenterWidget<?>> RegisteredRequestWidget<P> registerRequestWidget(
HasHandlers handler, Class<P> clazz, Object slot, boolean clearSlot,
RequestWidgetEvent.Callback<P> callback) {
return requestManager.registerWidget(handler, clazz, slot, clearSlot, callback);
}
}
RichPresenter
public abstract class RichPresenter<T extends View,
H extends Proxy<?>> extends Presenter<T, H> {
protected final PlaceManager placeManager;
private boolean leaveConfirmation;
private String defaultLeaveMessage = "Any unsaved work will be lost when " +
"leaving this page, are you sure you would like to leave?";
public RichPresenter(EventBus eventBus, T view, H proxy,
PlaceManager placeManager) {
this(eventBus, view, proxy, placeManager, false);
}
public RichPresenter(EventBus eventBus, T view, H proxy,
PlaceManager placeManager, boolean leaveConfirmation) {
super(eventBus, view, proxy);
this.placeManager = placeManager;
this.leaveConfirmation = leaveConfirmation;
}
/**
* Setup component control handlers for the UI
*/
protected void setupHandlers(final T view) {
// Do nothing by default
}
#Override
public void prepareFromRequest(PlaceRequest request) {
super.prepareFromRequest(request);
// Start Load Indicator
LoadingIndicatorEvent.fire(this, true);
// Attempt to set leave confirmation
setLeaveConfirmation(leaveConfirmation);
}
#Override
protected void onBind() {
super.onBind();
setupHandlers(getView());
}
#Override
protected void onUnbind() {
super.onUnbind();
// Remove all the event handlers
for(HandlerRegistration reg : handlerRegistrations) {
reg.removeHandler();
}
handlerRegistrations.clear();
}
#Override
protected void onReveal() {
super.onReveal();
// Stop Load Indicator
LoadingIndicatorEvent.fire(this, true);
// Attempt to set leave confirmation
setLeaveConfirmation(leaveConfirmation);
}
#Override
protected void onReset() {
super.onReset();
// Stop Load Indicator
LoadingIndicatorEvent.fire(this, false);
}
/**
* Set the page leave confirmation.
* #param leaveConfirmation
*/
public void setLeaveConfirmation(boolean leaveConfirmation) {
this.leaveConfirmation = leaveConfirmation;
if(leaveConfirmation && !BrowserUtils.isIEBrowser()) {
placeManager.setOnLeaveConfirmation(defaultLeaveMessage);
} else {
placeManager.setOnLeaveConfirmation(null);
}
}
public boolean isConfirmOnLeave() {
return leaveConfirmation;
}
public String getDefaultLeaveMessage() {
return defaultLeaveMessage;
}
public void setDefaultLeaveMessage(String message) {
this.defaultLeaveMessage = message;
}
}
I feel like this could be a contributing factor here. I have a large chain of presenters that I could be implementing wrong.
This is making it crazy hard for me to identify issues in my client side code. I have to go through my changes, reverting them until I no longer get this message. Which is just ridiculous. If you can see that I am doing something wrong or need more information let me know please! Would be so greatly appreciated.
Cheers! Ben