org.uberfire.client.workbench.Workbench.java Source code

Java tutorial

Introduction

Here is the source code for org.uberfire.client.workbench.Workbench.java

Source

/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.uberfire.client.workbench;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Event;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.ClosingEvent;
import com.google.gwt.user.client.Window.ClosingHandler;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import org.jboss.errai.bus.client.api.ClientMessageBus;
import org.jboss.errai.bus.client.framework.ClientMessageBusImpl;
import org.jboss.errai.ioc.client.api.AfterInitialization;
import org.jboss.errai.ioc.client.api.EnabledByProperty;
import org.jboss.errai.ioc.client.api.EntryPoint;
import org.jboss.errai.ioc.client.container.SyncBeanDef;
import org.jboss.errai.ioc.client.container.SyncBeanManager;
import org.jboss.errai.security.shared.api.identity.User;
import org.slf4j.Logger;
import org.uberfire.backend.vfs.Path;
import org.uberfire.client.mvp.PerspectiveActivity;
import org.uberfire.client.mvp.PlaceManager;
import org.uberfire.client.resources.WorkbenchResources;
import org.uberfire.client.workbench.events.ApplicationReadyEvent;
import org.uberfire.mvp.ParameterizedCommand;
import org.uberfire.mvp.impl.DefaultPlaceRequest;
import org.uberfire.mvp.impl.PathPlaceRequest;
import org.uberfire.rpc.SessionInfo;
import org.uberfire.rpc.impl.SessionInfoImpl;
import org.uberfire.security.authz.AuthorizationManager;
import org.uberfire.security.authz.AuthorizationPolicy;
import org.uberfire.security.authz.PermissionManager;

/**
 * Responsible for bootstrapping the client-side Workbench user interface by coordinating calls to the PanelManager and
 * PlaceManager. Normally this happens automatically with no need for assistance or interference from the application.
 * Thus, applications don't usually need to do anything with the Workbench class directly.
 * <p>
 * <h2>Delaying Workbench Startup</h2>
 * <p>
 * In special cases, applications may wish to delay the startup of the workbench. For example, an application that
 * relies on global variables (also known as singletons or Application Scoped beans) that are initialized based on
 * response data from the server doesn't want UberFire to start initializing its widgets until that server response has
 * come in.
 * <p>
 * To delay startup, add a <i>Startup Blocker</i> before Errai starts calling {@link AfterInitialization} methods. The
 * best place to do this is in the {@link PostConstruct} method of an {@link EntryPoint} bean. You would then remove the
 * startup blocker from within the callback from the server:
 * <p>
 * <pre>
 *   {@code @EntryPoint}
 *   public class MyMutableGlobal() {
 *     {@code @Inject private Workbench workbench;}
 *     {@code @Inject private Caller<MyRemoteService> remoteService;}
 *
 *     // set up by a server call. don't start the app until it's populated!
 *     {@code private MyParams params;}
 *
 *     {@code @PostConstruct}
 *     private void earlyInit() {
 *       workbench.addStartupBlocker(MyMutableGlobal.class);
 *     }
 *
 *     {@code @AfterInitialization}
 *     private void lateInit() {
 *       remoteService.call(new {@code RemoteCallback<MyParams>}{
 *         public void callback(MyParams params) {
 *           MyMutableGlobal.this.params = params;
 *           workbench.removeStartupBlocker(MyMutableGlobal.class);
 *         }
 *       }).fetchParameters();
 *     }
 *   }
 * </pre>
 */
@EntryPoint
@EnabledByProperty(value = "uberfire.plugin.mode.active", negated = true)
public class Workbench {

