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

文前说明

作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。

分析整理的版本为 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 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 定义。


    WebAdminHostPageServlet
    /WebAdmin.html



    WebAdminHostPageServlet
    org.ovirt.engine.ui.frontend.server.gwt.WebAdminHostPageServlet

  • WebAdminHostPageServlet 类中读取插件配置信息
List pluginData = getPluginData();
request.setAttribute(ATTR_PLUGIN_DEFS, getPluginDefinitionsArray(pluginData));

protected List getPluginData() {
        List currentData = new ArrayList(
                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 currentDataMapSnapshot = dataMapRef.get();

        // Create a local working copy of current data mappings (avoid modifying 'live' data)
        Map currentDataMapCopy = new HashMap(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 currentDataMapCopy) {
        Map entriesToUpdate = new HashMap();
        Set keysToRemove = new HashSet();

        // Optimization: make sure we don't check data that we already processed
        Set keysToCheckForRemoval = new HashSet(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) {
        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 访问浏览器窗口和文档对象时,必须分别用 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 访问域的域名(字段名)
// 对象实例.@全路径类名::域名(字段)
[email protected]::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

// 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) {
        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 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 = [email protected]::uiFunctions;
        var user = [email protected]::user;

        var canDoPluginAction = function(pluginName) {
            return [email protected]::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) {
                [email protected]::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) {
                [email protected]::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() {
                [email protected]::pluginReady(Ljava/lang/String;)(this.pluginName);
            },

            // Returns the configuration object associated with the plugin
            configObject: function() {
                return [email protected]::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 [email protected]::getFullUserName()();
                }
            },
            loginUserId: function() {
                if (canDoPluginAction(this.pluginName)) {
                    return [email protected]::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) {
                [email protected]::registerPluginEventHandlerObject(Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)(this.pluginName,sanitizeObject(eventHandlerObject));
},
......

// Indicates that the plugin is ready for use
ready: function() {
            [email protected]::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 的实现分析与整理)