/* * #%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 org.apache.commons.beanutils.BeanUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.carewebframework.api.property.IPropertyProvider; import org.carewebframework.api.property.PropertyUtil; import org.carewebframework.common.MiscUtil; import org.carewebframework.shell.layout.UIElementBase; import org.carewebframework.shell.layout.UIElementMenuItem; import org.carewebframework.shell.layout.UIElementPlugin; import org.carewebframework.shell.layout.UIElementTabPane; import org.carewebframework.shell.layout.UIElementTabView; import org.carewebframework.shell.layout.UIElementTreePane; import org.carewebframework.shell.layout.UIElementTreeView; import org.carewebframework.shell.layout.UIException; import org.carewebframework.shell.layout.UILayout; import org.carewebframework.shell.plugins.PluginDefinition; import org.carewebframework.shell.plugins.PluginRegistrationException; import org.carewebframework.shell.plugins.PluginRegistry; /** * This class is provided primarily for backward compatibility with the old fixed layout of tab * views containing tree views. It can only be used with a layout that contains a tab view * component. Its public methods are fully compatible with version 1.0. */ public class CareWebShellEx extends CareWebShell { public static final String TOOLBAR_PATH = "@toolbar"; private static final long serialVersionUID = 1L; private static final String delim = "\\\\"; private static final Log log = LogFactory.getLog(CareWebShellEx.class); private static final String EXC_UNKNOWN_PLUGIN = "@cwf.shell.error.plugin.unknown"; /** * Locates the plugin's parent UI element given a tab pane and a path. */ public class PathResolver { private final Class<? extends UIElementBase> rootClass; private final Class<? extends UIElementBase> childClass; /** * Creates a path resolver. * * @param rootClass This is the class that will hold instances of the child class. * @param childClass This is the class that will hold the plugin. */ public PathResolver(Class<? extends UIElementBase> rootClass, Class<? extends UIElementBase> childClass) { if (!UIElementBase.canAcceptChild(rootClass, childClass) || !UIElementBase.canAcceptParent(childClass, rootClass) || !UIElementBase.canAcceptParent(childClass, childClass) || !UIElementBase.canAcceptChild(childClass, UIElementPlugin.class)) { throw new UIException("Root and child classes are not compatible."); } this.rootClass = rootClass; this.childClass = childClass; } /** * Resolves the path, returning the UI element to be used as the parent of the plugin. * * @param tabPane Tab pane. * @param path Path to resolve. * @return The plugin parent */ protected UIElementBase resolvePath(UIElementTabPane tabPane, String path) { return getElement(path, getRoot(tabPane), childClass); } /** * Returns the root for the specified tab pane. If the tab pane does not yet have a root, * one will be created for it. * * @param tabPane The tab pane. * @return The root. */ protected UIElementBase getRoot(UIElementTabPane tabPane) { UIElementBase root = tabPane.findChildElement(rootClass); if (root == null) { try { root = rootClass.newInstance(); root.setParent(tabPane); } catch (Exception e) { throw MiscUtil.toUnchecked(e); } } return root; } } private UIElementTabView tabView; private String defaultPluginId; private PathResolver pathResolver; private final PluginRegistry pluginRegistry = PluginRegistry.getInstance(); public CareWebShellEx() { super(); } /** * Registers the plugin with the specified id and path. If a tree path is absent, the plugin is * associated with the tab itself. * * @param path Format is <tab name>\<tree node path> * @param id Unique id of plugin * @return Container created for the plugin. * @throws Exception Unspecified exception. */ public UIElementBase registerFromId(String path, String id) throws Exception { return registerFromId(path, id, null); } /** * Registers the plugin with the specified id and path. If a tree path is absent, the plugin is * associated with the tab itself. * * @param path Format is <tab name>\<tree node path> * @param id Unique id of plugin * @param propertySource Optional source for retrieving property values. * @return Container created for the plugin. * @throws Exception Unspecified exception. */ public UIElementBase registerFromId(String path, String id, IPropertyProvider propertySource) throws Exception { return register(path, pluginById(id), propertySource); } /** * Lookup a plugin definition by its id. Raises a runtime exception if the plugin is not found. * * @param id Plugin id. * @return The plugin definition. */ private PluginDefinition pluginById(String id) { PluginDefinition def = pluginRegistry.get(id); if (def == null) { throw new PluginRegistrationException(EXC_UNKNOWN_PLUGIN, id); } return def; } /** * Register a plugin by specifying a path and a url. * * @param path Format is <tab name>\<tree node path> * @param url Main url of plugin. * @return Container created for the plugin. * @throws Exception Unspecified exception. */ public UIElementBase register(String path, String url) throws Exception { return register(path, url, null); } /** * Register a plugin by specifying a path and a url. * * @param path Format is <tab name>\<tree node path> * @param url Main url of plugin. * @param propertySource Optional source for retrieving property values. * @return Container created for the plugin. * @throws Exception Unspecified exception. */ public UIElementBase register(String path, String url, IPropertyProvider propertySource) throws Exception { PluginDefinition def = new PluginDefinition(); def.setUrl(url); return register(path, def, propertySource); } /** * Register a menu. * * @param path Path for the menu. * @param action Associated action for the menu. * @return Created menu. */ public UIElementMenuItem registerMenu(String path, String action) { UIElementMenuItem menu = getElement(path, getUIDesktop().getMenubar(), UIElementMenuItem.class); menu.setAction(action); return menu; } private <T extends UIElementBase> T getElement(String path, UIElementBase root, Class<T> childClass) { UIElementBase parent = root; T ele = null; try { for (String pc : path.split("\\\\")) { ele = null; for (UIElementBase child : parent.getChildren()) { if (!childClass.isInstance(child)) { continue; } @SuppressWarnings("unchecked") T ele2 = (T) child; if (pc.equalsIgnoreCase(BeanUtils.getProperty(ele2, "label"))) { ele = ele2; break; } } if (ele == null) { ele = childClass.newInstance(); ele.setParent(parent); BeanUtils.setProperty(ele, "label", pc); } parent = ele; } } catch (Exception e) { throw MiscUtil.toUnchecked(e); } return ele; } /** * Registers the plugin with the specified definition with the specified path. If a tree path is * absent, the plugin is associated with the tab itself. * * @param path Format is <tab name>\<tree node path> * @param def Plugin definition * @return The newly created plugin. * @throws Exception Unspecified exception. */ public UIElementBase register(String path, PluginDefinition def) throws Exception { return register(path, def, null); } /** * Registers the plugin with the specified definition with the specified path. If a tree path is * absent, the plugin is associated with the tab itself. * * @param path Format is <tab name>\<tree node path> * @param def Plugin definition * @param propertySource Optional source for retrieving property values. * @return The newly created plugin. * @throws Exception Unspecified exception. */ public UIElementBase register(String path, PluginDefinition def, IPropertyProvider propertySource) throws Exception { if (def.isForbidden()) { log.info("Access to plugin " + def.getName() + " is restricted."); return null; } if (def.isDisabled()) { log.info("Plugin " + def.getName() + " is disabled."); return null; } UIElementBase parent = parentFromPath(path); UIElementBase plugin = parent == null ? null : def.createElement(parent, propertySource); String defPluginId = getDefaultPluginId(); if (!defPluginId.isEmpty() && (defPluginId.equalsIgnoreCase(def.getId()) || defPluginId.equalsIgnoreCase(def.getName()))) { plugin.activate(true); } return plugin; } /** * Registers a layout at the specified path. * * @param path Format is <tab name>\<tree node path> * @param resource Location of the xml layout. * @throws Exception Unspecified exception. */ public void registerLayout(String path, String resource) throws Exception { UILayout layout = UILayout.load(resource); UIElementBase parent = parentFromPath(path); if (parent != null) { layout.deserialize(parent); } } /** * Returns the parent UI element based on the provided path. * * @param path Format is <tab name>\<tree node path> * @return The parent UI element. * @throws Exception Unspecified exception. */ private UIElementBase parentFromPath(String path) throws Exception { if (TOOLBAR_PATH.equalsIgnoreCase(path)) { return getUIDesktop().getToolbar(); } String[] pieces = path.split(delim, 2); UIElementTabPane tabPane = pieces.length == 0 ? null : findTabPane(pieces[0]); UIElementBase parent = pieces.length < 2 ? null : getPathResolver().resolvePath(tabPane, pieces[1]); return parent == null ? tabPane : parent; } /** * Locate the tab with the corresponding label, or create one if not found. * * @param name Label text of tab to find. * @return Tab corresponding to label text. * @throws Exception Unspecified exception. */ private UIElementTabPane findTabPane(String name) throws Exception { UIElementTabView tabView = getTabView(); UIElementTabPane tabPane = null; while ((tabPane = tabView.getChild(UIElementTabPane.class, tabPane)) != null) { if (name.equalsIgnoreCase(tabPane.getLabel())) { return tabPane; } } tabPane = new UIElementTabPane(); tabPane.setParent(tabView); tabPane.setLabel(name); return tabPane; } /** * Returns the tab view that will receive plug-ins. Searches the UI desktop for the first * occurrence of a tab view that it finds. The result is cached. This may return null if a tab * view is not found. * * @return The target tab view, or null if not found. */ private UIElementTabView getTabView() { if (tabView == null) { tabView = getUIDesktop().findChildElement(UIElementTabView.class); } return tabView; } /** * Returns the default plugin id as a user preference. * * @return The default plugin id. */ private String getDefaultPluginId() { if (defaultPluginId == null) { try { defaultPluginId = PropertyUtil.getValue("CAREWEB.INITIAL.SECTION", null); if (defaultPluginId == null) { defaultPluginId = ""; } } catch (Exception e) { defaultPluginId = ""; } } return defaultPluginId; } /** * Returns the path resolver implementation. This implementation determines where in the layout * the plugin should be placed based on a path. A default implementation is provided. * * @return The path resolver. */ public PathResolver getPathResolver() { if (pathResolver == null) { pathResolver = new PathResolver(UIElementTreeView.class, UIElementTreePane.class); } return pathResolver; } /** * Sets the path resolver implementation. This must be set before any resources are registered. * * @param pathResolver The path resolver. */ public void setPathResolver(PathResolver pathResolver) { if (this.pathResolver != null) { throw new UIException("A path resolver can only be set once."); } this.pathResolver = pathResolver; } }