com.haulmont.cuba.gui.WindowManager.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.gui.WindowManager.java

Source

/*
 * Copyright (c) 2008-2016 Haulmont.
 *
 * 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 com.haulmont.cuba.gui;

import com.haulmont.bali.datastruct.Pair;
import com.haulmont.bali.util.Preconditions;
import com.haulmont.bali.util.ReflectionHelper;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.gui.components.*;
import com.haulmont.cuba.gui.config.WindowInfo;
import com.haulmont.cuba.gui.data.DataSupplier;
import com.haulmont.cuba.gui.data.Datasource;
import com.haulmont.cuba.gui.data.DsContext;
import com.haulmont.cuba.gui.data.impl.DatasourceImplementation;
import com.haulmont.cuba.gui.data.impl.DsContextImplementation;
import com.haulmont.cuba.gui.data.impl.GenericDataSupplier;
import com.haulmont.cuba.gui.executors.BackgroundWorker;
import com.haulmont.cuba.gui.logging.UIPerformanceLogger;
import com.haulmont.cuba.gui.logging.UIPerformanceLogger.LifeCycle;
import com.haulmont.cuba.gui.logging.UserActionsLogger;
import com.haulmont.cuba.gui.settings.Settings;
import com.haulmont.cuba.gui.settings.SettingsImpl;
import com.haulmont.cuba.gui.xml.data.DsContextLoader;
import com.haulmont.cuba.gui.xml.layout.ComponentLoader;
import com.haulmont.cuba.gui.xml.layout.LayoutLoader;
import com.haulmont.cuba.gui.xml.layout.LayoutLoaderConfig;
import com.haulmont.cuba.gui.xml.layout.ScreenXmlLoader;
import com.haulmont.cuba.gui.xml.layout.loaders.ComponentLoaderContext;
import com.haulmont.cuba.security.entity.PermissionType;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.dom4j.Element;
import org.perf4j.StopWatch;
import org.perf4j.slf4j.Slf4JStopWatch;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.Callable;

/**
 * GenericUI class intended for creation and opening application screens.
 */
public abstract class WindowManager {
    private org.slf4j.Logger userActionsLog = LoggerFactory.getLogger(UserActionsLogger.class);

    /**
     * Constant that is passed to {@link Window#close(String)} and {@link Window#close(String, boolean)} methods when
     * the screen is closed by window manager. Propagated to {@link Window.CloseListener#windowClosed}.
     */
    public static final String MAIN_MENU_ACTION_ID = "mainMenu";

    /**
     * How to open a screen: {@link #NEW_TAB}, {@link #THIS_TAB}, {@link #DIALOG}, {@link #NEW_WINDOW}.
     * <br>
     * You can set additional parameters for window using builder style methods:
     * <pre>
     * openEditor("sales$Customer.edit", customer,
     *            OpenType.DIALOG.width(300).resizable(false), params);
     * </pre>
     */
    public final static class OpenType {
        /**
         * Open a screen in new tab of the main window.
         * <br> In Web Client with {@code AppWindow.Mode.SINGLE} the new screen replaces current screen.
         */
        public static final OpenType NEW_TAB = new OpenType(OpenMode.NEW_TAB, false);

        /**
         * Open a screen on top of the current tab screens stack.
         */
        public static final OpenType THIS_TAB = new OpenType(OpenMode.THIS_TAB, false);

        /**
         * Open a screen as modal dialog.
         */
        public static final OpenType DIALOG = new OpenType(OpenMode.DIALOG, false);

        /**
         * In Desktop Client open a screen in new main window, in Web Client the same as new {@link #NEW_TAB}
         */
        public static final OpenType NEW_WINDOW = new OpenType(OpenMode.NEW_WINDOW, false);

        private OpenMode openMode;
        private boolean mutable = true;

        private Float width;
        private SizeUnit widthUnit;
        private Float height;
        private SizeUnit heightUnit;

        private Integer positionX;
        private Integer positionY;

        private Boolean resizable;
        private Boolean closeable;
        private Boolean modal;
        private Boolean closeOnClickOutside;
        private Boolean maximized;

        public OpenType(OpenMode openMode) {
            this.openMode = openMode;
        }

        private OpenType(OpenMode openMode, boolean mutable) {
            this.openMode = openMode;
            this.mutable = mutable;
        }

        public OpenMode getOpenMode() {
            return openMode;
        }

        public OpenType setOpenMode(OpenMode openMode) {
            OpenType instance = getMutableInstance();

            instance.openMode = openMode;
            return instance;
        }

        public SizeUnit getHeightUnit() {
            return heightUnit;
        }

        public OpenType setHeightUnit(SizeUnit heightUnit) {
            OpenType instance = getMutableInstance();
            instance.heightUnit = heightUnit;
            return instance;
        }

        public Float getHeight() {
            return height;
        }

        /**
         * @deprecated Use {@link #height(Float)} instead.
         */
        @Deprecated
        public OpenType height(Integer height) {
            return height(height.floatValue());
        }

        /**
         * @deprecated Use {@link #setHeight(Float)} instead.
         */
        @Deprecated
        public OpenType setHeight(Integer height) {
            return setHeight(height.floatValue());
        }

        public OpenType height(Float height) {
            OpenType instance = getMutableInstance();

            instance.height = height;
            return instance;
        }

