美文网首页Ovirt程序员
【Ovirt 笔记】Plugin 的实现分析与整理

【Ovirt 笔记】Plugin 的实现分析与整理

作者: 58bc06151329 | 来源:发表于2018-06-14 09:02 被阅读8次

    分析整理的版本为 Ovirt 3.4.5 版本。

    • 插件 Plugin 的实现,主要通过类 PluginManager 实现。
    • 管理门户 GWT 界面 注入机制将 PluginManager 对象注入到 MainSectionPresenter 中。
    • MainSectionView 界面的加载触发 onReveal 的执行,从而调用 pluginManager.enablePluginInvocation() 方法的执行。

    1. PluginManager 对象的创建

    • GWTP 的 @Inject 注解模式,构建 PluginManager。
    @Inject
    public PluginManager(PluginUiFunctions uiFunctions, CurrentUser user) {
         this.uiFunctions = uiFunctions;
         this.user = user;
         exposePluginApi();
         defineAndLoadPlugins();
    }
    
    • defineAndLoadPlugins 方法中实例化了 PluginDefinitions 插件配置对象。
    void defineAndLoadPlugins() {
    PluginDefinitions definitions = PluginDefinitions.instance();
    
            if (definitions != null) {
                JsArray<PluginMetaData> metaDataArray = definitions.getMetaDataArray();
    
                for (int i = 0; i < metaDataArray.length(); i++) {
                    PluginMetaData pluginMetaData = metaDataArray.get(i);
    
                    if (pluginMetaData != null) {
                        defineAndLoadPlugin(pluginMetaData);
                    }
                }
            }
    }
    

    1.1 PluginDefinitions 插件配置信息读入

    • 访问管理员门户的 Servlet 定义。
    <!-- webadmin/src/main/webapp/WEB-INF/web.xml 中定义 -->
    <servlet-mapping>
        <servlet-name>WebAdminHostPageServlet</servlet-name>
        <url-pattern>/WebAdmin.html</url-pattern>
    </servlet-mapping>
    
    <!-- frontend/src/main/resources/META-INF/web-fragment.xml -->
    <servlet>
        <servlet-name>WebAdminHostPageServlet</servlet-name>
        <servlet-class>org.ovirt.engine.ui.frontend.server.gwt.WebAdminHostPageServlet</servlet-class>
    </servlet>
    
    • WebAdminHostPageServlet 类中读取插件配置信息
    List<PluginData> pluginData = getPluginData();
    request.setAttribute(ATTR_PLUGIN_DEFS, getPluginDefinitionsArray(pluginData));
    
    protected List<PluginData> getPluginData() {
            List<PluginData> currentData = new ArrayList<PluginData>(
                    PluginDataManager.getInstance().reloadAndGetCurrentData());
            Collections.sort(currentData);
            return currentData;
    }
    

    1.1.1 PluginDataManager 插件数据信息读入

    • 实例化 PluginDataManager
    private static class Holder {
        private static final PluginDataManager INSTANCE = new PluginDataManager(PluginDataManager.resolvePluginDataPath(), PluginDataManager.resolvePluginConfigPath());
    }
    
    public static PluginDataManager getInstance() {
       return Holder.INSTANCE;
    }
    
    • 解析插件的配置文件
      • 计划任务插件功能解析的配置文件为 /usr/share/ovirt-engine/ui-plugins/scheduletasks.json
      • 域插件功能解析的配置文件为 /usr/share/ovirt-engine/ui-plugins/emdplugin.json
      • 解析配置数据封装在 PluginData 对象中。
    public void reloadData() {
            // Get a snapshot of current data mappings
            Map<String, PluginData> currentDataMapSnapshot = dataMapRef.get();
    
            // Create a local working copy of current data mappings (avoid modifying 'live' data)
            Map<String, PluginData> currentDataMapCopy = new HashMap<String, PluginData>(currentDataMapSnapshot);
    
            File[] descriptorFiles = pluginDataDir.listFiles(new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    return isJsonFile(pathname);
                }
            });
    
            if (descriptorFiles == null) {
                logger.warn("Cannot list UI plugin descriptor files in [" + pluginDataDir.getAbsolutePath() + "]"); //$NON-NLS-1$ //$NON-NLS-2$
                return;
            }
    
            // Reload descriptor/configuration data
            reloadData(descriptorFiles, currentDataMapCopy);
    
            // Apply changes through reference assignment
            if (!dataMapRef.compareAndSet(currentDataMapSnapshot, currentDataMapCopy)) {
                logger.warn("It seems that UI plugin data has changed, please reload WebAdmin application"); //$NON-NLS-1$
            }
        }
    
        void reloadData(File[] descriptorFiles, Map<String, PluginData> currentDataMapCopy) {
            Map<String, PluginData> entriesToUpdate = new HashMap<String, PluginData>();
            Set<String> keysToRemove = new HashSet<String>();
    
            // Optimization: make sure we don't check data that we already processed
            Set<String> keysToCheckForRemoval = new HashSet<String>(currentDataMapCopy.keySet());
    
            // Compare (possibly added or modified) files against cached data
            for (final File df : descriptorFiles) {
                final File cf = new File(pluginConfigDir, getConfigurationFileName(df));
    
                String descriptorFilePath = df.getAbsolutePath();
                PluginData currentData = currentDataMapCopy.get(descriptorFilePath);
    
                long descriptorLastModified = df.lastModified();
                long configurationLastModified = isJsonFile(cf) ? cf.lastModified() : MISSING_FILE_LAST_MODIFIED;
    
                // Check if data needs to be reloaded
                boolean reloadDescriptor, reloadConfiguration;
                if (currentDataMapCopy.containsKey(descriptorFilePath)) {
                    reloadDescriptor = descriptorLastModified > currentData.getDescriptorLastModified();
                    reloadConfiguration = configurationLastModified > currentData.getConfigurationLastModified();
    
                    // Change in descriptor causes reload of configuration
                    reloadConfiguration |= reloadDescriptor;
    
                    // Refresh configuration if the corresponding file has gone missing
                    reloadConfiguration |= (configurationLastModified == MISSING_FILE_LAST_MODIFIED
                            && currentData.getConfigurationLastModified() != MISSING_FILE_LAST_MODIFIED);
                } else {
                    reloadDescriptor = true;
                    reloadConfiguration = true;
                }
    
                // Read descriptor data
                JsonNode descriptorNode = currentData != null ? currentData.getDescriptorNode() : null;
                if (reloadDescriptor) {
                    logger.info("Reading UI plugin descriptor [" + df.getAbsolutePath() + "]"); //$NON-NLS-1$ //$NON-NLS-2$
                    descriptorNode = readJsonNode(df);
                    if (descriptorNode == null) {
                        // Failed to read descriptor data, nothing we can do about it
                        continue;
                    }
                } else if (descriptorNode == null) {
                    logger.warn("UI plugin descriptor node is null for [" + df.getAbsolutePath() + "]"); //$NON-NLS-1$ //$NON-NLS-2$
                    continue;
                }
    
                // Read configuration data
                JsonNode configurationNode = currentData != null ? currentData.getConfigurationNode() : null;
                if (reloadConfiguration) {
                    logger.info("Reading UI plugin configuration [" + cf.getAbsolutePath() + "]"); //$NON-NLS-1$ //$NON-NLS-2$
                    configurationNode = readConfigurationNode(cf);
                    if (configurationNode == null) {
                        // Failed to read configuration data, use empty object
                        configurationNode = createEmptyObjectNode();
                    }
                } else if (configurationNode == null) {
                    logger.warn("UI plugin configuration node is null for [" + cf.getAbsolutePath() + "]"); //$NON-NLS-1$ //$NON-NLS-2$
                    continue;
                }
    
                // Update data
                if (reloadDescriptor || reloadConfiguration) {
                    PluginData newData = new PluginData(descriptorNode, descriptorLastModified,
                            configurationNode, configurationLastModified, mapper.getNodeFactory());
    
                    // Validate data
                    boolean dataValid = newData.validate(new PluginData.ValidationCallback() {
                        @Override
                        public void descriptorError(String message) {
                            logger.warn("Validation error in [" + df.getAbsolutePath() + "]: " + message); //$NON-NLS-1$ //$NON-NLS-2$
                        }
    
                        @Override
                        public void configurationError(String message) {
                            logger.warn("Validation error in [" + cf.getAbsolutePath() + "]: " + message); //$NON-NLS-1$ //$NON-NLS-2$
                        }
                    });
                    if (!dataValid) {
                        // Data validation failed, nothing we can do about it
                        continue;
                    }
    
                    entriesToUpdate.put(descriptorFilePath, newData);
                }
    
                keysToCheckForRemoval.remove(descriptorFilePath);
            }
    
            // Compare cached data against (possibly missing) files
            for (String descriptorFilePath : keysToCheckForRemoval) {
                File df = new File(descriptorFilePath);
    
                if (!df.exists()) {
                    // Descriptor data file has gone missing
                    keysToRemove.add(descriptorFilePath);
                }
            }
    
            // Perform data updates
            currentDataMapCopy.putAll(entriesToUpdate);
            currentDataMapCopy.keySet().removeAll(keysToRemove);
    }
    

    1.1.2 将解析结果设置到页面变量 pluginDefinitions 中

    protected static final String ATTR_PLUGIN_DEFS = "pluginDefinitions";
    
    request.setAttribute(ATTR_PLUGIN_DEFS, getPluginDefinitionsArray(pluginData));
    
    protected ArrayNode getPluginDefinitionsArray(List<PluginData> pluginData) {
            ArrayNode arr = createArrayNode();
            for (PluginData data : pluginData) {
                ObjectNode dataObj = createObjectNode();
                dataObj.put("name", data.getName()); //$NON-NLS-1$
                dataObj.put("url", data.getUrl()); //$NON-NLS-1$
                dataObj.put("config", data.mergeConfiguration()); //$NON-NLS-1$
                dataObj.put("enabled", data.isEnabled()); //$NON-NLS-1$
                arr.add(dataObj);
            }
            return arr;
    }
    

    1.1.3 通过定向访问将获取的信息放到页面 GwtHostPage.jsp 中

    private static final String HOST_JSP = "/GwtHostPage.jsp";
    
    protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException,
            ServletException {
    
            // Set attribute for selector script
            request.setAttribute(MD5Attributes.ATTR_SELECTOR_SCRIPT.getKey(), getSelectorScriptName());
            // Set the messages that need to be replaced.
            request.setAttribute(MD5Attributes.ATTR_MESSAGES.getKey(),
                    getBrandingMessages(getApplicationTypeFromRequest(request), getLocaleFromRequest(request)));
            request.setAttribute(MD5Attributes.ATTR_BASE_CONTEXT_PATH.getKey(), getConfiguration(request));
            // Set attribute for userInfo object
            DbUser loggedInUser = getLoggedInUser(request.getSession().getId());
            if (loggedInUser != null) {
                request.setAttribute(MD5Attributes.ATTR_USER_INFO.getKey(), getUserInfoObject(loggedInUser));
            }
    
            try {
                // Calculate MD5 for use with If-None-Match request header
                String md5sum = getMd5Sum(request);
    
                if (request.getHeader(IF_NONE_MATCH_HEADER) != null
                        && request.getHeader(IF_NONE_MATCH_HEADER).equals(md5sum)) {
                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                } else {
                    RequestDispatcher dispatcher = request.getRequestDispatcher(HOST_JSP);
                    response.setContentType(UTF_CONTENT_TYPE);
                    response.addHeader(ETAG_HEADER, md5sum);
                    if (dispatcher != null) {
                        dispatcher.include(request, response);
                    }
                }
            } catch (NoSuchAlgorithmException ex) {
                throw new ServletException(ex);
            }
    }
    

    1.1.4 Javascript 本地接口(JSNI)

    • GWT 提供了 JSNI(JavaScript Native Interface)Javascript 本地接口实现 Java 与 Javascript 交互。
    • JSNI 可以实现以下功能
      • 从 JavaScript 中直接执行 Java 方法。
      • 从 JavaScript 中调用 Java 代码,或从 Java 代码中调用 JavaScript 代码。
      • 在 Java 和 Javascript 之间抛异常。
      • 在 JavaScript 中读写 Java 域属性。
    1.1.4.1 声明一个本地方法
    • 在 JSNI 中声明一个本地方法时,使用 Java 的标准 native 关键字。
    • 在 JSNI 中,本地 JavaScript 代码用一种特殊的注释格式直接嵌入到 Java 源代码中。
    • JSNI 注释块以 " /*-{ " 开头,以 " }-*/ " 结束。
    • 当用 JSNI 访问浏览器窗口和文档对象时,必须分别用 $wnd$doc 引用。
    public class Alert{
      public static native void alert(String msg) /*-{
        $wnd.alert(msg);
      }-*/;
    }
    
    1.1.4.2 工作机制
    • 在 WEB 模式中,GWT 编译器把客户端一半的 Java 程序转换成 JavaScript,正常情况,当编译器看到方法声明时,其括号内部的代码必须经历解释过程。
    • 如果方法是本地方法,编译器处理更为简单,直接复制 JavaScript 本地代码到已编译的结果中,一旦 JavaScript 被解释后,JavaScript 代码中的任何错误就只能在运行时才能发现。
    1.1.4.3 Java 通过 JSNI 调用内部 Javascript
    • 在 Java 中调用 JSNI 与调用其它 Java 方法没有什么不同。
    public class Alert{
      public static native void alert(String msg) /*-{
        $wnd.alert(msg);
      }-*/;
    }
    
    button.addClickListener(new ClickListener(){
      public void onClick(Widget sender){
        Alert.alert("Clicked!");
      }
    });
    
    1.1.4.4 内部 Javascript 通过 JSNI 调用 Java
    • 假定传递一个对象到 JSNI 方法,需要访问对象的一个域(字段)或执行对象中的一个方法。
    • 访问 Java 对象域(字段) obj.@class::field
    属性 含义
    obj 是引用对象的实例
    class 是具有 full-qualified 的类
    field 访问域的域名(字段名)
    // 对象实例.@全路径类名::域名(字段)
    user.@org.ovirt.engine.ui.common.auth.CurrentUser::userId
    
    • 调用 Java 方法的语法和访问 Java 域的语法相似。obj.@class::method(sig)(args)
    属性 含义
    obj 是引用对象的实例
    class 是具有 full-qualified 的类
    method 调用方法的方法名
    sig 内部的 Java 方法签名
    args 方法传递的参数列表
    // 对象实例.@全路径类名::静态方法名(方法签名)(传入参数)
    uiFunctions.@org.ovirt.engine.ui.webadmin.plugin.api.PluginUiFunctions::addMainTab(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/ovirt/engine/ui/webadmin/plugin/api/TabOptions;)(label,historyToken,contentUrl,sanitizeObject(options))
    
    1.1.4.5 方法签名
    • JSNI 方法签名确切地说和 JNI 方法签名一样,除了方法返回类型有所不同。
    Type Signature Java Type
    Z boolean
    B byte
    C char
    S short
    I int
    J long
    F float
    D double
    [type type[]
    Lfully-qualified-class; fully-qualified-class

    例如:

    void function(int n, String s, int[] arr)
    

    签名:

    function(ILjava/lang/String;[I)
    
    1.1.4.6 GWT 中 java 方法调用外部 Javascript
    // GwtHostPage.jsp
    <script type="text/javascript">
         var pluginDefinitions = <c:out value="${requestScope['pluginDefinitions']}" escapeXml="false"/>;
    </script>
    
    // java
    public static native PluginDefinitions instance() /*-{
        return $wnd.pluginDefinitions;
    }-*/;
    
    1.1.4.7 外部 Javascript 调用 GWT 的 Java 方法
    // java
    addMainTab: function(label, historyToken, contentUrl, options) {
           if (canDoPluginAction(this.pluginName)) {
                  uiFunctions.@org.ovirt.engine.ui.webadmin.plugin.api.PluginUiFunctions::addMainTab(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/ovirt/engine/ui/webadmin/plugin/api/TabOptions;)(label,historyToken,contentUrl,sanitizeObject(options));
           }
    },
    
    // javascript
    pluginApi.addMainTab(trans.TAB_NAME, 'jhrw-tab', urlUtil.relativeUrl('tab.html'));
    

    1.1.5 PluginDefinitions 通过 JSNI 与 Javascript 交互

    • 通过与 Javascript 交互,获取 PluginDefinitions 对象。
      • 获取 GwtHostPage.jsp 文件中 Javascript 代码中的 PluginDefinitions 对象。
      • 这个 PluginDefinitions 对象就是之前解析配置文件组装的对象。
      • getMetaDataArray 方法获取的是 ArrayNode 对象。
    public static native PluginDefinitions instance() /*-{
        return $wnd.pluginDefinitions;
    }-*/;
    
    protected ArrayNode getPluginDefinitionsArray(List<PluginData> pluginData) {
            ArrayNode arr = createArrayNode();
            for (PluginData data : pluginData) {
                ObjectNode dataObj = createObjectNode();
                dataObj.put("name", data.getName()); //$NON-NLS-1$
                dataObj.put("url", data.getUrl()); //$NON-NLS-1$
                dataObj.put("config", data.mergeConfiguration()); //$NON-NLS-1$
                dataObj.put("enabled", data.isEnabled()); //$NON-NLS-1$
                arr.add(dataObj);
            }
            return arr;
    }
    

    1.2 PluginManager 构造函数中将所有读取的插件信息放在 plugins 中

    void defineAndLoadPlugins() {
            PluginDefinitions definitions = PluginDefinitions.instance();
    
            if (definitions != null) {
                JsArray<PluginMetaData> metaDataArray = definitions.getMetaDataArray();
    
                for (int i = 0; i < metaDataArray.length(); i++) {
                    PluginMetaData pluginMetaData = metaDataArray.get(i);
    
                    if (pluginMetaData != null) {
                        defineAndLoadPlugin(pluginMetaData);
                    }
                }
            }
    }
    
    void defineAndLoadPlugin(PluginMetaData pluginMetaData) {
            String pluginName = pluginMetaData.getName();
            String pluginHostPageUrl = pluginMetaData.getHostPageUrl();
    
            if (pluginName == null || pluginName.trim().isEmpty()) {
                logger.warning("Plugin name cannot be null or empty"); //$NON-NLS-1$
                return;
            } else if (pluginHostPageUrl == null || pluginHostPageUrl.trim().isEmpty()) {
                logger.warning("Plugin [" + pluginName + "] has null or empty host page URL"); //$NON-NLS-1$ //$NON-NLS-2$
                return;
            } else if (getPlugin(pluginName) != null) {
                logger.warning("Plugin [" + pluginName + "] is already defined"); //$NON-NLS-1$ //$NON-NLS-2$
                return;
            }
    
            // Create an iframe element used to load the plugin host page
            IFrameElement iframe = Document.get().createIFrameElement();
            iframe.setSrc(pluginHostPageUrl);
            iframe.setFrameBorder(0);
            iframe.getStyle().setPosition(Position.ABSOLUTE);
            iframe.getStyle().setWidth(0, Unit.PT);
            iframe.getStyle().setHeight(0, Unit.PT);
            iframe.getStyle().setBorderStyle(BorderStyle.NONE);
    
            Plugin plugin = new Plugin(pluginMetaData, iframe);
            addPlugin(plugin);
            logger.info("Plugin [" + pluginName + "] is defined to be loaded from URL " + pluginHostPageUrl); //$NON-NLS-1$ //$NON-NLS-2$
    
            if (pluginMetaData.isEnabled()) {
                loadPlugin(plugin);
            }
    }
    
    void addPlugin(Plugin plugin) {
            plugins.put(plugin.getName(), plugin);
    }
    
    • Plugin 对象中包含了 Javascript 对象 eventHandlerObject 后续主要用于 Javascript 中方法调用。
    private JavaScriptObject eventHandlerObject;
    
    public void setEventHandlerObject(JavaScriptObject eventHandlerObject) {
            this.eventHandlerObject = eventHandlerObject;
    }
    

    2. Plugin 插件选项卡的加载

    • pluginManager.enablePluginInvocation() 方法的执行。
      • 调度执行插件的初始化。
    public void enablePluginInvocation() {
            canInvokePlugins = true;
    
            // Try to initialize all plugins after the browser event loop returns
            Scheduler.get().scheduleDeferred(new ScheduledCommand() {
                @Override
                public void execute() {
                    for (Plugin plugin : getPlugins()) {
                        initPlugin(plugin);
                    }
                }
            });
    }
    
    void initPlugin(Plugin plugin) {
            if (!canInvokePlugins) {
                return;
            }
    
            String pluginName = plugin.getName();
    
            // Try to invoke UiInit event handler function
            if (plugin.isInState(PluginState.READY)) {
                logger.info("Initializing plugin [" + pluginName + "]"); //$NON-NLS-1$ //$NON-NLS-2$
                plugin.markAsInitializing();
    
                if (invokePlugin(plugin, "UiInit", null)) { //$NON-NLS-1$
                    plugin.markAsInUse();
                    logger.info("Plugin [" + pluginName + "] is initialized and in use now"); //$NON-NLS-1$ //$NON-NLS-2$
                }
            }
    
            // Try to invoke all event handler functions scheduled for this plugin
            if (plugin.isInState(PluginState.IN_USE)) {
                invokeScheduledFunctionCommands(pluginName);
            }
    }
    
    • 执行绑定对象的 UiInit 方法。
      • 插件初始化时进行了调用。
    public JsFunction getEventHandlerFunction(String functionName) {
         return JsFunction.get(eventHandlerObject, functionName);
    }
    
    boolean invokePlugin(final Plugin plugin, final String functionName, JsArray<?> functionArgs) {
            final String pluginName = plugin.getName();
            logger.info("Invoking event handler function [" + functionName + "] for plugin [" + pluginName + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    
            return plugin.getEventHandlerFunction(functionName).invoke(functionArgs, new ErrorHandler() {
                @Override
                public void onError(String message) {
                    logger.severe("Exception caught while invoking event handler function [" + functionName //$NON-NLS-1$
                            + "] for plugin [" + pluginName + "]: " + message); //$NON-NLS-1$ //$NON-NLS-2$
    
                    // Remove the given plugin from service
                    Document.get().getBody().removeChild(plugin.getIFrameElement());
                    plugin.markAsFailed();
                    logger.warning("Plugin [" + pluginName + "] removed from service due to failure"); //$NON-NLS-1$ //$NON-NLS-2$
                }
            });
    }
    

    2.1 eventHandlerObject 对象的绑定

    • 每一个插件项目都是通过 AngularJs 工程实现。
      • 计划任务插件工程在 /usr/share/ovirt-engine/ui-plugins/scheduletasks-resources/ 目录中。
      • 域插件工程在 /usr/share/ovirt-engine/ui-plugins/emdplugin-resources 目录中。
    • PluginManager 构造函数中执行 exposePluginApi 从而暴露插件工程的 API。
      • 通过此方法创建 Javascript 对象 pluginApi。
    private native void exposePluginApi() /*-{
            var ctx = this;
            var uiFunctions = ctx.@org.ovirt.engine.ui.webadmin.plugin.PluginManager::uiFunctions;
            var user = ctx.@org.ovirt.engine.ui.webadmin.plugin.PluginManager::user;
    
            var canDoPluginAction = function(pluginName) {
                return ctx.@org.ovirt.engine.ui.webadmin.plugin.PluginManager::canDoPluginAction(Ljava/lang/String;)(pluginName);
            };
    
            var getEntityType = function(entityTypeName) {
                return @org.ovirt.engine.ui.webadmin.plugin.entity.EntityType::from(Ljava/lang/String;)(entityTypeName);
            };
    
            var sanitizeObject = function(object) {
                return (object != null) ? object : {};
            };
    
            // Define pluginApi function used to construct specific Plugin API instances
            var pluginApi = function(pluginName) {
                return new pluginApi.fn.init(pluginName);
            };
    
            // Define pluginApi.fn as an alias to pluginApi prototype
            pluginApi.fn = pluginApi.prototype = {
    
                pluginName: null, // Initialized in constructor function
    
                // Constructor function
                init: function(pluginName) {
                    this.pluginName = pluginName;
                    return this;
                },
    
                // Registers plugin event handler functions for later invocation
                register: function(eventHandlerObject) {
                    ctx.@org.ovirt.engine.ui.webadmin.plugin.PluginManager::registerPluginEventHandlerObject(Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)(this.pluginName,sanitizeObject(eventHandlerObject));
                },
    
                // Registers custom API options object associated with the plugin
                options: function(apiOptionsObject) {
                    ctx.@org.ovirt.engine.ui.webadmin.plugin.PluginManager::registerPluginApiOptionsObject(Ljava/lang/String;Lorg/ovirt/engine/ui/webadmin/plugin/api/ApiOptions;)(this.pluginName,sanitizeObject(apiOptionsObject));
                },
    
                // Indicates that the plugin is ready for use
                ready: function() {
                    ctx.@org.ovirt.engine.ui.webadmin.plugin.PluginManager::pluginReady(Ljava/lang/String;)(this.pluginName);
                },
    
                // Returns the configuration object associated with the plugin
                configObject: function() {
                    return ctx.@org.ovirt.engine.ui.webadmin.plugin.PluginManager::getConfigObject(Ljava/lang/String;)(this.pluginName);
                },
    
                // TODO(vszocs) inject API functions into "pluginApi.fn" dynamically using EventBus
                addMainTab: function(label, historyToken, contentUrl, options) {
                    if (canDoPluginAction(this.pluginName)) {
                        uiFunctions.@org.ovirt.engine.ui.webadmin.plugin.api.PluginUiFunctions::addMainTab(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/ovirt/engine/ui/webadmin/plugin/api/TabOptions;)(label,historyToken,contentUrl,sanitizeObject(options));
                    }
                },
                addSubTab: function(entityTypeName, label, historyToken, contentUrl, options) {
                    if (canDoPluginAction(this.pluginName)) {
                        uiFunctions.@org.ovirt.engine.ui.webadmin.plugin.api.PluginUiFunctions::addSubTab(Lorg/ovirt/engine/ui/webadmin/plugin/entity/EntityType;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/ovirt/engine/ui/webadmin/plugin/api/TabOptions;)(getEntityType(entityTypeName),label,historyToken,contentUrl,sanitizeObject(options));
                    }
                },
                setTabContentUrl: function(historyToken, contentUrl) {
                    if (canDoPluginAction(this.pluginName)) {
                        uiFunctions.@org.ovirt.engine.ui.webadmin.plugin.api.PluginUiFunctions::setTabContentUrl(Ljava/lang/String;Ljava/lang/String;)(historyToken,contentUrl);
                    }
                },
                setTabAccessible: function(historyToken, tabAccessible) {
                    if (canDoPluginAction(this.pluginName)) {
                        uiFunctions.@org.ovirt.engine.ui.webadmin.plugin.api.PluginUiFunctions::setTabAccessible(Ljava/lang/String;Z)(historyToken,tabAccessible);
                    }
                },
                addMainTabActionButton: function(entityTypeName, label, actionButtonInterface) {
                    if (canDoPluginAction(this.pluginName)) {
                        uiFunctions.@org.ovirt.engine.ui.webadmin.plugin.api.PluginUiFunctions::addMainTabActionButton(Lorg/ovirt/engine/ui/webadmin/plugin/entity/EntityType;Ljava/lang/String;Lorg/ovirt/engine/ui/webadmin/plugin/api/ActionButtonInterface;)(getEntityType(entityTypeName),label,sanitizeObject(actionButtonInterface));
                    }
                },
                addSubTabActionButton: function(mainTabEntityTypeName, subTabEntityTypeName, label, actionButtonInterface) {
                    if (canDoPluginAction(this.pluginName)) {
                        uiFunctions.@org.ovirt.engine.ui.webadmin.plugin.api.PluginUiFunctions::addSubTabActionButton(Lorg/ovirt/engine/ui/webadmin/plugin/entity/EntityType;Lorg/ovirt/engine/ui/webadmin/plugin/entity/EntityType;Ljava/lang/String;Lorg/ovirt/engine/ui/webadmin/plugin/api/ActionButtonInterface;)(getEntityType(mainTabEntityTypeName),getEntityType(subTabEntityTypeName),label,sanitizeObject(actionButtonInterface));
                    }
                },
                showDialog: function(title, dialogToken, contentUrl, width, height, options) {
                    if (canDoPluginAction(this.pluginName)) {
                        uiFunctions.@org.ovirt.engine.ui.webadmin.plugin.api.PluginUiFunctions::showDialog(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/ovirt/engine/ui/webadmin/plugin/api/DialogOptions;)(title,dialogToken,contentUrl,width,height,sanitizeObject(options));
                    }
                },
                setDialogContentUrl: function(dialogToken, contentUrl) {
                    if (canDoPluginAction(this.pluginName)) {
                        uiFunctions.@org.ovirt.engine.ui.webadmin.plugin.api.PluginUiFunctions::setDialogContentUrl(Ljava/lang/String;Ljava/lang/String;)(dialogToken,contentUrl);
                    }
                },
                closeDialog: function(dialogToken) {
                    if (canDoPluginAction(this.pluginName)) {
                        uiFunctions.@org.ovirt.engine.ui.webadmin.plugin.api.PluginUiFunctions::closeDialog(Ljava/lang/String;)(dialogToken);
                    }
                },
                loginUserName: function() {
                    if (canDoPluginAction(this.pluginName)) {
                        return user.@org.ovirt.engine.ui.common.auth.CurrentUser::getFullUserName()();
                    }
                },
                loginUserId: function() {
                    if (canDoPluginAction(this.pluginName)) {
                        return user.@org.ovirt.engine.ui.common.auth.CurrentUser::getUserId()();
                    }
                }
    
            };
    
            // Give init function the pluginApi prototype for later instantiation
            pluginApi.fn.init.prototype = pluginApi.fn;
    
            // Expose pluginApi function as a global object
            $wnd.pluginApi = pluginApi;
    }-*/;
    
    • 注入 pluginApi 对象。
      • 文件 /usr/share/ovirt-engine/ui-plugins/scheduletasks-resources/js/common.js 中实现。
      • 返回的实际就是 exposePluginApi 方法中创建的 pluginApi 对象。
    // javascript
    app.factory('pluginApi', ['$window', 'pluginName', function ($window, pluginName) {
          return $window.parent.pluginApi(pluginName);
    }]);
    
    • 初始化插件服务
    // javascript
    app.factory('initService', ['pluginApi', 'pluginEventHandlers', function (pluginApi, pluginEventHandlers) {
          return {
             bootstrapPlugin: function () {
    
              // Get the config from the file to setup the api plugin
              var config = pluginApi.configObject();
              pluginApi.options(config.allowedMessageOriginsJSON);
    
              pluginApi.register(pluginEventHandlers);
              pluginApi.ready();
            }
          };
       }]);
    
    app.run(['initService', function (initService) {
        initService.bootstrapPlugin();
    }]);
    
    • 注册插件事件
      • exposePluginApi 方法中定义 registerready 方法。(Java)
      • register 注册的就是组件 pluginEventHandlers。(Javascript)
      • 通过 plugin.setEventHandlerObject 将组件与 Plugin 对象进行了关联。(Java)
    // javascript
    app.factory('pluginEventHandlers', ['pluginName', 'pluginApi', 'tabManager', 'contentWindowService', function (pluginName, pluginApi, tabManager, contentWindow) {
          return {
             UiInit: function () {
                tabManager.addTab();
             },
    ......
    
    pluginApi.register(pluginEventHandlers);
    
    // java
    register: function(eventHandlerObject) {
                    ctx.@org.ovirt.engine.ui.webadmin.plugin.PluginManager::registerPluginEventHandlerObject(Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)(this.pluginName,sanitizeObject(eventHandlerObject));
    },
    ......
    
    // Indicates that the plugin is ready for use
    ready: function() {
                ctx.@org.ovirt.engine.ui.webadmin.plugin.PluginManager::pluginReady(Ljava/lang/String;)(this.pluginName);
    },
    ......
    
    // java
    void registerPluginEventHandlerObject(String pluginName, JavaScriptObject eventHandlerObject) {
            Plugin plugin = getPlugin(pluginName);
            if (plugin == null || eventHandlerObject == null) {
                return;
            }
    
            // Allow plugin event handler object to be set only once
            if (plugin.getEventHandlerObject() == null) {
                plugin.setEventHandlerObject(eventHandlerObject);
                logger.info("Plugin [" + pluginName + "] has registered the event handler object"); //$NON-NLS-1$ //$NON-NLS-2$
            } else {
                logger.warning("Plugin [" + pluginName + "] has already registered the event handler object"); //$NON-NLS-1$ //$NON-NLS-2$
            }
    }
    
    void pluginReady(String pluginName) {
            Plugin plugin = getPlugin(pluginName);
    
            if (plugin != null && plugin.isInState(PluginState.LOADING)) {
                if (plugin.getEventHandlerObject() == null) {
                    logger.warning("Plugin [" + pluginName //$NON-NLS-1$
                            + "] reports in as ready, but has no event handler object assigned"); //$NON-NLS-1$
                    return;
                }
    
                plugin.markAsReady();
                logger.info("Plugin [" + pluginName + "] reports in as ready"); //$NON-NLS-1$ //$NON-NLS-2$
    
                // Try to initialize the plugin, since the plugin might report in as ready
                // after WebAdmin enters the state that allows plugins to be invoked
                initPlugin(plugin);
            }
    }
    
    • pluginEventHandlers 组件中定义了 UiInit 方法。
      • pluginManager.enablePluginInvocation() 初始化方法中也就被执行了。
      • /usr/share/ovirt-engine/ui-plugins/scheduletasks-resources/js/plugin.js 文件中定义。
    // javascript
    app.factory('pluginEventHandlers', ['pluginName', 'pluginApi', 'tabManager', 'contentWindowService', function (pluginName, pluginApi, tabManager, contentWindow) {
          return {
             UiInit: function () {
                tabManager.addTab();
             },
    
    • tabManager 同样被定义为组件。
      • tabManager.addTab 方法,实际执行 pluginApi.addMainTab 方法。
    // javascript
    app.factory('tabManager', ['pluginApi', 'urlUtil', 'translationService', function (pluginApi, urlUtil, translationService) {
          return {
             addTab: function () {
                var  trans = translationService.translate();
    
                pluginApi.addMainTab(trans.TAB_NAME, 'jhrw-tab', urlUtil.relativeUrl('tab.html'));
             }
          };
    }]);
    
    • pluginApi.addMainTab 方法在 exposePluginApi 方法中已经被定义。
      • 即调用 PluginUiFunctionsaddMainTab 方法。
    // java
    addMainTab: function(label, historyToken, contentUrl, options) {
                    if (canDoPluginAction(this.pluginName)) {
                        uiFunctions.@org.ovirt.engine.ui.webadmin.plugin.api.PluginUiFunctions::addMainTab(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/ovirt/engine/ui/webadmin/plugin/api/TabOptions;)(label,historyToken,contentUrl,sanitizeObject(options));
                    }
                },
    ......
    
    public void addMainTab(String label, String historyToken,
                String contentUrl, TabOptions options) {
                addTab(MainTabPanelPresenter.TYPE_RequestTabs,
                        MainTabPanelPresenter.TYPE_ChangeTab,
                        MainTabPanelPresenter.TYPE_SetTabContent,
                        label, historyToken, true, contentUrl, options);
    }
    
    • 定义选项卡信息。
      • 因此登录管理门户在一级选项卡目录可以看到 计划任务 插件选项卡。
      • 选择 计划任务 选项卡时,访问的就是 tab.html 页面。
      • 插件界面中自身操作在 Javascript 工程中自行实现即可。
      • 插件工程实现界面包含在 IFrame 中。
    // /usr/share/ovirt-engine/ui-plugins/scheduletasks-resources/js/plugin.js
    pluginApi.addMainTab(trans.TAB_NAME, 'jhrw-tab', urlUtil.relativeUrl('tab.html'));
    
    // /usr/share/ovirt-engine/ui-plugins/scheduletasks-resources/js/translations.js
    "TAB_NAME" : "计划任务",
    
    // /usr/share/ovirt-engine/ui-plugins/scheduletasks-resources/js/common.js
    app.factory('urlUtil', ['pluginName', function (pluginName) {
         return {
             relativeUrl: function (path) {
                return 'plugin/' + pluginName + '/' + path;
             }
          };
    }]);
    

    相关文章

      网友评论

        本文标题:【Ovirt 笔记】Plugin 的实现分析与整理

        本文链接:https://www.haomeiwen.com/subject/gwnwxftx.html