    /**
     * List of classes who want to do stuff (often server communication) before the workbench shows up.
     */
    private final Set<Class<?>> startupBlockers = new HashSet<Class<?>>();
    private final Set<String> headersToKeep = new HashSet<String>();
    /**
     * This indirection exists so we can ignore spurious WindowCloseEvents in IE10.
     * In all other cases, the {@link WorkbenchCloseHandler} simply executes whatever command we pass it.
     */
    private final WorkbenchCloseHandler workbenchCloseHandler = GWT.create(WorkbenchCloseHandler.class);
    @Inject
    LayoutSelection layoutSelection;
    /**
     * Fired when all startup blockers have cleared and just before the workbench starts to build its components.
     */
    @Inject
    private Event<ApplicationReadyEvent> appReady;
    private boolean isStandaloneMode = false;
    @Inject
    private SyncBeanManager iocManager;
    @Inject
    private PlaceManager placeManager;
    private final Command workbenchCloseCommand = new Command() {
        @Override
        public void execute() {
            placeManager.closeAllPlaces(); // would be preferable to close current perspective, which should be recursive
        }
    };
    @Inject
    private PermissionManager permissionManager;
    @Inject
    private AuthorizationManager authorizationManager;
    @Inject
    private VFSServiceProxy vfsService;
    private WorkbenchLayout layout;
    @Inject
    private User identity;
    @Inject
    private ClientMessageBus bus;
    @Inject
    private Logger logger;
    private SessionInfo sessionInfo = null;

    /**
     * Requests that the workbench does not attempt to create any UI parts until the given responsible party has
     * been removed as a startup blocker. Blockers are tracked as a set, so adding the same class more than once has no
     * effect.
     * @param responsibleParty any Class object; typically it will be the class making the call to this method.
     * Must not be null.
     */
    public void addStartupBlocker(Class<?> responsibleParty) {
        startupBlockers.add(responsibleParty);
        System.out.println(responsibleParty.getName() + " is blocking workbench startup.");
    }

    /**
     * Causes the given responsible party to no longer block workbench initialization.
     * If the given responsible party was not already in the blocking set (either because
     * it was never added, or it has already been removed) then the method call has no effect.
     * <p>
     * After removing the blocker, if there are no more blockers left in the blocking set, the workbench UI is
     * bootstrapped immediately. If there are still one or more blockers left in the blocking set, the workbench UI
     * remains uninitialized.
     * @param responsibleParty any Class object that was previously passed to {@link #addStartupBlocker(Class)}.
     * Must not be null.
     */
    public void removeStartupBlocker(Class<?> responsibleParty) {
        if (startupBlockers.remove(responsibleParty)) {
            System.out.println(responsibleParty.getName() + " is no longer blocking startup.");
        } else {
            System.out.println(responsibleParty.getName()
                    + " tried to unblock startup, but it wasn't blocking to begin with!");
        }
        startIfNotBlocked();
    }

    // package-private so tests can call in
    @AfterInitialization
    void startIfNotBlocked() {
        System.out.println(startupBlockers.size() + " workbench startup blockers remain.");
        if (startupBlockers.isEmpty()) {
            bootstrap();
        }
    }

    @PostConstruct
    private void earlyInit() {
        layout = layoutSelection.get();
        WorkbenchResources.INSTANCE.CSS().ensureInjected();

        isStandaloneMode = Window.Location.getParameterMap().containsKey("standalone");

        for (final Map.Entry<String, List<String>> parameter : Window.Location.getParameterMap().entrySet()) {
            if (parameter.getKey().equals("header")) {
                headersToKeep.addAll(parameter.getValue());
            }
        }
    }