        public OpenType setHeight(Float height) {
            OpenType instance = getMutableInstance();

            instance.height = height;
            return instance;
        }

        public OpenType height(String height) {
            return setHeight(height);
        }

        public OpenType setHeight(String height) {
            OpenType instance = getMutableInstance();

            SizeWithUnit size = SizeWithUnit.parseStringSize(height);

            instance.height = size.getSize();
            instance.heightUnit = size.getUnit();
            return instance;
        }

        public OpenType heightAuto() {
            OpenType instance = getMutableInstance();

            instance.height = -1.0f;
            instance.heightUnit = SizeUnit.PIXELS;
            return instance;
        }

        public SizeUnit getWidthUnit() {
            return widthUnit;
        }

        public OpenType setWidthUnit(SizeUnit widthUnit) {
            OpenType instance = getMutableInstance();
            instance.widthUnit = widthUnit;
            return instance;
        }

        public Float getWidth() {
            return width;
        }

        /**
         * @deprecated Use {@link #width(Float)} instead.
         */
        @Deprecated
        public OpenType width(Integer width) {
            return width(width.floatValue());
        }

        /**
         * @deprecated Use {@link #setWidth(Float)} instead.
         */
        @Deprecated
        public OpenType setWidth(Integer width) {
            return setWidth(width.floatValue());
        }

        public OpenType width(Float width) {
            OpenType instance = getMutableInstance();

            instance.width = width;
            return instance;
        }

        public OpenType setWidth(Float width) {
            OpenType instance = getMutableInstance();

            instance.width = width;
            return instance;
        }

        public OpenType width(String width) {
            return setWidth(width);
        }

        public OpenType setWidth(String width) {
            OpenType instance = getMutableInstance();

            SizeWithUnit size = SizeWithUnit.parseStringSize(width);

            instance.width = size.getSize();
            instance.widthUnit = size.getUnit();
            return instance;
        }

        public OpenType widthAuto() {
            OpenType instance = getMutableInstance();

            instance.width = -1.0f;
            instance.widthUnit = SizeUnit.PIXELS;
            return instance;
        }

        public Integer getPositionX() {
            return positionX;
        }

        public OpenType setPositionX(Integer positionX) {
            OpenType instance = getMutableInstance();

            instance.positionX = positionX;
            return instance;
        }

        public OpenType positionX(Integer positionX) {
            OpenType instance = getMutableInstance();

            instance.positionX = positionX;
            return instance;
        }

        public Integer getPositionY() {
            return positionY;
        }

        public OpenType setPositionY(Integer positionY) {
            OpenType instance = getMutableInstance();

            instance.positionY = positionY;
            return instance;
        }

        public OpenType positionY(Integer positionY) {
            OpenType instance = getMutableInstance();

            instance.positionY = positionY;
            return instance;
        }

        public OpenType center() {
            OpenType instance = getMutableInstance();
            instance.positionX = null;
            instance.positionY = null;
            return instance;
        }

        public Boolean getResizable() {
            return resizable;
        }

        public OpenType setResizable(Boolean resizable) {
            OpenType instance = getMutableInstance();

            instance.resizable = resizable;
            return instance;
        }

        public OpenType resizable(Boolean resizable) {
            OpenType instance = getMutableInstance();

            instance.resizable = resizable;
            return instance;
        }

        public Boolean getCloseable() {
            return closeable;
        }

        public OpenType closeable(Boolean closeable) {
            OpenType instance = getMutableInstance();

            instance.closeable = closeable;
            return instance;
        }

        public OpenType setCloseable(Boolean closeable) {
            OpenType instance = getMutableInstance();

            instance.closeable = closeable;
            return instance;
        }

        public Boolean getModal() {
            return modal;
        }

        public OpenType modal(Boolean modal) {
            OpenType instance = getMutableInstance();

            instance.modal = modal;
            return instance;
        }

        public OpenType setModal(Boolean modal) {
            OpenType instance = getMutableInstance();

            instance.modal = modal;
            return instance;
        }

        /**
         * @return true if a window can be closed by click on outside window area
         */
        public Boolean getCloseOnClickOutside() {
            return closeOnClickOutside;
        }

        /**
         * Set closeOnClickOutside to true if a window should be closed by click on outside window area.
         * It works when a window has a modal mode.
         */
        public OpenType closeOnClickOutside(Boolean closeOnClickOutside) {
            OpenType instance = getMutableInstance();

            instance.closeOnClickOutside = closeOnClickOutside;
            return instance;
        }

        /**
         * Set closeOnClickOutside to true if a window should be closed by click on outside window area.
         * It works when a window has a modal mode.
         */
        public OpenType setCloseOnClickOutside(Boolean closeOnClickOutside) {
            OpenType instance = getMutableInstance();

            instance.closeOnClickOutside = closeOnClickOutside;
            return instance;
        }

        /**
         * @return true if a window is maximized across the screen.
         */
        public Boolean getMaximized() {
            return maximized;
        }

        /**
         * Set maximized to true if a window should be maximized across the screen.
         */
        public OpenType maximized(Boolean maximized) {
            OpenType instance = getMutableInstance();

            instance.maximized = maximized;
            return instance;
        }

        /**
         * Set maximized to true if a window should be maximized across the screen.
         */
        public OpenType setMaximized(Boolean maximized) {
            OpenType instance = getMutableInstance();

            instance.maximized = maximized;
            return instance;
        }

