文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
分析整理的版本为 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 方法中定义 register 和 ready 方法。(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 方法中已经被定义。
- 即调用 PluginUiFunctions 的 addMainTab 方法。
// 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;
}
};
}]);