#dominoforever
关注我!会有很多干货哟!
另一篇文章:源码级分析!解构HCL Domino Volt低代码工具(一)今天进入我们这个技术文章系列的重头戏!我们来分析一下Volt的源代码。我们是怎么得到Volt的源代码的呢?来源有两个:
Volt应用数据库volt/.nsf中的“资源-文件”。这些文件是Volt应用运行时所需的一些Web资源,例如图片、js,json、css、html等等。获取这些代码很容易,会使用Domino Designer的技术人员很容易获得这些源代码文件的。需要说明的一点是:在“资源-文件”中的文件名称如果是desktop/..../xxxx格式的,我们导出以后会变为desktop_2f_...._2f_....这个样的文件名,这里的“_2f_”代表“/”而已。
我们使用了特殊方式去“窥视”了一下Volt的一些jar包。具体的方法就不说了,懂行的人立马明白,不懂行的人也别追究了。这里必须说明一下:这些源代码是有知识产权的,学习和借鉴过程中,千万不要侵犯HCL的知识产权!
二、技术原理
Volt其实是Domino OSGi的一种技术实现。Domino本身就是应用了大量的Java技术,从IBM Domino R8.5开始就开始把OSGi引入到Domino Web开发中,允许Java程序员利用OSGi对Domino平台进行扩展。我们熟知的XPages技术就是一种OSGi。Volt的核心系统代码就在Domino服务器的osgi/volt/eclipse/plugins目录里面。如下图所示。 在这些Volt的系统代码(就是Java程序代码)中,我们可以看到如下几大类组件:IBM Form Platform Service组件 —— 其实Volt的本源就是IBM Forms,后来叫HCL Leap,把IBM Forms(HCL Leap)改造一下并封装成Domino OSGi应用后就变成了HCL Domino Volt。
HCL Domino Leap(dleap) —— 一个Domino OSGi应用,就是HCL Domino Volt的主体程序。
邮件 —— javax.mail API :用于发送邮件。
PDF —— Apache PDFBox :用于操作PDF文件。
POI —— Apache POI:用于操作Excel文件。
Logging —— Apache Common Logging:用于进行日志记录。
Apache HTTP Client , Commons IO , Commons File Upload—— 用于实现REST API访问、文件操作、文件上传等HTTP操作。
JNA —— Java本地资源访问的JNI的封装。
Apache Abdera —— 一个用Java实现的Atom协议,基于HTTP实现Web资源的编辑和发布的协议。现在您明白了为什么要在安装HCL Domino Volt后,需要在Domino服务器的notes.ini文件中加入“HTTPEnableMethods=GET,POST,PUT,DELETE,HEAD”参数了吧。
其它辅助性组件
Volt表单范例:我们看到的界面。
Volt表单对应的JS文件。
我们看一下在JS文件中定义的Volt表单的主体的HTML代码部分。主体部分是dojo.declare声明的——你设计一个Volt表单时有几个页面(Page),就有几个dojo.declare。每个dojo.declare定义一个页面(Page)的HTML主体部分。下图是一个Volt表单的一个页面(Page)的定义。Volt表单中每一个页面的JS定义。
我们可以看到键值“templateString”就是这个Volt表单的一个页面的主体HTML代码。但是HTML代码中还有很多动态参数定义。这些动态参数定义将在前端被替换为实际的内容。我们摘取其中一段来讲解一下。<label id=\"${_uid}F_Paragraphtext1-label\" for=\"${_uid}F_Paragraphtext1-widget\" class=\"lfFormLabel\">多行条目label>\n<div class=\"hint lfFormFieldHint\" id=\"${_uid}F_Paragraphtext1-hint\" style=\"display: block;\">这里是您的个人简介div>
这里面的${_uid}将被替换为真实的Uid。见下图。
那么这个Uid是怎么来的呢?在哪里定义的呢?我在这里先提及一下。来自于"资源-文件"中的application.xml。后面我们会单独分析这个文件。这个文件太重要了,是一切Volt系统程序的起点。上一篇文章说过,当你用Volt设计应用时,所有的设计元素,表单、页面、表格、输入框……全部都会自动被标识上一个Uid。如果你在设计Volt表单时还使用了服务(Service),那么这些内容的前台定义都在该表单名称相关的html,js,json中。具体的就不细讲了。我们还要关注的是类似于“desktop/widget/F_xxx_P_xxx.html”这样的html文件。我们打开看一看,这里面是啥玩意。
我的天啊,里面全部是基于dojo的HTML代码啊。但是请注意,里面还是有${_uid}这样的动态标签,会被替换成真实的Uid的内容。下面,我们看一看Volt表单在浏览器端的代码是啥样的。可以说,全部是通过dojo与后台Volt系统交互出来的HTML代码。就说一点:就是前台的dojo去访问后台的这些Web资源文件(通过Volt代码,不是直接访问Notes数据库的文件资源)由前台JS动态生成的Web界面。
在此范例的Web源代码中,最重要的一句话是:
if(FREEDOM.theMainFormName != null) { dojo.require("freedom.widget.solution.environment.CurrentItemView"); dojo.require("freedomapplication._92fc4c93_1c65_42de_85a2_41f6f6ad273e"); dojo.require('freedom.client.DataStore'); dojo.require('freedom.client.SimpleController'); }
看到那个Volt应用的Uid了吗?这就是一切Volt程序的起点。通过这个Uid,系统自动找到Volt应用数据库(volt/92fc4c93_1c65_42de_85a2_41f6f6ad273e.nsf)然后后台加载数据库中的“资源-文件”,返回到前台后由dojo在动态生成表单界面。
另外,我们发现了一个动态加载第三方JS的代码。虽然这个JS目前没有被加载,可能用于访问分析,但是还是建议HCL去掉这种敏感性的东西。
我们总结一下前台代码分析的结果:前台Volt表单显示并不是使用的Notes表单直接显示的,而是通过Volt系统动态生成的。
当我们启动一个Volt应用显示我们设计好的表单界面时,Volt会从Volt应用数据库中的“资源-文件”中加载表单对应的html,js,json,css等Web资源文件,在处理以后发送给前台的dojo。
Volt在应用数据库的“资源-文件”中缓存了该Volt应用的前台的UI代码、js代码,css代码,这样做其实是加快了后台处理速度。(这点值得学习)
至于那些"desktop/....html,js,json"是到底是什么样的关系,起到什么作用,只有您亲自去研究了。我就不讲了,嘿嘿……(这也是我们的一点点私心嘛,请您理解哟)。提示一下:前台看到一段代码,就反推回去,再对照“资源-文件”中的那些文件,看看里面的内容,就会搞明白了哟。
为什么要要研究Volt的前台代码的技术原理呢?因为你可以改那些资源文件嘛。改一改就知道Volt前台的用户界面也是可以从底层就被您换掉的喽。这也属于Volt应用深度定制的一种方法呀。
二、2、后台Java代码分析终于到了揭开Volt真面目的时候了。我们的研究方法仅限于学习和借鉴的目的,千万不要去侵犯HCL的知识版权!切记!切记!你可以做copycat,但是别做小偷!
在IBM Forms(HCL Leap)中,数据存储都是基于后台关系型数据库的,例如Oracle,DB2之类的。在HCL Domino Volt提供的那些jar包中有相当一部分是使用的java.sql或javax.sql.DataSource。看到这里懂Java技术的人就明白了,IBM Forms(HCL Leap)会使用JDBC/JNDI/数据库连接池之类的技术和后台数据库进行交互。
HCL Domino Volt使用了相当一部分IBM Forms(HCL Leap)的代码。这一点从下图的文件列表就可以看出来。ibm.fsp.*,ibm.nitro.*,这些jar包基本上都是IBM Forms(HCL Leap)的产品的程序包。
但是,HCL Domino Volt是使用的Notes数据库直接通过Notes Java API和对Notes API的JNI调用进行数据操作的。我们通过对 "dleap.util.NSFUtils"、“dleap.util.JNIUtils”和“dleap.util.JNAUtils”的源代码分析就可以得到这个结论。
dleap.util.JNIUtils里面引用了Domino本地函数调用。com.ibm.designer.domino.napi.*类似于调用Domino Designer的功能,可以完成对Notes数据库和Notes设计元素的本地C API方法的调用。
import com.ibm.designer.domino.napi.NotesAPIException;import com.ibm.designer.domino.napi.NotesCollection;import com.ibm.designer.domino.napi.NotesCollectionEntry;import com.ibm.designer.domino.napi.NotesDatabase;import com.ibm.designer.domino.napi.NotesHandle;import com.ibm.designer.domino.napi.NotesNote;import com.ibm.designer.domino.napi.NotesSession;import com.ibm.designer.domino.napi.util.NotesIterator;import com.ibm.designer.domino.napi.util.NotesUtils;import com.ibm.domino.napi.c.BackendBridge;
现在您就应该明白了:我们在发布(部署)一个Volt应用时,Volt会创建一个Volt应用数据库(volt/.nsf)并在这个数据库中自动创建对应的Notes表单、子表单、视图,“资源-文件”。当你访问一个Volt应用时,Volt会在后台读取Volt应用数据库的一些资源文件,会在数据库中进行数据操作(大多数通过RESTful方式)……这些都是通过JNA(Notes API JNI)实现的。
这个过程其实和IBM Forms(HCLLeap)有大同小异:IBM Forms(HCL Leap)是通过JDBC/JNDI/数据库连接池操作后台关系型数据库,Volt是通过Notes Java API和Notes API JNA操作Notes数据库。
说到这里说一个题外话:很多人说Notes数据库是封闭的,不会对它进行数据操作,其实你可以用Notes Java API啊。自己不学习,还要抱怨Notes数据库本身,你这样和“自己不会XXOO,还想制造人类”有啥区别!
我们再看看Volt源代码的常量定义部分,看看有哪些技术上的猫腻。
package dleap.util;public abstract interface DominoConstants{。。。 public static final String VOLTCONFIG_ALL_SETTINGS_VIEW = "lkp-SettingsbySettingName"; public static final String VOLTCONFIG_CRED_ALIAS = "CredAlias"; public static final String VOLTCONFIG_CRED_PASSWORD = "Password"; public static final String VOLTCONFIG_CRED_USER_NAME = "UserName"; public static final String VOLTCONFIG_CRED_VIEW = "ServiceCredentials"; 。。。 public static final String VOLT_VIEW = "VoltView"; public static final String ALLDOCS_VIEW = "VoltView"; public static final String CAT_VIEW = "CatView"; public static final String VOLT_FORM = "VoltForm"; public static final String VOLT_FORM_VIEW = "formView"; public static final String VOLT_SUBFORM_VIEW = "subformView"; public static final String VOLT_STAGE_HASH_VIEW = "(FlowHashLookup)";。。。 public static final String DIRECTORY_USER_LOOKUP_INDEX = "($Users)"; public static final String DIRECTORY_GROUP_LOOKUP_INDEX = "Groups"; public static final String VOLTAPP_BUILDER_PATH = "volt/VoltBuilder.nsf"; public static final String BUILDER_APPOWNER_BYAPPNAME_VIEW = "(appsByOwnerByNameLookup)"; public static final String BUILDER_APPOWNER_BYUPDATE_VIEW = "(appsByOwnerByUpdatedLookup)"; public static final String BUILDER_ALL_BYAPPNAME_VIEW = "(allAppsByNameLookup)"; public static final String BUILDER_ALL_BYUPDATE_VIEW = "(allAppsByUpdatedLookup)"; public static final String BUILDER_ALL_BYID_VIEW = "(allAppsById)"; public static final String BUILDER_FILES_BYID_VIEW = "(filesById)"; public static final String BUILDER_FILES_BYAPPID_VIEW = "(filesByAppId)"; public static final String BUILDER_FILES_PARENTUNID_VIEW = "(filesByParentUnid)";。。。}
果然如我们所预见的那样,Volt去volt/VoltBuilder.nsf中通过Notes视图去读取应用列表的。处于设计阶段的Volt应用也是存储在volt/VoltBuilder.nsf中的。
另外,在Volt 1.0.1.9中,可以在Domino目录中选择用户,看来也是通过读取names.nsf中的Notes视图($Users)和Groups实现的。
下面是一些关键的信息。如果您读懂了,也就读懂了;没有搞明白,别深究了。
下面是dleap.util包中的几个类。通过对这些类的源代码的研究,我们更加明白了Volt是如何操作Notes数据库的。
- dleap.util.NSFUtils:Notes Java API操作Notes数据库。
- dleap.util.JNAUtils / dleap.util.JNIUtils / dleap.util.DDesignDBManager:Notes API 本地调用操作Notes数据库。
- dleap.util.DXLUtils:通过DXL操作Notes数据库。
- dleap.util.LeapUtils:把Volt表单的字段定义与Notes域进行映射。
- dleap.util.DCRUDUtils:通过DQL读取Notes数据、通过CRUD上传和下载Notes文档中的附件、Notes文档的数据变成键值对(JSON)等等。
为了保护HCL知识产权,我们这里就简单地窥视一下dleap.util.NSFUtils的源代码。
整个dleap包的类结构如下图所示。我们后面再列出一些关键的信息给大家。
为了不透露太多HCL的核心代码,我们下面就列出重要的一些信息给大家。如果有机会,您可以自己慢慢研究。以我们目前的研究成果,dleap包里面确实是包罗万象,对我们有很大的启发作用。大公司就是大公司,能够从源代码级别学习人家的产品设计和研发方法,真的是一种“找罪受的享受”。哈哈哈哈……
dleap.FSPServlet / dleap.Activator :加载Volt主界面。
dleap.util.*:包含了对Notes数据库的各种操作。
dleap.impl.credentials.*:调用外部服务时的用户凭证存储操作。
dleap.impl.data.*:Volt前台JS API的BO类的后台处理。
dleap.impl.datastore.*:存取Notes数据库。这里替换了HCL Leap对关系型数据库的存取,使用了Notes数据库存取数据。
dleap.impl.endpoints.*:前台读取Notes数据库数据时的后台处理,主要是返回JSON。
dleap.impl.nl.*:语言资源。怪不得支持那么多种语言呢。
dleap.impl.service.*:把能够号称为service的都放到这里了。自己看截图吧。
说不上来为什么这么安排类路径。反正能叫service的都在这里。
dleap.impl.services.directory.*和dleap.impl.services.dominoservice.*:目录服务,用于读取用户目录的。
dleap.impl.storagefile.*:文件的流操作。磁盘文件的生成都是在这里的。
另外,ibm.nitro.*和ibm.fsp.*都是来自于IBM Forms(HCL Leap)。通过对这些程序包的源代码的初步研究,我们可以得出一个结论:Volt的数据展现模型是前后台搭配实现的,前台实时地动态拉取和显示,实现了类似于MVVM的数据模型,只不过前台用的是dojo,没有用现在流行的react,angular,vue而已。
Volt自身包含了对Excel和PDF的操作,这些都是通过POI和PDFBox实现的。懂得Java技术的读者应该都知道是怎么操作的了。
二、3、application.xml这个文件是一切Volt程序的起点。因为在这个文件里面定义了一个Volt应用的全部配置信息。
在这个XML文件中,配置信息定义分成几个部分:
bindings:字段和Volt表单的绑定关系。
files:Volt应用的文件资源,例如表单上的图片等。
setvices:状态和服务定义。
types:每个字段的定义信息。
ui:Volt应用的表单的界面的定义。结构是ui-form-page-gridLayout-rows-row-cell。
篇幅关系,我们就着重讲一讲ui部分。很多人也很想知道Volt设计出来的表单是怎么所见即所得的。
先看看Volt设计器的界面。这里你会发现Volt设计表单的时候,是按照行和列的模式进行的。所有设计元素,例如字段,表格,区段、Tab页等都放在每一行的单元格里面。
在Volt的后台存储的配置信息里面,就是按照设计器里面的行列模式存储的。如上图的两行两列四个单元格,在application.xml里面,就是:
...这里是字段信息...
我们截取一部分cell代码,可以分析一下这个单元格是如何显示内容的。下面是中一段代码:
<cell> <textField length="medium" placeHolder="" seenInOverview="true" isUppercase="false" formatRegEx="false" name="A_Uername" uid="cab9544b-ef34-4a89-8935-2df9d9e06e99" customCssClasses="" customAttribute=""> <label>姓名label> <description>description> <hint>请输入您的姓名,必须填写!hint> <events> <event name="onClick" serviceId="41033924-ec60-4241-808f-cb5b3488e7b0" formulaId="">event> events> textField>cell>
这个单元格里面定义了一个textField,标题是“姓名”,描述为“”,提示信息是“请输入您的姓名,必须填写”。那么我们怎么知道这个textField的类型呢,该是什么样的显示方式呢?是单行文本框,多行的,下拉列表还是复选框呢?我们需要通过这个textField的Uid属性去到types里面查找它的类型定义。
<string length="50" isUppercase="false" formatRegEx="false" name="A_Uername" uid="cab9544b-ef34-4a89-8935-2df9d9e06e99" required="true" unique="false" dataLabel="" customAttribute=""> <label>姓名label> <description>description> <defaultValue>defaultValue>string>
我们看到该字段是string,长度是50,没有format属性(说明是单行文本框),required属性是true,说明:
要生成一个的HTML代码。
需要在提交时判断这个数据框是否为空,因为required="true",是必填的字段。
因为有提示文字,所以该输入框下面会有一段说明文字。
通过对Volt前后台源代码的分析,我们发现Volt的技术原理并不复杂。其实我们也了解了IBM Forms(HCL Leap)的工作原理。
总结一下Volt的技术原理。
- Volt设计器中进行应用设计时,所有的信息都会存储到Notes数据库中。
- Volt应用发布(部署)以后,Volt会通过JNA方式创建Notes数据库volt/.nsf。并通过JNA/JNI、Notes Java API等创建对应的数据结构,例如表单、子表单和视图等。同时在Volt应用数据库中以“资源-文件”的方式存储Volt应用的配置信息、前台JS,HTML,CSS等。
- 访问一个Volt应用时,Volt从该数据库中读取配置信息,资源文件和数据。主要的技术是JNA/JNI、DQL、Notes Java API。
- application.xml里面定义了一个Volt应用的所有的数据结构和表单界面。
- HCL Domino Volt的原型就是IBM Forms(HCL Leap)。只不过是变成了Domino OSGi应用而已,数据库使用的Notes数据库。
我们最新推出的JoinHand DomPortal Volt集成工具,就是对Volt源码级的解构的最新成果。这个工具是全球第一个,也是目前唯一一个实现Volt低代码工具与专业代码工具进行集成的解决方案!JoinHand DomPortal —— Domino应用的创意未来!
恭喜你,当你读到这里的时候,差不多阅读了6500个文字。加上我们的第一部分《数据结构》(点击即可阅读),差不多洋洋洒洒10000多个文字了。非常感谢您的阅读!
另一篇文章:
源码级分析!解构HCL Domino Volt低代码工具(一)
关注我,会有很多干货哟。我是持续创新的Domino开发者!