        private OpenType getMutableInstance() {
            if (!mutable) {
                return copy();
            }

            return this;
        }

        public static OpenType valueOf(String openTypeString) {
            Preconditions.checkNotNullArgument(openTypeString, "openTypeString should not be null");

            switch (openTypeString) {
            case "NEW_TAB":
                return NEW_TAB;

            case "THIS_TAB":
                return THIS_TAB;

            case "DIALOG":
                return DIALOG;

            case "NEW_WINDOW":
                return NEW_WINDOW;

            default:
                throw new IllegalArgumentException("Unable to parse OpenType");
            }
        }

        public OpenType copy() {
            OpenType openType = new OpenType(openMode);

            openType.setModal(modal);
            openType.setResizable(resizable);
            openType.setCloseable(closeable);
            openType.setHeight(height);
            openType.setHeightUnit(heightUnit);
            openType.setWidth(width);
            openType.setWidthUnit(widthUnit);
            openType.setCloseOnClickOutside(closeOnClickOutside);
            openType.setMaximized(maximized);
            openType.setPositionX(positionX);
            openType.setPositionY(positionY);

            return openType;
        }
    }

    public enum OpenMode {
        /**
         * Open a screen in new tab of the main window.
         * <br> In Web Client with {@code AppWindow.Mode.SINGLE} the new screen replaces current screen.
         */
        NEW_TAB,
        /**
         * Open a screen on top of the current tab screens stack.
         */
        THIS_TAB,
        /**
         * Open a screen as modal dialog.
         */
        DIALOG,
        /**
         * In Desktop Client open a screen in new main window, in Web Client the same as new {@link #NEW_TAB}
         */
        NEW_WINDOW
    }

    public interface WindowCloseListener {
        void onWindowClose(Window window, boolean anyOpenWindowExist);
    }

    protected DataSupplier defaultDataSupplier;

    protected Messages messages = AppBeans.get(Messages.NAME);

    protected Scripting scripting = AppBeans.get(Scripting.NAME);

    protected Resources resources = AppBeans.get(Resources.NAME);

    protected Security security = AppBeans.get(Security.NAME);

    protected Configuration configuration = AppBeans.get(Configuration.NAME);

    protected BackgroundWorker backgroundWorker = AppBeans.get(BackgroundWorker.NAME);

    protected UserSessionSource userSessionSource = AppBeans.get(UserSessionSource.NAME);

    protected ScreenXmlLoader screenXmlLoader = AppBeans.get(ScreenXmlLoader.NAME);

    protected ScreenViewsLoader screenViewsLoader = AppBeans.get(ScreenViewsLoader.NAME);

    private DialogParams dialogParams;

    protected List<WindowCloseListener> listeners = new ArrayList<>();

    protected WindowManager() {
        dialogParams = createDialogParams();
        defaultDataSupplier = new GenericDataSupplier();
    }

    public abstract Collection<Window> getOpenWindows();

    /**
     * Select tab with window in main tabsheet.
     */
    public abstract void selectWindowTab(Window window);

    /**
     * @deprecated Please use {@link Window#setCaption(String)} ()} and {@link Window#setDescription(String)} ()} methods.
     */
    @Deprecated
    public abstract void setWindowCaption(Window window, String caption, String description);

    protected Integer getHash(WindowInfo windowInfo, Map<String, Object> params) {
        return windowInfo.hashCode() + params.hashCode();
    }

    protected Window createWindow(WindowInfo windowInfo, OpenType openType, Map<String, Object> params,
            LayoutLoaderConfig layoutConfig, boolean topLevel) {
        if (!topLevel) {
            checkPermission(windowInfo);
        }

        StopWatch loadDescriptorWatch = new Slf4JStopWatch(windowInfo.getId() + "#" + LifeCycle.LOAD,
                LoggerFactory.getLogger(UIPerformanceLogger.class));

        Element element = screenXmlLoader.load(windowInfo.getTemplate(), windowInfo.getId(), params);

        preloadMainScreenClass(element);//try to load main screen class to resolve dynamic compilation dependencies issues

        ComponentLoaderContext componentLoaderContext = new ComponentLoaderContext(params);
        componentLoaderContext.setFullFrameId(windowInfo.getId());
        componentLoaderContext.setCurrentFrameId(windowInfo.getId());

        ComponentLoader windowLoader = createLayout(windowInfo, element, componentLoaderContext, layoutConfig);
        Window clientSpecificWindow = (Window) windowLoader.getResultComponent();
        Window windowWrapper = wrapByCustomClass(clientSpecificWindow, element);

        screenViewsLoader.deployViews(element);

        DsContext dsContext = loadDsContext(element);
        initDatasources(clientSpecificWindow, dsContext, params);

        componentLoaderContext.setDsContext(dsContext);

        WindowContext windowContext = new WindowContextImpl(clientSpecificWindow, openType, params);
        clientSpecificWindow.setContext(windowContext);
        dsContext.setFrameContext(windowContext);

        //noinspection unchecked
        windowLoader.loadComponent();

        clientSpecificWindow.setWindowManager(this);

        loadDescriptorWatch.stop();

        initWrapperFrame(windowWrapper, componentLoaderContext, element, params);

        componentLoaderContext.setFrame(windowWrapper);
        componentLoaderContext.executePostInitTasks();

        if (configuration.getConfig(GlobalConfig.class).getTestMode()) {
            initDebugIds(clientSpecificWindow);
        }

        StopWatch uiPermissionsWatch = new Slf4JStopWatch(windowInfo.getId() + "#" + LifeCycle.UI_PERMISSIONS,
                LoggerFactory.getLogger(UIPerformanceLogger.class));

        // apply ui permissions
        WindowCreationHelper.applyUiPermissions(clientSpecificWindow);

        uiPermissionsWatch.stop();

        return windowWrapper;
    }

