package org.sigmah.client.event; /* * #%L * Sigmah * %% * Copyright (C) 2010 - 2016 URD * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import org.sigmah.client.dispatch.CommandResultHandler; import org.sigmah.client.i18n.I18N; import org.sigmah.client.inject.Injector; import org.sigmah.client.page.Page; import org.sigmah.client.page.PageRequest; import org.sigmah.client.page.RequestParameter; import org.sigmah.client.page.event.PageRequestEvent; import org.sigmah.client.ui.notif.N10N; import org.sigmah.client.ui.presenter.base.Presenter; import org.sigmah.client.ui.widget.Loadable; import org.sigmah.client.ui.zone.Zone; import org.sigmah.client.ui.zone.ZoneRequest; import org.sigmah.client.ui.zone.event.ZoneRequestEvent; import org.sigmah.shared.command.SecureNavigationCommand; import org.sigmah.shared.command.result.SecureNavigationResult; import com.allen_sauer.gwt.log.client.Log; import com.google.gwt.event.shared.GwtEvent; import com.google.gwt.event.shared.HandlerManager; import com.google.inject.Inject; import com.google.inject.Singleton; /** * Application event bus implementation. * <p> * FIXME: This implementation makes strong references on the registered * handlers. This can create memory leaks and undesired network usage when the * view associated to the handler is destroyed. * * A better implementation should use a * <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet">WeakSet</a> * to avoid storing strong references. * * @author Denis Colliot (dcolliot@ideia.fr) * @author Tom Miette (tmiette@ideia.fr) */ @Singleton public class EventBusImpl extends HandlerManager implements EventBus { /** * Page where anonymous users are redirected using "{@code navigate(null)}". */ private static final Page DEFAULT_ANONYMOUS_PAGE = Page.LOGIN; /** * Page where authenticated users are redirected using "{@code navigate(null)}". */ private static final Page DEFAULT_AUTHENTICATED_PAGE = Page.DASHBOARD; /** * Callback interface used to handle {@link PageRequestEvent} dispatch action. * * @author Denis Colliot (dcolliot@ideia.fr) */ private static interface PageRequestEventCallback { /** * Method called once the dispatch action is complete. * * @param event * The returned event. */ void onPageRequestEventComplete(final GwtEvent<?> event); } /** * Injected application injector. */ private final Injector injector; /** * Last failed accessed {@link PageRequest} that will be reload on next authentication. */ private PageRequest failedAccessedPageRequest; /** * Optional {@link Loadable}s to set in {@code loading} mode during page access. */ private Loadable[] loadables; /** * Application event bus initialization. * * @param injector * The application injector. */ @Inject public EventBusImpl(final Injector injector) { super(null); this.injector = injector; this.failedAccessedPageRequest = null; } /** * {@inheritDoc} */ @Override public void logout() { // Clears the session. injector.getAuthenticationProvider().clearAuthentication(); // Navigates to default page. navigate(null); } /** * {@inheritDoc} */ @Override public void fireEvent(final GwtEvent<?> event) { if (!(event instanceof PageRequestEvent)) { // Other (or null) events. handleEvent(event); return; } // Page request event. // Before leaving the current presenter. final Presenter<?> presenter = injector.getPageManager().getCurrentPresenter(); updateZoneRequest(Zone.APP_LOADER.requestWith(RequestParameter.CONTENT, true)); if (presenter == null) { handleEvent(event); return; } final PageRequest pageRequest = ((PageRequestEvent) event).getRequest(); final Page page = pageRequest != null ? pageRequest.getPage() : null; if (injector.getPageManager().isPopupView(page)) { // Popup case : no 'before leaving' action. // TODO [BEFORE LEAVING] Associer le 'beforeLeaving' des popup à la fermeture des popups. handleEvent(event); } else { // Page change : 'before leaving' action launched. presenter.beforeLeaving(new LeavingCallback() { @Override public void leavingOk() { handleEvent(event); } @Override public void leavingKo() { handleEvent(null); updateZoneRequest(Zone.APP_LOADER.requestWith(RequestParameter.CONTENT, false)); } }); } } /** * Handlers the given {@code event}. * * @param event * The event. If the event is a {@link PageRequestEvent}, a command is sent to the server in order to * retrieve user access rights. */ private void handleEvent(final GwtEvent<?> event) { if (!(event instanceof PageRequestEvent)) { // Other (or null) events. if (event != null) { super.fireEvent(event); } return; } // Page request event. handlePageRequestEvent((PageRequestEvent) event, new PageRequestEventCallback() { @SuppressWarnings("deprecation") @Override public void onPageRequestEventComplete(final GwtEvent<?> event) { updateZoneRequest(Zone.APP_LOADER.requestWith(RequestParameter.CONTENT, false)); if (event == null) { navigate(null, loadables); return; } // Local cache initialization. injector.getClientCache().init(); EventBusImpl.super.fireEvent(event); } }); } /** * Executes a command to retrieve access rights for the current [authenticated/anonymous] user. * * @param event * The {@link PageRequestEvent} event. * @param callback * The {@link PageRequestEventCallback} instance executed once event has been handled (i.e. on command * callback execution). */ private void handlePageRequestEvent(final PageRequestEvent event, final PageRequestEventCallback callback) { final Page page = event.getRequest().getPage(); if (Log.isTraceEnabled()) { Log.trace("User is attempting to access page '" + page + "'."); } // Determining page token to access. final PageRequestEvent accessedPageEvent; if (page != null && injector.getPageManager().getPage(page.getToken()) != null) { // Page is valid and registered with PageManager. accessedPageEvent = event; } else if (injector.getAuthenticationProvider().isAnonymous()) { // Page is invalid and user anonymous: redirecting user to login page. accessedPageEvent = new PageRequestEvent(DEFAULT_ANONYMOUS_PAGE); } else { // Page is invalid and user authenticated: redirecting user to home page. accessedPageEvent = new PageRequestEvent(DEFAULT_AUTHENTICATED_PAGE); } // Executing command securing the navigation event. injector.getDispatch().execute(new SecureNavigationCommand(accessedPageEvent.getRequest().getPage()), new CommandResultHandler<SecureNavigationResult>() { @Override protected void onCommandSuccess(final SecureNavigationResult result) { final boolean wasAuthenticated = !injector.getAuthenticationProvider().isAnonymous(); // Sets the authentication. injector.getAuthenticationProvider().updateCache(result.getAuthentication()); if (result.isGranted()) { // Page access is granted. callback.onPageRequestEventComplete(accessedPageEvent); } else { // Unauthorized page access. if (event.isFromHistory()) { // Directly from URL. callback.onPageRequestEventComplete(null); } else { // From application link. N10N.error(I18N.CONSTANTS.navigation_unauthorized_access()); if (wasAuthenticated && injector.getAuthenticationProvider().isAnonymous()) { // User is no longer authenticated (expired session). callback.onPageRequestEventComplete(null); } } } } @Override protected void onCommandFailure(final Throwable caught) { if (Log.isErrorEnabled()) { Log.error("An unexpected error occured during 'SecureNavigationCommand' executin.", caught); } N10N.error(I18N.CONSTANTS.navigation_error()); } }, loadables); } /** * {@inheritDoc} */ @Override public void navigate(final Page page, final Loadable... loadables) { final PageRequest pageRequest; // If a requested page (not a popup one) has been recorded, we redirect the logged in user towards its previous // request. if (page == null && failedAccessedPageRequest != null && !injector.getPageManager().isPopupView(failedAccessedPageRequest.getPage())) { pageRequest = failedAccessedPageRequest; failedAccessedPageRequest = null; } // Default case. else { pageRequest = new PageRequest(page); } navigateRequest(pageRequest, loadables); } /** * {@inheritDoc} */ @Override public void navigateRequest(final PageRequest request, final Loadable... loadables) { this.loadables = loadables; this.fireEvent(new PageRequestEvent(request)); } /** * {@inheritDoc} */ @Override public void updateZone(final Zone zone) { updateZoneRequest(new ZoneRequest(zone)); } /** * {@inheritDoc} */ @Override public void updateZoneRequest(final ZoneRequest zoneRequest) { this.fireEvent(new ZoneRequestEvent(zoneRequest)); } }