/* * #%L * carewebframework * %% * Copyright (C) 2008 - 2016 Regenstrief Institute, Inc. * %% * 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. * * This Source Code Form is also subject to the terms of the Health-Related * Additional Disclaimer of Warranty and Limitation of Liability available at * * http://www.carewebframework.org/licensing/disclaimer. * * #L% */ package org.carewebframework.shell; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.carewebframework.api.AppFramework; import org.carewebframework.api.FrameworkUtil; import org.carewebframework.api.context.UserContext.IUserContextEvent; import org.carewebframework.api.event.EventManager; import org.carewebframework.api.event.IEventManager; import org.carewebframework.api.property.PropertyUtil; import org.carewebframework.api.security.SecurityUtil; import org.carewebframework.api.spring.SpringUtil; import org.carewebframework.common.MiscUtil; import org.carewebframework.common.StrUtil; import org.carewebframework.help.HelpModule; import org.carewebframework.help.HelpSetCache; import org.carewebframework.help.IHelpSet; import org.carewebframework.help.viewer.HelpUtil; import org.carewebframework.shell.layout.UIElementDesktop; import org.carewebframework.shell.layout.UIElementZKBase; import org.carewebframework.shell.layout.UILayout; import org.carewebframework.shell.plugins.PluginContainer; import org.carewebframework.shell.plugins.PluginDefinition; import org.carewebframework.shell.plugins.PluginResourceHelp; import org.carewebframework.ui.Application; import org.carewebframework.ui.FrameworkWebSupport; import org.carewebframework.ui.command.CommandEvent; import org.carewebframework.ui.command.CommandRegistry; import org.carewebframework.ui.command.CommandUtil; import org.carewebframework.ui.zk.MessageWindow; import org.carewebframework.ui.zk.MessageWindow.MessageInfo; import org.carewebframework.ui.zk.PromptDialog; import org.carewebframework.ui.zk.ZKUtil; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.Executions; import org.zkoss.zk.ui.event.ClientInfoEvent; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.KeyEvent; import org.zkoss.zk.ui.ext.AfterCompose; import org.zkoss.zk.ui.util.Clients; import org.zkoss.zul.Div; import org.zkoss.zul.Menu; import org.zkoss.zul.Span; import org.zkoss.zul.Style; import org.zkoss.zul.impl.XulElement; /** * Implements a generic UI shell that can be dynamically extended with plug-ins. */ public class CareWebShell extends Div implements AfterCompose { private static final long serialVersionUID = 1L; protected static final Log log = LogFactory.getLog(CareWebShell.class); public final String LBL_CONFIRM_CLOSE = StrUtil.getLabel("cwf.shell.confirmclose.message"); public final String LBL_NO_LAYOUT = StrUtil.getLabel("cwf.shell.nolayout.message"); public final String LBL_LOGOUT_CONFIRMATION = StrUtil.getLabel("cwf.shell.logout.confirmation.message"); public final String LBL_LOGOUT_CONFIRMATION_CAPTION = StrUtil.getLabel("cwf.shell.logout.confirmation.caption"); public final String LBL_LOGOUT_CANCEL = StrUtil.getLabel("cwf.shell.logout.cancel.message"); private final AppFramework appFramework = FrameworkUtil.getAppFramework(); private final IEventManager eventManager = EventManager.getInstance(); private final CommandRegistry commandRegistry = SpringUtil.getBean("commandRegistry", CommandRegistry.class); private final List<PluginContainer> plugins = new ArrayList<>(); private final List<HelpModule> helpModules = new ArrayList<>(); private final List<String> propertyGroups = new ArrayList<>(); private UILayout layout = new UILayout(); private UIElementDesktop desktop; private final Component registeredStyles = new Span(); private CareWebStartup startupRoutines; private MessageWindow messageWindow; private String defaultLayoutName; private boolean autoStart; private boolean logoutConfirm = true; private final IUserContextEvent userContextListener = new IUserContextEvent() { /** * @see IUserContextEvent#canceled() */ @Override public void canceled() { } /** * @see IUserContextEvent#committed() */ @Override public void committed() { reset(); } /** * Prompt user for logout confirmation (unless suppressed). * * @see IUserContextEvent#pending */ @Override public String pending(boolean silent) { if (silent || PromptDialog.confirm(LBL_LOGOUT_CONFIRMATION, LBL_LOGOUT_CONFIRMATION_CAPTION, "LOGOUT.CONFIRM")) { return null; } return LBL_LOGOUT_CANCEL; } }; /** * Returns the application name for this instance of the CareWeb shell. * * @return Application name, or null if not set. */ public static String getApplicationName() { return FrameworkUtil.getAppName(); } /** * Create the shell instance. */ public CareWebShell() { super(); CareWebUtil.setShell(this); Executions.getCurrent().getDesktop().enableServerPush(true); } /** * Perform additional initializations. */ @Override public void afterCompose() { try { CommandUtil.associateCommand("help", this); appendChild(registeredStyles); appendChild(messageWindow = new MessageWindow()); desktop = new UIElementDesktop(this); setLogoutConfirm(logoutConfirm); String confirmClose = FrameworkWebSupport.getFrameworkProperty("confirmClose", "CAREWEB.CONFIRM.CLOSE"); if (StringUtils.isEmpty(confirmClose) || BooleanUtils.toBoolean(confirmClose)) { Clients.confirmClose(LBL_CONFIRM_CLOSE); } String layout = defaultLayoutName != null ? defaultLayoutName : FrameworkWebSupport.getFrameworkProperty("layout", "CAREWEB.LAYOUT.DEFAULT"); if (!StringUtils.isEmpty(layout)) { loadLayout(layout); } } catch (Exception e) { log.error("Error initializing the shell.", e); throw MiscUtil.toUnchecked(e); } } /** * Handle help requests. * * @param event A command event. */ public void onCommand(CommandEvent event) { if ("help".equals(event.getCommandName())) { Component ref = event.getReference(); HelpUtil.showCSH(ref == null ? event.getTarget() : ref); } } /** * Return information about the browser client. * * @return Client information. */ public ClientInfoEvent getClientInformation() { return Application.getDesktopInfo(getDesktop()).getClientInformation(); } /** * Capture unhandled shortcut key press events. * * @param event Control key event. */ public void onCtrlKey(Event event) { KeyEvent keyEvent = (KeyEvent) ZKUtil.getEventOrigin(event); String shortcut = CommandUtil.getShortcut(keyEvent); Collection<? extends XulElement> plugins = getActivatedPlugins(null); if (!plugins.isEmpty()) { commandRegistry.fireCommands(shortcut, keyEvent, plugins); } } /** * Returns a reference to the current UI desktop. * * @return The current UI desktop. */ public UIElementDesktop getUIDesktop() { return desktop; } /** * Returns a reference to the current UI layout. * * @return The current UI layout. */ public UILayout getUILayout() { return layout; } /** * Executed once all plugins are loaded. */ public void start() { desktop.activate(true); String initialPlugin = PropertyUtil.getValue("CAREWEB.INITIAL.SECTION", getApplicationName()); if (!StringUtils.isEmpty(initialPlugin)) { for (PluginContainer plugin : plugins) { if (initialPlugin.equals(plugin.getPluginDefinition().getId())) { plugin.bringToFront(); break; } } } if (startupRoutines == null) { startupRoutines = SpringUtil.getBean("careWebStartup", CareWebStartup.class); } startupRoutines.execute(); } /** * Loads a layout from the specified resource. * * @param resource The layout resource to load. * @throws Exception Unspecified exception. */ public void loadLayout(String resource) throws Exception { layout = UILayout.load(resource); FrameworkUtil.setAppName(layout.getName()); if (layout.isEmpty()) { PromptDialog.showError(LBL_NO_LAYOUT); } else { buildUI(layout); } } /** * Returns the name of the layout to be loaded. * * @return Name of layout to be loaded. */ public String getLayout() { return defaultLayoutName; } /** * Sets the layout to be loaded. If null, the layout specified by the configuration will be * loaded. * * @param defaultLayoutName The default layout name. * @throws Exception Unspecified exception. */ public void setLayout(String defaultLayoutName) throws Exception { this.defaultLayoutName = defaultLayoutName; if (desktop != null && !StringUtils.isEmpty(defaultLayoutName)) { loadLayout(defaultLayoutName); } } /** * Returns the auto-start setting. * * @return True if the start method is to be called automatically after loading a layout. False * if the start method must be called manually. */ public boolean isAutoStart() { return autoStart; } /** * Sets the auto-start setting. * * @param autoStart True if the start method is to be called automatically after loading a * layout. False if the start method must be called manually. */ public void setAutoStart(boolean autoStart) { this.autoStart = autoStart; } /** * Returns true if confirmation is required upon user-initiated logout. * * @return True if confirmation is required upon user-initiated logout. */ public boolean isLogoutConfirm() { return logoutConfirm; } /** * Set to true if confirmation is required upon user-initiated logout. * * @param logoutConfirm True if confirmation is required upon user-initiated logout. */ public void setLogoutConfirm(boolean logoutConfirm) { this.logoutConfirm = logoutConfirm; if (logoutConfirm) { appFramework.registerObject(userContextListener); } else { appFramework.unregisterObject(userContextListener); } } /** * Build the UI based on the specified layout. * * @param layout Layout for building UI. * @throws Exception Unspecified exception. */ public void buildUI(UILayout layout) throws Exception { this.layout = layout; reset(); layout.deserialize(desktop); layout.moveTop(); desktop.setTitle(layout.readString("title", "")); desktop.setIcon(layout.readString("icon", "")); desktop.setAppId(FrameworkUtil.getAppName()); desktop.activate(true); if (autoStart) { start(); } } /** * Resets the desktop to its baseline state and clears registered help modules and property * groups. */ public void reset() { FrameworkUtil.setAppName(null); try { desktop.activate(false); desktop.clear(); helpModules.clear(); desktop.afterInitialize(false); HelpUtil.getViewer().load(null); propertyGroups.clear(); registerPropertyGroup("CAREWEB.CONTROLS"); ZKUtil.detachChildren(registeredStyles); plugins.clear(); } catch (Exception e) {} } /** * Registers a plugin and its resources. Called internally when a plugin is instantiated. * * @param plugin Plugin to register. */ public void registerPlugin(PluginContainer plugin) { plugins.add(plugin); } /** * Unregisters a plugin and its resources. Called internally when a plugin is destroyed. * * @param plugin Plugin to unregister. */ public void unregisterPlugin(PluginContainer plugin) { plugins.remove(plugin); } /** * Adds a component to the common tool bar. * * @param component Component to add */ public void addToolbarComponent(Component component) { desktop.getToolbar().addToolbarComponent(component, null); } /** * Registers a help resource. * * @param resource Resource defining the help menu item to be added. */ public void registerHelpResource(PluginResourceHelp resource) { HelpModule def = HelpModule.getModule(resource.getModule()); if (def != null) { if (helpModules.contains(def)) { return; } IHelpSet hs = HelpSetCache.getInstance().get(def); if (hs == null) { return; } HelpUtil.getViewer().mergeHelpSet(hs); helpModules.add(def); } desktop.addHelpMenu(resource); } /** * Registers an external style sheet. If the style sheet has not already been registered, * creates a style component and adds it to the current page. * * @param url URL of style sheet. */ public void registerStyleSheet(String url) { if (findStyleSheet(url) == null) { registeredStyles.appendChild(new Style(url)); } } /** * Returns the style sheet associated with the specified URL. * * @param url URL of style sheet. * @return The associated style sheet, or null if not found. */ private Style findStyleSheet(String url) { for (Style style : registeredStyles.<Style> getChildren()) { if (style.getSrc().equals(url)) { return style; } } return null; } /** * Registers a property group. * * @param propertyGroup Property group to register. */ public void registerPropertyGroup(String propertyGroup) { if (!propertyGroups.contains(propertyGroup)) { propertyGroups.add(propertyGroup); eventManager.fireLocalEvent(Constants.EVENT_RESOURCE_PROPGROUP_ADD, propertyGroup); } } /** * Adds a menu. * * @param path Path for the menu. * @param action Associated action for the menu. * @return Created menu item. */ public Menu addMenu(String path, String action) { return desktop.addMenu(path, action, false); } /** * Returns a list of all plugins currently loaded into the UI. * * @return Currently loaded plugins. */ public Iterable<PluginContainer> getLoadedPlugins() { return plugins; } /** * Locates a loaded plugin with the specified id. * * @param id Id of plugin to locate. * @return A reference to the loaded plugin, or null if not found. */ public PluginContainer getLoadedPlugin(String id) { for (PluginContainer plugin : plugins) { if (id.equals(plugin.getPluginDefinition().getId())) { return plugin; } } return null; } /** * Locates a loaded plugin with the specified id. * * @param id Id of plugin to locate. * @param forceInit If true the plugin will be initialized if not already so. * @return A reference to the loaded and fully initialized plugin, or null if not found. */ public PluginContainer getLoadedPlugin(String id, boolean forceInit) { PluginContainer plugin = getLoadedPlugin(id); if (plugin != null && forceInit) { plugin.load(); } return plugin; } /** * Locates an activated plugin with the specified id. * * @param id Id of plugin to locate. * @return The requested plugin, or null if not found. */ public PluginContainer getActivatedPlugin(String id) { for (PluginContainer plugin : plugins) { if (id.equals(plugin.getPluginDefinition().getId()) && UIElementZKBase.getAssociatedUIElement(plugin).isActivated()) { return plugin; } } return null; } /** * Returns a list of all active plugins. * * @return List of all active plugins. */ public Iterable<PluginContainer> getActivatedPlugins() { return getActivatedPlugins(null); } /** * Populates a list of all activated plugins. * * @param list The list to be populated. If null, a new list is created. * @return A list of active plugins. */ public Collection<PluginContainer> getActivatedPlugins(Collection<PluginContainer> list) { if (list == null) { list = new ArrayList<>(); } else { list.clear(); } for (PluginContainer plugin : plugins) { if (UIElementZKBase.getAssociatedUIElement(plugin).isActivated()) { list.add(plugin); } } return list; } /** * Returns a list of all plugin definitions that are currently in use (i.e., have associated * plugins loaded) in the environment. * * @return List of PluginDefinition objects that have corresponding plugins loaded in the * environment. May be empty, but never null. */ public Iterable<PluginDefinition> getLoadedPluginDefinitions() { List<PluginDefinition> result = new ArrayList<>(); for (PluginContainer plugin : plugins) { PluginDefinition def = plugin.getPluginDefinition(); if (!result.contains(def)) { result.add(def); } } return result; } /** * Returns a list of property groups bound to loaded plugins. Guarantees each group name will * appear at most once in the list. * * @return List of all property groups bound to loaded plugins. */ public List<String> getPropertyGroups() { return propertyGroups; } /** * Logout user after confirmation prompt. */ public void logout() { // Ensure that shell is last context subscriber (should be a better way // to do this). if (logoutConfirm) { setLogoutConfirm(false); setLogoutConfirm(true); } SecurityUtil.getSecurityService().logout(false, null, null); } /** * Lock the desktop. */ public void lock() { eventManager.fireLocalEvent(org.carewebframework.ui.Constants.LOCK_EVENT, true); } /** * Shows a slide-down message. * * @param message Message to display. * @see org.carewebframework.ui.zk.MessageWindow#show(String) */ public void showMessage(String message) { messageWindow.show(message); } /** * Shows a slide-down message. * * @param message Message to display. * @param caption Caption for message. * @see org.carewebframework.ui.zk.MessageWindow#show(String, String) */ public void showMessage(String message, String caption) { messageWindow.show(message, caption); } /** * Shows a slide-down message. * * @param message Message to display * @param caption Caption for message. * @param color Background color for message window. * @see org.carewebframework.ui.zk.MessageWindow#show(String, String, String) */ public void showMessage(String message, String caption, String color) { messageWindow.show(message, caption, color); } /** * Shows a slide-down message. * * @param message Message to display * @param caption Caption for message. * @param color Background color for message window. * @param duration Message duration in milliseconds. * @see org.carewebframework.ui.zk.MessageWindow#show(String, String, String, int) */ public void showMessage(String message, String caption, String color, int duration) { messageWindow.show(message, caption, color, duration); } /** * Shows a slide-down message. * * @param message Message to display * @param caption Caption for message. * @param color Background color for message window. * @param duration Message duration in milliseconds. * @param tag Tag to classify message. * @see org.carewebframework.ui.zk.MessageWindow#show(String, String, String, Integer, String) */ public void showMessage(String message, String caption, String color, Integer duration, String tag) { messageWindow.show(message, caption, color, duration, tag); } /** * Shows a slide-down message. * * @param message Message to display * @param caption Caption for message. * @param color Background color for message window. * @param duration Message duration in milliseconds. * @param tag Tag to classify message. * @param action Javascript action. * @see org.carewebframework.ui.zk.MessageWindow#show(String, String, String, Integer, String, * String) */ public void showMessage(String message, String caption, String color, Integer duration, String tag, String action) { messageWindow.show(message, caption, color, duration, tag, action); } /** * Shows a slide-down message. * * @param messageInfo Message info structure. * @see org.carewebframework.ui.zk.MessageWindow#show(MessageInfo) */ public void showMessage(MessageInfo messageInfo) { messageWindow.show(messageInfo); } /** * Clears all slide-down messages; * * @see org.carewebframework.ui.zk.MessageWindow#clear */ public void clearMessages() { messageWindow.clear(); } /** * Clears all slide-down messages with specified tag; * * @param tag Messages with this tag will be cleared. * @see org.carewebframework.ui.zk.MessageWindow#clear(String) */ public void clearMessages(String tag) { messageWindow.clear(tag); } }