    protected void preloadMainScreenClass(Element element) {
        String screenClass = element.attributeValue("class");
        if (!StringUtils.isBlank(screenClass)) {
            scripting.loadClass(screenClass);
        }
    }

    protected void initDebugIds(Frame frame) {
    }

    protected void checkPermission(WindowInfo windowInfo) {
        boolean permitted = security.isScreenPermitted(windowInfo.getId());
        if (!permitted) {
            throw new AccessDeniedException(PermissionType.SCREEN, windowInfo.getId());
        }
    }

    protected void initDatasources(Window window, DsContext dsContext, Map<String, Object> params) {
        window.setDsContext(dsContext);

        for (Datasource ds : dsContext.getAll()) {
            if (Datasource.State.NOT_INITIALIZED.equals(ds.getState()) && ds instanceof DatasourceImplementation) {
                ((DatasourceImplementation) ds).initialized();
            }
        }
    }

    protected ComponentLoader createLayout(WindowInfo windowInfo, Element rootElement,
            ComponentLoader.Context context, LayoutLoaderConfig layoutConfig) {
        String descriptorPath = windowInfo.getTemplate();

        LayoutLoader layoutLoader = new LayoutLoader(context, AppConfig.getFactory(), layoutConfig);
        layoutLoader.setLocale(getLocale());
        if (!StringUtils.isEmpty(descriptorPath)) {
            if (descriptorPath.contains("/")) {
                descriptorPath = StringUtils.substring(descriptorPath, 0, descriptorPath.lastIndexOf("/"));
            }

            String path = descriptorPath.replaceAll("/", ".");
            int start = path.startsWith(".") ? 1 : 0;
            path = path.substring(start);

            layoutLoader.setMessagesPack(path);
        }
        //noinspection UnnecessaryLocalVariable
        ComponentLoader windowLoader = layoutLoader.createWindow(rootElement, windowInfo.getId());
        return windowLoader;
    }

    protected DsContext loadDsContext(Element element) {
        DataSupplier dataSupplier;

        String dataSupplierClass = element.attributeValue("dataSupplier");
        if (StringUtils.isEmpty(dataSupplierClass)) {
            dataSupplier = defaultDataSupplier;
        } else {
            Class<Object> aClass = ReflectionHelper.getClass(dataSupplierClass);
            try {
                dataSupplier = (DataSupplier) aClass.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException("Unable to create data supplier for screen", e);
            }
        }

        //noinspection UnnecessaryLocalVariable
        DsContext dsContext = new DsContextLoader(dataSupplier).loadDatasources(element.element("dsContext"), null);
        return dsContext;
    }

    protected Window createWindow(WindowInfo windowInfo, Map<String, Object> params) {
        Window window;
        try {
            window = (Window) windowInfo.getScreenClass().newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException("Unable to instantiate window class", e);
        }

        window.setId(windowInfo.getId());
        window.setWindowManager(this);

        init(window, params);

        StopWatch uiPermissionsWatch = new Slf4JStopWatch(windowInfo.getId() + "#" + LifeCycle.UI_PERMISSIONS,
                LoggerFactory.getLogger(UIPerformanceLogger.class));

        // apply ui permissions
        WindowCreationHelper.applyUiPermissions(window);

        uiPermissionsWatch.stop();

        return window;
    }

    protected Window createWindowByScreenClass(WindowInfo windowInfo, Map<String, Object> params) {
        Class screenClass = windowInfo.getScreenClass();

        Class[] paramTypes = ReflectionHelper.getParamTypes(params);
        Constructor constructor = null;
        try {
            constructor = screenClass.getConstructor(paramTypes);
        } catch (NoSuchMethodException e) {
            //
        }

        Object obj;
        try {
            if (constructor == null) {
                obj = screenClass.newInstance();
            } else {
                obj = constructor.newInstance(params);
            }
        } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException("Unable to instantiate window class", e);
        }

