I have figured out how to log when a request is an ajax request and which page it is from, in a filter.
What I would really like to do is log what the ajax request is actually for. Such as the name of the method being called by the ajax (eg "findAddress" in this call:<p:ajax process="contactDetails" update="#form" listener="#{aboutYouController.findAddress}" .... )
How can I do this? My app has many ajax requests and I want to log which are being triggered.
public class TrackingFilter implements Filter {
private static Logger LOG = Logger.getLogger(TrackingFilter.class);
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) request;
String pageHit = req.getRequestURI().substring(req.getContextPath().length()+1).replace(".xhtml", "");
if(!pageHit.contains("javax.faces.resource")){ // if is a url we want to log
if ("partial/ajax".equals(req.getHeader("Faces-Request"))) {
LOG.trace("ajax on URI: " + req.getRequestURI());
}
What I would really like to do is log what the ajax request is actually for. Such as the name of the method being called by the ajax (eg "findAddress" in this call:<p:ajax process="contactDetails" update="#form" listener="#{aboutYouController.findAddress}" ....)
This information is only available in the JSF component tree. The JSF component tree is only available after view build time. A view is only built when the request has been served by FacesServlet. Thus, a servlet filter is way too early as it runs before any servlet.
You'd better run the code after the restore view phase of a postback. The JSF component tree is guaranteed to be available during that moment. You can use FacesContext#isPostback() to check if the current request is a postback. You can use PartialViewContext#isAjaxRequest() to check if the current request is an ajax request. You can use the predefined javax.faces.source request parameter to obtain the client ID of the source component of the ajax request. You can use the predefined javax.faces.behavior.event request parameter to obtain the ajax event name (e.g. change, click, action, etc).
Obtaining the associated behavior listeners is in turn a story apart. This is easy on ActionSource2 components (e.g. <h|p:commandButton action="#{...}">) as the MethodExpression is just available by ActionSource2#getActionExpression(). However, this isn't easy on BehaviorBase taghandlers (e.g. <f|p:ajax listener="#{...}">) as this API doesn't have any method like getBehaviorListeners(). There are only methods to add and remove them, but not to obtain a list of them. So some nasty reflection trickery is necessary to access the private field with those listeners whose name is JSF implementation specific. In Mojarra it's listeners and in MyFaces it's _behaviorListeners. Both are fortunately assignable from List and it's the only field of that type, so we could just check for that. Once having hand of the BehaviorListener instance, then you still need to do another reflection trickery to obtain the MethodExpression field of that instance. Yuck.
All in all, here's how the trickery look like in flavor of a PhaseListener listening on afterPhase of RESTORE_VIEW:
public class AjaxActionLoggerPhaseListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
#Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
#Override
public void afterPhase(PhaseEvent event) {
FacesContext context = event.getFacesContext();
if (!(context.isPostback() && context.getPartialViewContext().isAjaxRequest())) {
return; // Not an ajax postback.
}
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
String sourceClientId = params.get("javax.faces.source");
String behaviorEvent = params.get("javax.faces.behavior.event");
UIComponent source = context.getViewRoot().findComponent(sourceClientId);
List<String> methodExpressions = new ArrayList<>();
if (source instanceof ClientBehaviorHolder && behaviorEvent != null) {
for (ClientBehavior behavior : ((ClientBehaviorHolder) source).getClientBehaviors().get(behaviorEvent)) {
List<BehaviorListener> listeners = getField(BehaviorBase.class, List.class, behavior);
if (listeners != null) {
for (BehaviorListener listener : listeners) {
MethodExpression methodExpression = getField(listener.getClass(), MethodExpression.class, listener);
if (methodExpression != null) {
methodExpressions.add(methodExpression.getExpressionString());
}
}
}
}
}
if (source instanceof ActionSource2) {
MethodExpression methodExpression = ((ActionSource2) source).getActionExpression();
if (methodExpression != null) {
methodExpressions.add(methodExpression.getExpressionString());
}
}
System.out.println(methodExpressions); // Do your thing with it.
}
private static <C, F> F getField(Class<? extends C> classType, Class<F> fieldType, C instance) {
try {
for (Field field : classType.getDeclaredFields()) {
if (field.getType().isAssignableFrom(fieldType)) {
field.setAccessible(true);
return (F) field.get(instance);
}
}
} catch (Exception e) {
// Handle?
}
return null;
}
}
In order to get it to run, register as below in faces-config.xml:
<lifecycle>
<phase-listener>com.example.AjaxActionLoggerPhaseListener</phase-listener>
</lifecycle>
Above is tested and compatible with Mojarra and PrimeFaces and theoretically also compatible with MyFaces.
Update: in case you're using JSF utility library OmniFaces, or are open to, since version 2.4 you can use the new Components#getCurrentActionSource() utility method to find out the current action source component and Components#getActionExpressionsAndListeners() to get a list of all action methods and listeners registered on a given component. This is also useable on regular (non-ajax) requests. With that, the above PhaseListener example can be reduced as below:
public class FacesActionLoggerPhaseListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.PROCESS_VALIDATIONS;
}
#Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
#Override
public void afterPhase(PhaseEvent event) {
if (!event.getFacesContext().isPostback())) {
return;
}
UIComponent source = Components.getCurrentActionSource();
List<String> methodExpressions = Components.getActionExpressionsAndListeners(source);
System.out.println(methodExpressions); // Do your thing with it.
}
}
Related
My controllers return unified RequestResult:
public Task<RequestResult> SomeAction()
{
...
return new RequestResult(RequestResultType.NotFound);
}
public class RequestResult
{
public RequestResultType Type { get;set; }
... //actual data
}
public enum RequestResultType
{
Success = 1,
NotFound = 2
}
So basically RequestResult combines actual Action data and error type (if it happened). Now I need to specify Response Type at some point in case if Action returned Error. My best guess here is to use Middleware:
public class ResponseTypeMiddleware
{
private readonly RequestDelegate next;
public ResponseTypeMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
await next(context);
var response = context.Response.Body; //how to access object?
}
}
but I can't figure out what to do with it. What I'd perfectly like to do is to check if response is of type RequestResult, then specify ResponseType equal BadRequest. But I don't see how I can do it here as what I have is just a stream. May be I can hijack into pipeline earlier, before result was serialized (Controller?).
P. S. The reason why I don't use Controller.BadRequest directly in Action is that my Action's logic is implemented via CQRS command/query handlers, so I don't have direct access to Controller.
As you are going to process controller's action result (MVC), the best way is to use ActionFilter or ResultFilter here, instead of Middleware. Filters in ASP.NET Core are a part of MVC and so know about controllers, actions and so on. Middleware is a more common conception - it is an additional chain in application request-response pipeline.
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
}
public void OnActionExecuted(ActionExecutedContext context)
{
// do something after the action executes
// get or set controller action result here
var result = context.Result as RequestResult;
}
}
I have a number of Wicket components on a page that use a PropertyModel to reflect properties of some beans. Using AjaxFormComponentUpdatingBehaviors, these components are automatically updated via Ajax when the user changes them.
When properties are changed, the beans I want to edit with my components fire PropertyChangeEvents that should trigger re-renders of certain components that listen to these events (implementing PropertyChangeListener):
Example:
User edits a TextField with a PropertyModel and an AjaxFormComponentUpdatingBehavior
An AJAX request is sent
Wicket dispatches the request to the AjaxFormComponentUpdatingBehavior
The behavior's onEvent updates the PropertyModel (unfortunately, this method is final)
The PropertyModel calls the backing bean's property setter
The backing bean fires and PropertyChangeEvent
Now I want all components listening for changes of the same backing bean to be notified
The behavior calls the abstract onUpdate, but now it's to late, the property change events are already handled.
Since my beans are not serializable, I cannot register the components permanently as event listeners. I either need to register proxy objects that somehow retrieve the component to notify, or register my components temporarily for the scope of the AJAX request.
What I would like to do is to hook into Wickets request cycle after the target page has been loaded but before the Ajax behavior updates the model, that would lead to the PropertyChangeEvent. Here I can register every component as a event listener on their backing beans (addPropertyChangeListener) so that they are notified if they need to be updated.
Then, in onEvent, each component can take measures to update itself using the AjaxRequestTarget if they received a PropertyChangeEvent before.
Finally, in onDetach, the components can unregister from their beans (removePropertyChangeListener).
Unfortunately, I found no built-in way to get a notification "on Ajax request". In my Ajax behavior's onUpdate methods, the model has already been updated and it is too late to register change listeners. I could implement my own behavior, but with the different component options (text fields, choice lists, etc.), this is quite an effort.
Did I miss something?
I don't quite understand exactly what you mean by "components registering as event listeners". Are you talking about registering IRequestCycleListeners?
Either way, perhaps Wicket's inter-component events can help you here. Every component implements the following interface:
public interface IEventSink
{
/**
* Called when an event is sent to this sink
*
* #param event
*/
void onEvent(IEvent<?> event);
}
You could subclass AjaxFormComponentUpdatingBehavior to fire an event after a model is updated like so:
public class AjaxUpdateEvent {
private final AjaxRequestTarget target;
public AjaxUpdateEvent(AjaxRequestTarget target) {
this.target = target;
}
public AjaxRequestTarget getAjaxRequestTarget() {
return target;
}
}
public class BeanModifiedEvent extends AjaxUpdateEvent {
private final Bean bean;
public BeanModifiedEvent(AjaxRequestTarget target, Bean theBean) {
super(target);
}
public Bean getBean() {
return bean;
}
}
public class CustomUpdatingBehavior extends AjaxFormComponentUpdatingBehavior {
protected abstract void onUpdate(AjaxRequestTarget target) {
Bean bean = getFormComponent().getModelObject();
getComponent().send(getComponent().getPage(), Broadcast.BREADTH, new BeanModifiedEvent(target, bean));
}
}
You can then catch the event in the required components and add them to the ajax request:
public class UserDetailsPanel extends Panel {
.....
#Override
public void onEvent(IEvent event) {
if(event.getPayload() instanceof BeanModifiedEvent) {
// if(whatever) to control whether to add or not
AjaxRequestTarget target = ((BeanModifiedEvent) event.getPayload()).getAjaxRequestTarget();
target.add(...);
}
}
Event doc:
17.2, "Wicket events infrastructure" section
18.3, "Built-in AJAX behaviors" section
You can override #getUpdateModel() to return false, then in #onUpdate() do whatever you want before calling getFormComponent().updateModel().
You could be overriding onModelChanging of each component you are using and firing your PropertyChangeEvent there. According to the documentation onModelChanging is called before
the model is changed.
#Override
protected void onModelChanging() {
super.onModelChanging();
oldModelObject = yourComponent.getModelObject();
//fire PropertyChangeEvent
}
This is what I came up with in the end.
I subclassed IContextProvider<AjaxRequestTarget, Page> to create a custom provider for AjaxRequestTarget objects. When an AjaxRequestTarget is requested, I broadcast it to the component tree using Wicket's event mechanism.
public class BroadcastingAjaxRequestTargetProvider implements IContextProvider<AjaxRequestTarget, Page> {
private final IContextProvider<AjaxRequestTarget, Page> parent;
public BroadcastingAjaxRequestTargetProvider(IContextProvider<AjaxRequestTarget, Page> parent) {
this.parent = parent;
}
#Override
public AjaxRequestTarget get(Page page) {
AjaxRequestTarget target = parent.get(page);
page.send(page, Broadcast.BREADTH, new AjaxRequestBegin(target));
return target;
}
}
The class AjaxRequestBegin is just a small payload object encapsulating the AjaxRequestTarget.
I register this provider in my Wicket application's init() method:
setAjaxRequestTargetProvider(new BroadcastingAjaxRequestTargetProvider(getAjaxRequestTargetProvider()));
Now each component gets notified when an AJAX request is handled, before Wicket dispatches it to a component or behavior. A component can override onEvent to register a PropertyChangeListener for the request:
public void onEvent(IEvent<?> event) {
final Object payload = event.getPayload();
if (payload instanceof AjaxRequestBegin) {
final AjaxRequestTarget target = ((AjaxRequestBegin) payload).getTarget()
AjaxPropertyChangeListener listener = new AjaxPropertyChangeListener(target);
target.addListener(listener);
getBean().addPropertyChangeListener(listener);
}
}
private class AjaxPropertyChangeListener implements PropertyChangeListener, AjaxRequestTarget.IListener {
private final AjaxRequestTarget target;
public AjaxPropertyChangeListener(AjaxRequestTarget target) {
this.target = target;
}
#Override
public void propertyChange(PropertyChangeEvent event) {
target.add(MyComponent.this);
}
#Override
public void onBeforeRespond(Map<String, Component> map, AjaxRequestTarget target) {
}
#Override
public void onAfterRespond(Map<String, Component> map, IJavaScriptResponse response) {
getBean().removePropertyChangeListener(this);
}
}
Note that AjaxPropertyChangeListener also implements AjaxRequestTarget.IListener to unregister itself after the AJAX request has been completed.
Suppose I have a Website that is used in normal mode (browser) and in some other mode, like a MobileView mode (inside a mobile app). For each Controller I create, there might be correspondent controller for MobileView, processing the same url.
The easiest solution is to create ifs in all the Controllers that have MobileView logic. Another solution would be to use a correspondent url for MobileView (similar to the normal url) and two separate Controllers (possible where one extends from another; or use some other way to recycle common code)
But, a more elegant solution would be to have some extra annotations, like #SupportsMobileView (to mark a controller, and tell the app that this will have a correspondent MobileView Controller) and #MobileViewController (to mark a second controller, and tell the app that this controller needs to run immediately after the initial controller marked with #SupportsMobileView). The link between a normal controller and a MobileView controller would be through the url they process (defined with #RequestMapping).
Is it possible to extend Spring MVC (A)? Where to inject new annotation scanners (B) and annotation handlers / component handlers (C)? How should the MobileView controller be executed (D) (right now I am thinking that it could be executed through AOP, where the new handler of my new controller type programatically creates a Join-Point on the corresponding normal controller)
Note that I did not mention how this MobileView mode is triggered and detected. Let's just say that there a Session boolean variable (flag) for that.
Critics on any points (A), (B), (C) or (D) are welcomed, as well as technical hints and alternative solution to any point or the whole solution.
HandlerInterceptor can be used to intercept the RequestMapping handling. This is a simple example how to configure and implement one.
You can check for your session variable and will have a bunch of methods that will allow you to do custom processing or just exchange the view from the normal controller handling with your mobile view.
Ok, warnings:
this is only a proof of concept of what I understood must be done so:
+#MobileViewEnable and #MobileView annotated (and related) methods need to stay in the same controller
+there's no check for the httpAction used
+the two methods must have the same signature
+mobileView annotation value and requestMapping annotation value must be equals and uniques
+the logic inside callYourLogic(..) defines which method is going to be called, at the moment there's a very simple logic that check if exist the parameter ("mobile") in the request, just to test
+this code is not intended to be used as is (at all)
+don't know if it works at all outside my pc (joke :D, ehm..)
SO:
Annotations:
#Retention(RetentionPolicy.RUNTIME)
public #interface MobileView {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
public #interface MobileViewEnable {
}
ExampleController:
#Controller
public class MainController extends BaseController {
private final static Logger logger = LoggerFactory.getLogger(MainController.class);
private final static String PROVA_ROUTE = "prova";
#MobileViewEnable
#RequestMapping(PROVA_ROUTE)
public String prova() {
logger.debug("inside prova!!!");
return "provaview";
}
#MobileView(PROVA_ROUTE)
public String prova2() {
logger.debug("inside prova2!!!");
return "prova2view";
}
}
Aspect definition:
<bean id="viewAspect" class="xxx.yyy.ViewAspect" />
<aop:config>
<aop:pointcut expression="#annotation(xxx.yyy.MobileViewEnable)" id="viewAspectPointcut" />
<aop:aspect ref="viewAspect" order="1">
<aop:around method="around" pointcut-ref="viewAspectPointcut" arg-names="viewAspectPointcut"/>
</aop:aspect>
</aop:config>
Aspect implementation:
public class ViewAspect implements BeforeAdvice, ApplicationContextAware {
private final static Logger logger = LoggerFactory.getLogger(ViewAspect.class);
private ApplicationContext applicationContext;
public Object around(ProceedingJoinPoint joinPoint) {
Method mobileViewAnnotatedMethod = null;
HttpServletRequest request = getCurrentHttpRequest();
String controllerName = getSimpleClassNameWithFirstLetterLowercase(joinPoint);
Object[] interceptedMethodArgs = getInterceptedMethodArgs(joinPoint);
String methodName = getCurrentMethodName(joinPoint);
Method[] methods = getAllControllerMethods(joinPoint);
Method interceptedMethod = getInterceptedMethod(methods, methodName);
String interceptedMethodRoute = getRouteFromInterceptedMethod(interceptedMethod);
if (callYourLogic(request)) {
mobileViewAnnotatedMethod = getMobileViewAnnotatedMethodWithRouteName(methods, interceptedMethodRoute);
if (mobileViewAnnotatedMethod != null)
return invokeMethod(mobileViewAnnotatedMethod, interceptedMethodArgs, controllerName);
}
return continueInterceptedMethodExecution(joinPoint, interceptedMethodArgs);
}
private Object continueInterceptedMethodExecution(ProceedingJoinPoint joinPoint, Object[] interceptedMethodArgs) {
try {
return joinPoint.proceed(interceptedMethodArgs);
} catch (Throwable e) {
logger.error("unable to proceed with intercepted method call: " + e);
}
return null;
}
private Object[] getInterceptedMethodArgs(JoinPoint joinPoint) {
return joinPoint.getArgs();
}
private boolean callYourLogic(HttpServletRequest request) {
// INSERT HERE YOUR CUSTOM LOGIC (e.g.: is the server accessed from a mobile device?)
// THIS IS A STUPID LOGIC USED ONLY FOR EXAMPLE
return request.getParameter("mobile")!= null;
}
private HttpServletRequest getCurrentHttpRequest() {
return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
}
private String invokeMethod(Method method, Object[] methodArgs, String className) {
if (method != null) {
try {
Object classInstance = getInstanceOfClass(method, className);
return (String) method.invoke(classInstance, methodArgs);
} catch (Exception e) {
logger.error("unable to invoke method" + method + " - " + e);
}
}
return null;
}
private Object getInstanceOfClass(Method method, String className) {
return applicationContext.getBean(className);
}
private Method getMobileViewAnnotatedMethodWithRouteName(Method[] methods, String routeName) {
for (Method m : methods) {
MobileView mobileViewAnnotation = m.getAnnotation(MobileView.class);
if (mobileViewAnnotation != null && mobileViewAnnotation.value().equals(routeName))
return m;
}
return null;
}
private String getRouteFromInterceptedMethod(Method method) {
RequestMapping requestMappingAnnotation = method.getAnnotation(RequestMapping.class);
if (requestMappingAnnotation != null)
return requestMappingAnnotation.value()[0];
return null;
}
private String getCurrentMethodName(JoinPoint joinPoint) {
return joinPoint.getSignature().getName();
}
private Method[] getAllControllerMethods(JoinPoint joinPoint) {
return joinPoint.getThis().getClass().getSuperclass().getMethods();
}
private String getSimpleClassNameWithFirstLetterLowercase(JoinPoint joinPoint) {
String simpleClassName = joinPoint.getThis().getClass().getSuperclass().getSimpleName();
return setFirstLetterLowercase(simpleClassName);
}
private String setFirstLetterLowercase(String simpleClassName) {
String firstLetterOfTheString = simpleClassName.substring(0, 1).toLowerCase();
String restOfTheString = simpleClassName.substring(1);
return firstLetterOfTheString + restOfTheString;
}
private Method getInterceptedMethod(Method[] methods, String lookingForMethodName) {
for (Method m : methods)
if (m.getName().equals(lookingForMethodName))
return m;
return null;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
Quick project explanation: We have a built application based on JSF2 + Spring with Dynamic data sources. The data reference control is made with a spring-config:
<bean id="dataSource" class="com.xxxx.xxxx.CustomerRoutingDataSource">
....
and a class (referenced above):
public class CustomerRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return CustomerContextHolder.getCustomerType();
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
the CustomerContextHolder called above is as follows:
public class CustomerContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
String manager = (String)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("dataBaseManager");
if (manager != null) {
contextHolder.set(manager);
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("dataBaseManager", null);
} else {
String base = (String)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("currentDatabBase");
if (base != null)
contextHolder.set(base);
}
return (String) contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
The problem is that the last guy is calling FacesContext.getCurrentInstance() to get the servlet context. Just to explain, it uses the session Attribute dataBaseManager to tell which base it should use.
For the actual solution it was working fine, but with the implementation of a RESTEASY web service, when we make a get request the FacesContext.getCurrentInstance() is obviously returning null and crashing.
I searched a lot and could not find a way of getting the servlet-context from outside of the #GET params. I would like to know if is there any way of getting it, or if there is another solution for my dynamic datasource problem.
Thanks!
Like magic and probably not much people know.
I searched deep into the Resteasy documentation, and found a part of springmvc plugin that comes with the resteasy jars, that has a class called RequestUtil.class.
With that I was able to use the method getRequest() without the "#Context HttpServletRequest req" param.
Using that I was able to set the desired database on the request attributes, and from another thread (called by spring) get it and load the stuff from the right place!
I'm using it for a week now and it works like a charm. Only thing that I needed to do is change the determineLookupKey() above to this:
#Override
protected String determineCurrentLookupKey() {
if (FacesContext.getCurrentInstance() == null) {
//RESTEASY
HttpServletRequest hsr = RequestUtil.getRequest();
String lookUpKey = (String) hsr.getAttribute("dataBaseManager");
return lookUpKey;
}else{
//JSF
return CustomerContextHolder.getCustomerType();
}
}
Hope this helps other people!
Thiago
Background Story:
I am developing a GWT application, using the standard MVP design pattern, and also using RPC to get data from my custom data handling servlet (does a lot behind the scenes). Anyway, my goal is to create a very simple custom caching mechanism, that stores the data returned from the RPC callback in a static cache POJO. (The callback also sends a custom event using the SimpleEventBus to all registered handlers.) Then when I request the data again, I'll check the cache before doing the RPC server call again. (And also send a custom event using the EventBus).
The Problem:
When I send the event from the RPC callback, everything works fine. The problem is when I send the event outside the RPC callback when I just send the cached object. For some reason this event doesn't make it to my registered handler. Here is some code:
public void callServer(final Object source)
{
if(cachedResponse != null)
{
System.err.println("Getting Response from Cache for: "+ source.getClass().getName());
//Does this actually fire the event?
eventBus.fireEventFromSource(new ResponseEvent(cachedResponse),source);
}
else
{
System.err.println("Getting Response from Server for: "+ source.getClass().getName());
service.callServer(new AsyncCallback<String>(){
#Override
public void onFailure(Throwable caught) {
System.err.println("RPC Call Failed.");
}
#Override
public void onSuccess(String result) {
cachedResponse = result;
eventBus.fireEventFromSource(new ResponseEvent(cachedResponse),source);
}
});
}
}
Now I have two Activities, HelloActivity and GoodbyeActivity (taken from: GWT MVP code)
They also print out messages when the handler is called. Anyway, this is the output I get from the logs: (Not correct)
Getting Response from Cache for: com.hellomvp.client.activity.HelloActivity
Response in GoodbyeActivity from: com.hellomvp.client.activity.HelloActivity
Getting Response from Cache for: com.hellomvp.client.activity.GoodbyeActivity
Response in HelloActivity from: com.hellomvp.client.activity.GoodbyeActivity
What I expect to get is this:
Getting Response from Cache for: com.hellomvp.client.activity.HelloActivity
Response in HelloActivity from: com.hellomvp.client.activity.HelloActivity
Getting Response from Cache for: com.hellomvp.client.activity.GoodbyeActivity
Response in GoodbyeActivity from: com.hellomvp.client.activity.GoodbyeActivity
And I will get this expected output if I change the above code to the following: (This is the entire file this time...)
package com.hellomvp.client;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.hellomvp.events.ResponseEvent;
public class RequestManager {
private EventBus eventBus;
private String cachedResponse;
private HelloServiceAsync service = GWT.create(HelloService.class);
public RequestManager(EventBus eventBus)
{
this.eventBus = eventBus;
}
public void callServer(final Object source)
{
if(cachedResponse != null)
{
System.err.println("Getting Response from Cache for: "+ source.getClass().getName());
service.doNothing(new AsyncCallback<Void>(){
#Override
public void onFailure(Throwable caught) {
System.err.println("RPC Call Failed.");
}
#Override
public void onSuccess(Void result) {
eventBus.fireEventFromSource(new ResponseEvent(cachedResponse),source);
}
});
}
else
{
System.err.println("Getting Response from Server for: "+ source.getClass().getName());
service.callServer(new AsyncCallback<String>(){
#Override
public void onFailure(Throwable caught) {
System.err.println("RPC Call Failed.");
}
#Override
public void onSuccess(String result) {
cachedResponse = result;
eventBus.fireEventFromSource(new ResponseEvent(cachedResponse),source);
}
});
}
}
}
So the point it out, the only change is that I created a new RPC call that does nothing, and send the event in its callback, with the cached data instead, and it causes the application to work as expected.
So the Question:
What am I doing wrong? I don't understand why 'eventBus.fireEvent(...)' Needs to be in an RPC Callback to work properly. I'm thinking this is a threading issue, but I have searched Google in vain for anything that would help.
I have an entire Eclipse project that showcases this issue that I'm having, it can be found at: Eclipse Problem Project Example
Edit: Please note that using eventBus.fireEventFromSource(...) is only being used for debugging purposes, since in my actual GWT Application I have more than one registered Handler for the events. So how do you use EventBus properly?
If I understand your problem correctly you are expecting calls to SimpleEventBus#fireEventFromSource to be routed only to the source object. This is not the case - the event bus will always fire events to all registered handlers. In general the goal of using an EventBus is to decouple the sources of events from their handlers - basing functionality on the source of an event runs counter to this goal.
To get the behavior you want pass an AsyncCallback to your caching RPC client instead of trying to use the EventBus concept in a way other than intended. This has the added benefit of alerting the Activity in question when the RPC call fails:
public class RequestManager {
private String cachedResponse = null;
private HelloServiceAsync service = GWT.create(HelloService.class);
public void callServer(final AsyncCallback<String> callback) {
if (cachedResponse != null) {
callback.onSuccess(cachedResponse);
} else {
service.callServer(new AsyncCallback<String>(){
#Override
public void onFailure(Throwable caught) {
callback.onFailure(caught);
}
#Override
public void onSuccess(String result) {
cachedResponse = result;
callback.onSuccess(cachedResponse);
}
});
}
}
}
And in the Activity:
clientFactory.getRequestManager().callServer(new AsyncCallback<String>() {
#Override
public void onFailure(Throwable caught) {
// Handle failure.
}
#Override
public void onSuccess(String result) {
helloView.showResponse(result);
}
});