    private void bootstrap() {
        logger.info("Starting workbench...");
        ((SessionInfoImpl) currentSession()).setId(((ClientMessageBusImpl) bus).getSessionId());

        layout.setMarginWidgets(isStandaloneMode, headersToKeep);
        layout.onBootstrap();

        addLayoutToRootPanel(layout);

        //Lookup PerspectiveProviders and if present launch it to set-up the Workbench
        if (!isStandaloneMode) {
            final PerspectiveActivity homePerspective = getHomePerspectiveActivity();
            if (homePerspective != null) {
                appReady.fire(new ApplicationReadyEvent());
                placeManager.goTo(new DefaultPlaceRequest(homePerspective.getIdentifier()));
            } else {
                logger.error("No home perspective available!");
            }
        } else {
            handleStandaloneMode(Window.Location.getParameterMap());
        }

        // Ensure orderly shutdown when Window is closed (eg. saves workbench state)
        Window.addWindowClosingHandler(new ClosingHandler() {

            @Override
            public void onWindowClosing(ClosingEvent event) {
                workbenchCloseHandler.onWindowClose(workbenchCloseCommand);
            }
        });

        // Resizing the Window should resize everything
        Window.addResizeHandler(new ResizeHandler() {
            @Override
            public void onResize(ResizeEvent event) {
                layout.resizeTo(event.getWidth(), event.getHeight());
            }
        });

        // Defer the initial resize call until widgets are rendered and sizes are available
        Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
            @Override
            public void execute() {
                layout.onResize();
            }
        });
    }

    // TODO add tests for standalone startup vs. full startup
    private void handleStandaloneMode(final Map<String, List<String>> parameters) {
        if (parameters.containsKey("perspective") && !parameters.get("perspective").isEmpty()) {
            placeManager.goTo(new DefaultPlaceRequest(parameters.get("perspective").get(0)));
        } else if (parameters.containsKey("path") && !parameters.get("path").isEmpty()) {
            placeManager.goTo(new DefaultPlaceRequest("StandaloneEditorPerspective"));
            vfsService.get(parameters.get("path").get(0), new ParameterizedCommand<Path>() {
                @Override
                public void execute(final Path response) {
                    if (parameters.containsKey("editor") && !parameters.get("editor").isEmpty()) {
                        placeManager.goTo(new PathPlaceRequest(response, parameters.get("editor").get(0)));
                    } else {
                        placeManager.goTo(new PathPlaceRequest(response));
                    }
                }
            });
        }
    }

    /**
     * Get the home perspective defined at the workbench authorization policy.
     * <p>
     * <p>If no home is defined then the perspective marked as "{@code isDefault=true}" is taken.</p>
     * <p>
     * <p>Notice that access permission over the selected perspective is always required.</p>
     * @return A perspective instance or null if no perspective is found or access to it has been denied.
     */
    public PerspectiveActivity getHomePerspectiveActivity() {

        // Get the user's home perspective
        PerspectiveActivity homePerspective = null;
        AuthorizationPolicy authPolicy = permissionManager.getAuthorizationPolicy();
        String homePerspectiveId = authPolicy.getHomePerspective(identity);

        // Get the workbench's default perspective
        PerspectiveActivity defaultPerspective = null;
        final Collection<SyncBeanDef<PerspectiveActivity>> perspectives = iocManager
                .lookupBeans(PerspectiveActivity.class);

        for (final SyncBeanDef<PerspectiveActivity> perspective : perspectives) {
            final PerspectiveActivity instance = perspective.getInstance();

            if (homePerspectiveId != null && homePerspectiveId.equals(instance.getIdentifier())) {
                homePerspective = instance;
                if (defaultPerspective != null) {
                    iocManager.destroyBean(defaultPerspective);
                }
            } else if (instance.isDefault()) {
                defaultPerspective = instance;
            } else {
                iocManager.destroyBean(instance);
            }
        }
        // The home perspective has always priority over the default
        PerspectiveActivity targetPerspective = homePerspective != null ? homePerspective : defaultPerspective;

        // Check access rights
        if (targetPerspective != null && authorizationManager.authorize(targetPerspective, identity)) {
            return targetPerspective;
        }
        return null;
    }

    @Produces
    @ApplicationScoped
    private SessionInfo currentSession() {
        if (sessionInfo == null) {
            sessionInfo = new SessionInfoImpl(identity);
        }
        return sessionInfo;
    }

    void addLayoutToRootPanel(final WorkbenchLayout layout) {
        RootLayoutPanel.get().add(layout.getRoot());
    }
}