        if (obj instanceof Callable) {
            try {
                Callable callable = (Callable) obj;
                Window window = (Window) callable.call();
                return window;
            } catch (Exception e) {
                throw new RuntimeException("Unable to instantiate window class", e);
            }
        } else if (obj instanceof Runnable) {
            ((Runnable) obj).run();
            return null;
        } else
            throw new IllegalStateException("Screen class must be an instance of Callable<Window> or Runnable");
    }

    public boolean windowExist(WindowInfo windowInfo, Map<String, Object> params) {
        return (getWindow(getHash(windowInfo, params)) != null);
    }

    public Window openWindow(WindowInfo windowInfo, OpenType openType, Map<String, Object> params) {
        if (params == null) {
            params = Collections.emptyMap();
        }

        checkCanOpenWindow(windowInfo, openType, params);
        Integer hashCode = getHash(windowInfo, params);
        params = createParametersMap(windowInfo, params);
        String template = windowInfo.getTemplate();

        Window window;

        if (template != null) {
            window = createWindow(windowInfo, openType, params, LayoutLoaderConfig.getWindowLoaders(), false);
            String caption = loadCaption(window, params);
            String description = loadDescription(window, params);
            if (isOpenAsNewTab(openType)) {
                putToWindowMap(window, hashCode);
            }
            showWindow(window, caption, description, openType, windowInfo.getMultipleOpen());
            userActionsLog.trace("Window {} was opened", windowInfo.getId());
            return window;
        } else {
            Class screenClass = windowInfo.getScreenClass();
            if (screenClass != null) {
                window = createWindowByScreenClass(windowInfo, params);
                if (isOpenAsNewTab(openType)) {
                    putToWindowMap(window, hashCode);
                }
                userActionsLog.trace("Window {} was opened", windowInfo.getId());
                return window;
            } else
                return null;
        }
    }

    protected boolean isOpenAsNewTab(OpenType openType) {
        return openType.getOpenMode() == OpenMode.NEW_TAB;
    }

    public Window openWindow(WindowInfo windowInfo, OpenType openType) {
        return openWindow(windowInfo, openType, Collections.emptyMap());
    }

    protected abstract void putToWindowMap(Window window, Integer hashCode);

    protected abstract Window getWindow(Integer hashCode);

    protected abstract void checkCanOpenWindow(WindowInfo windowInfo, OpenType openType,
            Map<String, Object> params);

    protected String loadCaption(Window window, Map<String, Object> params) {
        String caption = window.getCaption();
        if (!StringUtils.isEmpty(caption)) {
            caption = TemplateHelper.processTemplate(caption, params);
        } else {
            caption = WindowParams.CAPTION.getString(params);
            if (StringUtils.isEmpty(caption)) {
                String msgPack = window.getMessagesPack();
                if (msgPack != null) {
                    caption = messages.getMessage(msgPack, "caption");
                    if (!"caption".equals(caption)) {
                        caption = TemplateHelper.processTemplate(caption, params);
                    }
                }
            } else {
                caption = TemplateHelper.processTemplate(caption, params);
            }
        }
        window.setCaption(caption);

        return caption;
    }

    protected String loadDescription(Window window, Map<String, Object> params) {
        String description = window.getDescription();
        if (!StringUtils.isEmpty(description)) {
            return TemplateHelper.processTemplate(description, params);
        } else {
            description = WindowParams.DESCRIPTION.getString(params);
            if (StringUtils.isEmpty(description)) {
                description = null;
            } else {
                description = TemplateHelper.processTemplate(description, params);
            }
        }
        window.setDescription(description);

        return description;
    }

    public Window.Editor openEditor(WindowInfo windowInfo, Entity item, OpenType openType, Datasource parentDs) {
        return openEditor(windowInfo, item, openType, Collections.emptyMap(), parentDs);
    }

    public Window.Editor openEditor(WindowInfo windowInfo, Entity item, OpenType openType) {
        return openEditor(windowInfo, item, openType, Collections.emptyMap());
    }

    public Window.Editor openEditor(WindowInfo windowInfo, Entity item, OpenType openType,
            Map<String, Object> params) {
        return openEditor(windowInfo, item, openType, params, null);
    }

    public Window.Editor openEditor(WindowInfo windowInfo, Entity item, OpenType openType,
            Map<String, Object> params, Datasource parentDs) {
        if (params == null) {
            params = Collections.emptyMap();
        }

        checkCanOpenWindow(windowInfo, openType, params);

        Integer hashCode = getHash(windowInfo, params);
        String template = windowInfo.getTemplate();

        if (openType.getOpenMode() != OpenMode.DIALOG) {
            Window existingWindow = getWindow(hashCode);
            if (existingWindow != null) {
                params = createParametersMap(windowInfo, params);
                String caption = loadCaption(existingWindow, params);
                String description = loadDescription(existingWindow, params);

                showWindow(existingWindow, caption, description, openType, false);
                return (Window.Editor) existingWindow;
            }
        }

        params = createParametersMap(windowInfo, params);
        WindowParams.ITEM.set(params, item instanceof Datasource ? ((Datasource) item).getItem() : item);

        Window window;
        if (template != null) {
            window = createWindow(windowInfo, openType, params, LayoutLoaderConfig.getEditorLoaders(), false);
        } else {
            Class windowClass = windowInfo.getScreenClass();
            if (windowClass != null) {
                window = createWindow(windowInfo, params);
                if (!(window instanceof Window.Editor)) {
                    throw new IllegalStateException(
                            String.format("Class %s does't implement Window.Editor interface", windowClass));
                }
            } else {
                throw new IllegalStateException("Invalid WindowInfo: " + windowInfo);
            }
        }
        ((Window.Editor) window).setParentDs(parentDs);

        StopWatch setItemWatch = new Slf4JStopWatch(windowInfo.getId() + "#" + LifeCycle.SET_ITEM,
                LoggerFactory.getLogger(UIPerformanceLogger.class));

        ((Window.Editor) window).setItem(item);

        setItemWatch.stop();

        String caption = loadCaption(window, params);
        String description = loadDescription(window, params);
        showWindow(window, caption, description, openType, false);

        userActionsLog.trace("Editor {} was opened", windowInfo.getId());

        return (Window.Editor) window;
    }

    public Window.Lookup openLookup(WindowInfo windowInfo, Window.Lookup.Handler handler, OpenType openType,
            Map<String, Object> params) {
        if (params == null) {
            params = Collections.emptyMap();
        }

        checkCanOpenWindow(windowInfo, openType, params);

        params = createParametersMap(windowInfo, params);

        String template = windowInfo.getTemplate();
        Window window;

        if (template != null) {
            window = createWindow(windowInfo, openType, params, LayoutLoaderConfig.getLookupLoaders(), false);

            ((Window.Lookup) window).initLookupLayout();

            Element element = ((Component.HasXmlDescriptor) window).getXmlDescriptor();
            String lookupComponent = element.attributeValue("lookupComponent");
            if (!StringUtils.isEmpty(lookupComponent)) {
                Component component = window.getComponent(lookupComponent);
                ((Window.Lookup) window).setLookupComponent(component);
            }
        } else {
            Class windowClass = windowInfo.getScreenClass();
            if (windowClass != null) {
                window = createWindow(windowInfo, params);
                if (!(window instanceof Window.Lookup)) {
                    throw new IllegalStateException(
                            String.format("Class %s does't implement Window.Lookup interface", windowClass));
                }
            } else {
                throw new IllegalStateException("Invalid WindowInfo: " + windowInfo);
            }
        }

        ((Window.Lookup) window).setLookupHandler(handler);

        String caption = loadCaption(window, params);
        String description = loadDescription(window, params);

        showWindow(window, caption, description, openType, false);

        userActionsLog.trace("Lookup {} was opened", windowInfo.getId());

        return (Window.Lookup) window;
    }

    public Window.Lookup openLookup(WindowInfo windowInfo, Window.Lookup.Handler handler, OpenType openType) {
        return openLookup(windowInfo, handler, openType, Collections.emptyMap());
    }

    public Frame openFrame(Frame parentFrame, Component parent, WindowInfo windowInfo) {
        return openFrame(parentFrame, parent, windowInfo, Collections.emptyMap());
    }

    public Frame openFrame(Frame parentFrame, Component parent, WindowInfo windowInfo, Map<String, Object> params) {
        return openFrame(parentFrame, parent, null, windowInfo, params);
    }

    public Frame openFrame(Frame parentFrame, Component parent, @Nullable String id, WindowInfo windowInfo,
            Map<String, Object> params) {
        if (params == null) {
            params = Collections.emptyMap();
        }

        // Parameters can be useful later
        params = createParametersMap(windowInfo, params);

        String src = windowInfo.getTemplate();

        ComponentLoaderContext context = new ComponentLoaderContext(params);
        context.setDsContext(parentFrame.getDsContext());
        context.setFullFrameId(windowInfo.getId());
        context.setCurrentFrameId(windowInfo.getId());

        LayoutLoader loader = new LayoutLoader(context, AppConfig.getFactory(),
                LayoutLoaderConfig.getFrameLoaders());
        loader.setLocale(getLocale());
        loader.setMessagesPack(parentFrame.getMessagesPack());

        StopWatch loadDescriptorWatch = new Slf4JStopWatch(windowInfo.getId() + "#" + LifeCycle.LOAD,
                LoggerFactory.getLogger(UIPerformanceLogger.class));

        Frame component;
        String frameId = id != null ? id : windowInfo.getId();

        Pair<ComponentLoader, Element> loaderElementPair = loader.createFrameComponent(src, frameId,
                context.getParams());
        component = (Frame) loaderElementPair.getFirst().getResultComponent();

        if (parent != null) {
            showFrame(parent, component);
        } else {
            component.setFrame(parentFrame);
        }

        loaderElementPair.getFirst().loadComponent();

        if (component.getMessagesPack() == null) {
            component.setMessagesPack(parentFrame.getMessagesPack());
        }

        context.executeInjectTasks();
        context.setFrame(component);
        context.executePostWrapTasks();

        // init of frame
        context.executeInitTasks();

        context.executePostInitTasks();

        loadDescriptorWatch.stop();

        initDebugIds(component);

        userActionsLog.trace("Frame {} was opened", windowInfo.getId());

        return component;
    }

    protected Map<String, Object> createParametersMap(WindowInfo windowInfo, Map<String, Object> params) {
        Map<String, Object> map = new HashMap<>(params.size());

        Element element = windowInfo.getDescriptor();
        if (element != null) {
            Element paramsElement = element.element("params") != null ? element.element("params") : element;
            if (paramsElement != null) {
                @SuppressWarnings({ "unchecked" })
                List<Element> paramElements = paramsElement.elements("param");
                for (Element paramElement : paramElements) {
                    String name = paramElement.attributeValue("name");
                    String value = paramElement.attributeValue("value");
                    if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
                        Boolean booleanValue = Boolean.valueOf(value);
                        map.put(name, booleanValue);
                    } else {
                        map.put(name, value);
                    }
                }
            }
        }
        map.putAll(params);

        return map;
    }

    @Deprecated
    protected DialogParams createDialogParams() {
        return new DialogParams();
    }

    @Deprecated
    public DialogParams getDialogParams() {
        return dialogParams;
    }

    protected void fireListeners(Window window, boolean anyOpenWindowExist) {
        for (WindowCloseListener wcl : listeners) {
            wcl.onWindowClose(window, anyOpenWindowExist);
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    protected abstract void showWindow(Window window, String caption, OpenType openType, boolean multipleOpen);

    protected abstract void showWindow(Window window, String caption, String description, OpenType openType,
            boolean multipleOpen);

    protected abstract void showFrame(Component parent, Frame frame);

    @Deprecated
    protected void copyDialogParamsToOpenType(OpenType mutableOpenType) {
        DialogParams dialogParams = getDialogParams();
        if (dialogParams.getCloseable() != null && mutableOpenType.getCloseable() == null) {
            mutableOpenType.closeable(dialogParams.getCloseable());
        }
        if (dialogParams.getModal() != null && mutableOpenType.getModal() == null) {
            mutableOpenType.setModal(dialogParams.getModal());
        }
        if (dialogParams.getResizable() != null && mutableOpenType.getResizable() == null) {
            mutableOpenType.setResizable(dialogParams.getResizable());
        }
        if (dialogParams.getWidth() != null && mutableOpenType.getWidth() == null) {
            mutableOpenType.setWidth(dialogParams.getWidth());
        }
        if (dialogParams.getWidthUnit() != null && mutableOpenType.getWidthUnit() == null) {
            mutableOpenType.setWidthUnit(dialogParams.getWidthUnit());
        }
        if (dialogParams.getHeight() != null && mutableOpenType.getHeight() == null) {
            mutableOpenType.setHeight(dialogParams.getHeight());
        }
        if (dialogParams.getHeightUnit() != null && mutableOpenType.getHeightUnit() == null) {
            mutableOpenType.setHeightUnit(dialogParams.getHeightUnit());
        }
    }

    protected OpenType overrideOpenTypeParams(OpenType mutableOpenType, DialogOptions dialogOptions) {
        if (BooleanUtils.isTrue(dialogOptions.getForceDialog())) {
            mutableOpenType.setOpenMode(OpenMode.DIALOG);
        }

        if (dialogOptions.getHeight() != null) {
            mutableOpenType.setHeight(dialogOptions.getHeight());
        }

        if (dialogOptions.getHeightUnit() != null) {
            mutableOpenType.setHeightUnit(dialogOptions.getHeightUnit());
        }

        if (dialogOptions.getWidth() != null) {
            mutableOpenType.setWidth(dialogOptions.getWidth());
        }

        if (dialogOptions.getWidthUnit() != null) {
            mutableOpenType.setWidthUnit(dialogOptions.getWidthUnit());
        }

        if (dialogOptions.getResizable() != null) {
            mutableOpenType.setResizable(dialogOptions.getResizable());
        }

        if (dialogOptions.getCloseable() != null) {
            mutableOpenType.setCloseable(dialogOptions.getCloseable());
        }

        if (dialogOptions.getModal() != null) {
            mutableOpenType.setModal(dialogOptions.getModal());
        }

        if (dialogOptions.getCloseOnClickOutside() != null) {
            mutableOpenType.setCloseOnClickOutside(dialogOptions.getCloseOnClickOutside());
        }

        if (dialogOptions.getMaximized() != null) {
            mutableOpenType.setMaximized(dialogOptions.getMaximized());
        }

        if (dialogOptions.getPositionX() != null) {
            mutableOpenType.setPositionX(dialogOptions.getPositionX());
        }

        if (dialogOptions.getPositionY() != null) {
            mutableOpenType.setPositionY(dialogOptions.getPositionY());
        }

        return mutableOpenType;
    }

    protected Settings getSettingsImpl(String id) {
        return new SettingsImpl(id);
    }

    protected void afterShowWindow(Window window) {
        if (!WindowParams.DISABLE_APPLY_SETTINGS.getBool(window.getContext())) {
            window.applySettings(getSettingsImpl(window.getId()));
        }
        if (!WindowParams.DISABLE_RESUME_SUSPENDED.getBool(window.getContext())) {
            ((DsContextImplementation) window.getDsContext()).resumeSuspended();
        }

        if (window instanceof AbstractWindow) {
            AbstractWindow abstractWindow = (AbstractWindow) window;

            if (abstractWindow.isAttributeAccessControlEnabled()) {
                AttributeAccessSupport attributeAccessSupport = AppBeans.get(AttributeAccessSupport.NAME);
                attributeAccessSupport.applyAttributeAccess(abstractWindow, false);
            }

            StopWatch readyStopWatch = new Slf4JStopWatch(window.getId() + "#" + LifeCycle.READY,
                    LoggerFactory.getLogger(UIPerformanceLogger.class));

            abstractWindow.ready();

            readyStopWatch.stop();
        }
    }

    public abstract void close(Window window);

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    protected Locale getLocale() {
        return userSessionSource.getUserSession().getLocale();
    }

    protected Window wrapByCustomClass(Frame window, Element element) {
        String screenClass = element.attributeValue("class");
        if (StringUtils.isBlank(screenClass)) {
            throw new GuiDevelopmentException("'class' attribute is not defined in XML descriptor", window.getId());
        }

        Class<?> aClass = scripting.loadClass(screenClass);
        if (aClass == null) {
            throw new GuiDevelopmentException("Unable to load controller class", window.getId());
        }
        //noinspection UnnecessaryLocalVariable
        Window wrappingWindow = ((WrappedWindow) window).wrapBy(aClass);
        return wrappingWindow;
    }

    protected void initWrapperFrame(Window wrappingWindow, ComponentLoaderContext context, Element element,
            Map<String, Object> params) {
        if (wrappingWindow instanceof AbstractWindow) {
            Element companionsElem = element.element("companions");
            if (companionsElem != null) {
                StopWatch companionStopWatch = new Slf4JStopWatch(
                        wrappingWindow.getId() + "#" + LifeCycle.COMPANION,
                        LoggerFactory.getLogger(UIPerformanceLogger.class));

                initCompanion(companionsElem, (AbstractWindow) wrappingWindow);

                companionStopWatch.stop();
            }
        }

        StopWatch injectStopWatch = new Slf4JStopWatch(wrappingWindow.getId() + "#" + LifeCycle.INJECTION,
                LoggerFactory.getLogger(UIPerformanceLogger.class));

        ControllerDependencyInjector dependencyInjector = AppBeans.getPrototype(ControllerDependencyInjector.NAME,
                wrappingWindow, params);
        dependencyInjector.inject();

        injectStopWatch.stop();

        context.executeInjectTasks();
        context.executePostWrapTasks();

        init(wrappingWindow, params);

        context.executeInitTasks();
    }

    protected void init(Window window, Map<String, Object> params) {
        if (window instanceof AbstractWindow) {
            StopWatch initStopWatch = new Slf4JStopWatch(window.getId() + "#" + LifeCycle.INIT,
                    LoggerFactory.getLogger(UIPerformanceLogger.class));

            ((AbstractWindow) window).init(params);

            initStopWatch.stop();
        }
    }

    protected void initCompanion(Element companionsElem, AbstractWindow window) {
        Element element = companionsElem.element(AppConfig.getClientType().toString().toLowerCase());
        if (element != null) {
            String className = element.attributeValue("class");
            if (!StringUtils.isBlank(className)) {
                Class aClass = scripting.loadClassNN(className);
                Object companion;
                try {
                    companion = aClass.newInstance();
                    window.setCompanion(companion);

                    CompanionDependencyInjector cdi = new CompanionDependencyInjector(window, companion);
                    cdi.inject();
                } catch (Exception e) {
                    throw new RuntimeException("Unable to init Companion", e);
                }
            }
        }
    }

    /**
     * Opens default screen. Implemented only for the web module.
     * <p>
     * Default screen can be defined with the {@code cuba.web.defaultScreenId} application property.
     */
    public abstract void openDefaultScreen();

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * Show notification with {@link Frame.NotificationType#HUMANIZED}. <br>
     * Supports line breaks ({@code \n}).
     *
     * @param caption text
     */
    public abstract void showNotification(String caption);

    /**
     * Show notification. <br>
     * Supports line breaks ({@code \n}).
     *
     * @param caption text
     * @param type    defines how to display the notification.
     *                Don't forget to escape data from the database in case of {@code *_HTML} types!
     */
    public abstract void showNotification(String caption, Frame.NotificationType type);

    /**
     * Show notification with caption description. <br>
     * Supports line breaks ({@code \n}).
     *
     * @param caption     caption
     * @param description text
     * @param type        defines how to display the notification.
     *                    Don't forget to escape data from the database in case of {@code *_HTML} types!
     */
    public abstract void showNotification(String caption, String description, Frame.NotificationType type);

    /**
     * Show message dialog with title and message. <br>
     * Supports line breaks ({@code \n}) for non HTML messageType.
     *
     * @param title       dialog title
     * @param message     text
     * @param messageType defines how to display the dialog.
     *                    Don't forget to escape data from the database in case of {@code *_HTML} types!
     */
    public abstract void showMessageDialog(String title, String message, Frame.MessageType messageType);

    /**
     * Show options dialog with title and message. <br>
     * Supports line breaks ({@code \n}) for non HTML messageType.
     *
     * @param title       dialog title
     * @param message     text
     * @param messageType defines how to display the dialog.
     *                    Don't forget to escape data from the database in case of {@code *_HTML} types!
     * @param actions     available actions
     */
    public abstract void showOptionDialog(String title, String message, Frame.MessageType messageType,
            Action[] actions);

    /**
     * Shows exception dialog with default caption, message and displays stacktrace of given throwable.
     *
     * @param throwable throwable
     */
    public abstract void showExceptionDialog(Throwable throwable);

    /**
     * Shows exception dialog with given caption, message and displays stacktrace of given throwable.
     *
     * @param throwable throwable
     * @param caption   dialog caption
     * @param message   dialog message
     */
    public abstract void showExceptionDialog(Throwable throwable, @Nullable String caption,
            @Nullable String message);

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * Open a web page in browser.
     * @param url       URL of the page
     * @param params    optional parameters.
     * <br>The following parameters are recognized by Web client:
     * <ul>
     * <li>{@code target} - String value used as the target name in a
     * window.open call in the client. This means that special values such as
     * "_blank", "_self", "_top", "_parent" have special meaning. If not specified, "_blank" is used.</li>
     * <li> {@code width} - Integer value specifying the width of the browser window in pixels</li>
     * <li> {@code height} - Integer value specifying the height of the browser window in pixels</li>
     * <li> {@code border} - String value specifying the border style of the window of the browser window.
     * Possible values are "DEFAULT", "MINIMAL", "NONE".</li>
     * </ul>
     * Desktop client doesn't support any parameters and just ignores them.
     */
    public abstract void showWebPage(String url, @Nullable Map<String, Object> params);
}