Apache OFbiz MiniLang 源码解读

MiniLang所有元素的父类——MiniLangElement

MiniLang 是基于XML的“描述型语言”。所有的元素,包括节点、属性都继承自该类。它包含三个属性:

  • lineNumber:表示解析MiniLang的源码(通常是Java)所处的行号,主要是为了便于日志记录
  • tagName:当前元素的tag名称,主要用于日志记录
  • simpleMethod:simpleMethod是一个大的“传输对象”,里面实现了MiniLang支持的所有执行方式,其作用类似于serviceengine中的serviceDispatcher。

该类中没有太多的功能代码,除了outputTraceMessage。它用于记录跟踪日志消息,以上提及的两个属性(lineNumber/tagName)主要用在这里。

对应于服务引擎的实现——SimpleServiceEngine

在之前一篇关于ofbizservice engine的剖析中,我们谈到了里面也有minilang相关的一个执行引擎,其实就是此处的SimpleServiceEngine。它继承了GenericAsyncEngine(这里继承的语义并不是is-a的关系,主要是代码复用,因为GenericAsyncEngine实现了runAsync,而其他的子引擎都没必要实现这个接口方法)。

SimpleServiceEngine的继承关系图:

SimpleServiceEngine实现了父类中的两个抽象run方法:

但这两个方法都不包含主要的实现逻辑,真正的逻辑被定义在私有方法:serviceInvoker中,它最终是通过SimpleMethod的静态runSimpleService方法来执行的。

BeanScript的引擎实现——SimpleMethodBsfEngine

准确来说,它是对IBM的Beanscripting 库的适配器。用于在minilang中调用Beanscript。它实现了如下方法:

其中apply以及call未提供实现(抛出异常),它在内部维护了一个上下文对象,用来存放BSFDeclaredBean,而该上下文对象用于为SimpleMethod的执行提供另一个上下文。维护上下文对象通过如下两个方法:

  • declareBean
  • undeclareBean

方法执行的主要逻辑实现位于eval中。它通过方法参数找到SimpleMethod对象,然后构造执行的上下文对象,调用SimpleMethod对象的exec执行。

MiniLang真正的执行者——SimpleMethod

SimpleMethod继承自MiniLangElement,说明它也是MiniLang中的一个元素。SimpleMethod包含了很多跟执行任务相关的代码:

当然这些方法大多是应对MiniLang可以应用场合的重载,可以看到它可以应用于简单方法、事件以及服务。最终真正的执行方法是exec方法。

当然还有一些方法是静态帮助方法。比如readOperations,它返回当前元素所有子元素所被代表的MethodOperation实例的集合,(关于MethodOperation后面会讲到,每个minilang节点都最终被影射为一个MethodOperation,由Java解释执行)。

验证检查器——MiniLangValidate

由于MiniLang是采用xml来描述任务的“语言”,因此它并不具备编程语言的很多编译检查机制。所以它采用了一些辅助方法来进行检查:包括了验证属性、验证子元素、测试元素是否为空若为空设置默认值、测试某属性是否为常量值等、是严格模式还是宽松模式以及错误处理等等

MiniLang的帮助类——MiniLangUtil

该类是MiniLang的帮助类,提供了一些方法:调用SimpleMethod的方法、类型转换/获取的方法、设置MiniLang内容的方法等

字段验证及转换器——SimpleMapProcessor

SimpleMapProcessor从上下文对象中获取到字段信息,然后进行验证以及转换(当然真正的验证以及转换逻辑并不是由他来实现的,它只是起到触发动作),然后将验证通过的字段放入目标集合中去。MiniLang会定义很多这样的MapProcessor,而SimpleMapProcessor主要用于获取这些所有的mapProcessor(内部获取,不对外公开)然后运行他们:

对于所有的mapProcessor,SimpleMapProcessor支持两种缓存,一种是基于字符串xmlResource作key的缓存,一种是基于URLxmlURL为key的缓存。而所有的runSimpleMapProcessor都是先获取到mapProcessor集合中特定的mapProcessor,然后调用其exec方法来执行验证以及转换动作(后面会有介绍)。

操作描述的基类——SimpleMapOperation

MiniLang中提供了很多的operation,而所有这些operation都继承自SimpleMapOperation这个抽象类:

SimpleMapOperation的构造方法接收两个参数:

  • element:一个xml元素
  • simpleMapProcess:SimpleMapProcess类的实例

构造方法中还初始化了一些参数,比如failMessage、failProperty等。

另外提供了一个方法addMessage,它用于结合failMessage、failProperty,以提供更加完善的错误信息。另一个很重要的方法exec被标记为abstract,以待子类的实现。

字符串参数综合处理器——SimpleMapProcess

该类是MiniLang中参数的综合处理器。它包含一个readOperations方法,用于遍历当前节点的所有子节点,分别对这些子节点的节点名称进行匹配并生成各种不同的SimpleMapOperation的实例,然后加入到集合中去。

而该类也提供了一个exec方法,它遍历所有的处理器,一个一个得对它们运行各处理器自己的exec方法以完成相应的处理。

提供比较操作的基类——BaseCompare

MiniLang中有很多操作是比较操作,而BaseCompare就是执行比较的抽象基类,从上图很明显看出它继承自SimpleMapOperation。

在构造函数中,它从参数element中提取三个属性:

  • operator: 比较操作符
  • type: 比较的对象类型
  • format:格式

另外还有三个方法:

  • doRealCompare是真正用于比较的静态方法,它依赖于ofbiz的其他实现,返回bool值的比较结果
  • doCompare调用doRealCompare获得比较结果,如果结果为false,则调用SimpleMapOperation的addMessage方法,添加失败信息
  • exec:空实现,这是符合语义的,因为这只是个compareoperation

值比较器——Compare

该类提供对输入参数的比较功能。在构造函数中,它提取该元素的value属性,作为要比较的对象之一。

该类对exec方法的实现为:

从方法参数inMap(MiniLang执行所需的所有传入参数的集合)中获得某个fieldName的值(此处fieldName,正是SimpleMapProcess当前正在处理元素中提取出来的)作为另一个比较对象,然后调用BaseCompare的doCompare进行比较。

字段比较器——CompareField

上面的Compare提供某个参数的值跟MiniLang定义的某个元素的值进行比较。而CompareField提供某个参数的值跟MiniLang定义的某个字段的参数值进行比较,这个听着比较绕口,看看代码就明白了:

Compare:

public Compare(Element element, SimpleMapProcess simpleMapProcess) {
        super(element, simpleMapProcess);
        this.value = element.getAttribute("value");
    }

    @Override
    public void exec(Map<String, Object> inMap, Map<String, Object> results, List<Object> messages, Locale locale, ClassLoader loader) {
        Object fieldValue = inMap.get(fieldName);
        doCompare(fieldValue, value, messages, locale, loader, true);
    }

CompareField:

public CompareField(Element element, SimpleMapProcess simpleMapProcess) {
        super(element, simpleMapProcess);
        this.compareName = element.getAttribute("field");
    }

    @Override
    public void exec(Map<String, Object> inMap, Map<String, Object> results, List<Object> messages, Locale locale, ClassLoader loader) {
        Object compareValue = inMap.get(compareName);
        Object fieldValue = inMap.get(fieldName);

        doCompare(fieldValue, compareValue, messages, locale, loader, false);
    }

MiniLang的拷贝操作——Copy

该类提供将一个参数从输入参数列表拷贝到输出参数列表。在构造方法中,获取元素需要拷贝到的目标key,并获取是否替换以及如果为null则设置相关的配置。exec方法中主要实现拷贝动作。

对字段的非空检测——NotEmpty

该类用于检测当前字段是否为非空(null或者空字符串),如果为空则设置错误信息。示例:

<not-empty>
            <fail-property resource="AccountingUiLabels" property="AccountingCardNumberMissing"/>
        </not-empty>

MiniLang的类型转换器——Convert

Convert类用于转换某个处于输入参数集合中的参数然后将其放入输出参数集合。在构造方法中,它先获得要处理元素的“to-field”属性,然后获得要转换的类型。在exec中,获取要转换的对象,进行转换并放入输出参数集合

MiniLang的正则表达式验证器——Regexp

Regexp用于正则表达式验证,从处理元素中获取expr属性,然后在exec方法中,先将当前字段转换为String,然后对其进行正则匹配。

MiniLang的验证方法调用器——ValidateMethod

minilang支持对字符串调用指定的验证方法进行验证,形如:

<validate-method method="isAnyCard" class="org.ofbiz.base.util.UtilValidate">
            <fail-property resource="AccountingUiLabels" property="AccountingCardNumberIncorrect"/>
        </validate-method>

在构造方法中,它先去获取该元素的method属性以及class属性。其exec方法,先获得该字段的值,然后将其转换为String类型,获得当前线程的classloader,加载该类,获取该方法(值得一提的,这里只支持静态方法)。因为在用反射调用该方法的时候并没有传入该类的实例。

字符串输入参数生成器——MakeInStringOperation

MiniLang支持根据配置生成特定的字符串值作为一个输入参数,这里涉及到几种不同的字符串生成操作类型:

MakeInStringOperation是抽象基类,它仅仅定义了一个抽象方法:exec。

  • PropertyOper:基于Property的生成参数
  • InFieldOper:基于输入参数列表(inMap)生成参数
  • ConstantOper:追加“字符串常量”生成参数

字符串生成器的容器——MakeInString

MakeInString是上面这些MakeInStringOperation的容器,在构造函数中,它先获得当前元素的field属性,作为某个参数的key。然后获取当前元素的所有子元素,判断节点名为其生成不同的MakeInStringOperation并放入一个集合中去。

在exec方法中,遍历所有的MakeInStringOperation,分别执行它们的exec方法,并获取它们返回的结果。

真实的Map处理器——MapProcessor

之前上面有提到SimpleMapProcessor其实是一个总处理器,而真正的处理器是MapProcessor。所谓的处理包括两个方面:

  • makeInStrings:对应miniLang中的“make-in-string”子节点
  • simpleMapProcess:对应miniLang中的“process”子节点

示例:

<simple-map-processor name="newAffilPostalPurpose">
    <make-in-string field="contactMechPurposeTypeId"><constant>BILLING_LOCATION</constant></make-in-string>
    <process field="contactMechId"><copy/><not-empty>
    <fail-property resource="PartyUiLabels" property="PartyContactMechIdMissing"/></not-empty></process>
    <process field="contactMechPurposeTypeId"><copy/></process>
  </simple-map-processor>

没错,其实之前我们一直都在谈跟这两个方面相关的实现类。

MapProcessor用两个集合来放置上面的两种处理器。在构造方法中,它先去遍历如上说的两个子节点,来初始化这两个集合。

而同样不出意外的是,exec会分别遍历两个处理器中的实例来处理map中的参数。

元素实体的基类——MethodOperation

我们进入到miniLang认可的合法“语法”的部分了,它们非常得多,我们先看一下包的集成关系图:

MethodOperation正是这些包里所有类的抽象基类。MethodOperation继承自MiniLangElement。它拥有一个受保护的构造方法,需要两个参数:

  • element:当前表示的元素
  • simpleMethod:SimpleMethod类的实例

主要用于初始化MiniLangElement。

它定义了一个抽象方法exec,该方法接收一个上下文对象。返回一个bool值,用于指示脚本的执行是否需要继续。

该类定义了一个用于创建MethodOperation的工厂接口,主要包含了创建MethodOperation的接口方法。

值得一提的是,MethodOperation类中还定义了一个DeprecatedOperation注解,用于标示已废弃的MethodOperation。

MiniLang中跟Entity相关的实体

毫无疑问,跟entity相关的所有操作都是由Delegator代理的,这个在之前的entityengine相关的文章中有介绍过。在miniLang中需要对entity进行操作,就需要将那些语法翻译为Delegator操作它们的java代码。而这些都被封装在entityopspackage中。并且这里每个实现类中都包含有一个实现了MethodOperation内定义的工厂接口的静态内部类,用于创建特定的工厂。

另外每个实现类的toString方法都还原了miniLang中当前节点的字符串表达。

MiniLang中跟if判断条件有关的判断实体

  • IfNotEmpty:对应miniLang中的if-not-empty节点,用于判断当前字段是否为空。在构造函数中,它会解析出其所有子节点的操作以及如果有else元素节点,则同时解析出else元素下的所有子节点操作。在exec方法中,先判断如果满足不为空的条件,则执行if下的所有操作,否则如果else节点下操作不为空,则执行这些操作
  • IfInstanceOf:判断某个对象是否是某个类的实例。而至于处理过程跟IfNotEmpty类似
  • CheckPermission:检查权限,在之前的一篇文章中,我们简单谈了一下ofbiz的权限设计,其中有多个层面上的权限检查。由于minilang在ofbiz中可以实现service、eca、seca,因此在其中也必须嵌入对权限的检查语法。在该类中,定义了一个内部实体类:PermissionInfo。它用于从CheckPermission节点,提取出permission,action。并定义有一个hasPermission方法,用于判断用户有无实体操作权限或是否通过了认证授权。在CheckPermission的exec方法中调用hasPermission进行判断。

MiniLang中跟条件相关的判断实体

  • Conditional:所有condition实体所继承的接口,该接口包含两个方法
    • checkCondition:检查条件,返回Bool值结果
    • prettyPrint:打印,主要用于执行Assert命令时,给出完整的错误信息
  • ConditionalFactory:一个用于创建条件实体的工厂,需要它的部分原因是:miniLang可以被扩展用于支持额外的条件元素(通过继承该类然后提供自定义的实现)。该类是一个抽象泛型类,在静态构造器中,它获取配置过的所有的ConditionalFactory并加以缓存。该类包含一个静态方法,以及两个抽象方法:
    • makeConditional:从缓存中获取一个factory并创建某个condition的实例
    • createCondition:有待所有condition实现的创建方法
    • getName:有待所有condition实现的返回其名称的方法
    • 因此下面的每个condition实现类,都会创建一个静态内部工厂类并继承ConditionalFactory以及之前提到过的Factory(MethodOperation语义相关)。
  • Compare:一个抽象类(也可以理解为帮助类),提供各种条件比较。包含了一个doCompare方法,由多个内部静态类实现。
  • CombinedCondition:该类是一个抽象类,它用于实现由and/or/not/xor连接的多个条件的判断。它并没有继承MethodOperation(没有实现之前的exec方法,因此它也不是一个简单的operation),而是继承自MiniLangElement,但同时实现了Conditional接口(总得来看,Conditional提供比MethodOperation更窄的“接口”,但Conditional只负责条件相关的比较操作)。它拥有一个subConditions集合,该集合在构造方法中被初始化同时存放当前节点的子节点对应的condition。该类并没有对具体的逻辑操作符进行区分实现,但对ConditionalFactory的实现却给出了基于不同逻辑操作符的实现(多个不同的静态内部类)。在每个不同的factory中,会根据自身的语义实例化CombinedCondition。
  • MasterIf/ElseIf:这两个类用于联合实现if/ else if / else的条件判断逻辑。

MiniLang中跟环境检查/设置相关的操作实体

ventops 包中包含了一些miniLang中支持的事件,比如:字段与请求参数之间的互相转换;字段与session参数之间的转换。在必须实现的exec方法中,首先必须判断当前的methodType必须为EVENT,其他的并无不同。

MiniLang中跟调用相关的操作实体

在miniLang中支持对其他组件的调用,比如Service、bsh、script、classMethod等。这些都实现在callops包中,虽然它们之间并无相互间的关联关系。但它们都继承自MethodOperation。并且每个类内部都实现了一个静态内部工厂。

MiniLang中其他操作实体

otherops包内涉及了计算、跟踪、日志记录相关的几个非功能性操作的实现。

跟方法相关的参数基类——MethodObject

MethodObject是一个抽象类,提供多MiniLang中传递参数的抽象。它包含三个抽象方法:

  • 获得对象的值:getObject
  • 获得类型的Class对象:getTypeClass
  • 获得类型名称的字符串表示:getTypeName

以及两个实现类:

  • FieldObject:抽象类通用参数类型
  • StringObject:抽象了字符串的类型表示

字段的通用对象表示——FieldObject

MiniLang的语法中有很多field属性,它们对应到程序里的Model类就是FieldObject。该类有一个关键属性就是type。至于方法,最关键的方法是getObject,它从消息上下文对象(MethodContext)中获取其值。

字符串的表示类——StringObject

该对象的实例表示一个JavaString对象,用于传递到MiniLang进行方法调用。它具有两个属性:

  • 当前XML元素的文本值,位于xml的开/闭标签之间的值
  • 当前XML元素的“value”属性的值

关键方法为:getObject,它将连接value属性的值跟文本值,作为一个整体字符串返回。

消息元素的表示类——MessageElement

MiniLang对于方法通常都可以配置fail-message,用于方法调用产生异常时提供相关的失败信息。该类即用于解析并封装此对象。

方法调用的上下文对象——MethodContext

MethodContext是一个大对象,它包含MiniLang中方法执行需要的一切对象信息。其中的关键对象:

  • delegator:Delegator的实例,用于代理ofbiz中所有的数据访问
  • dispatcher:LocalDispatcher的实例,用于代理ofbiz中所有的服务访问
  • request:HttpServletRequest的实例,表示客户端的请求对象
  • response:HttpServletResponse的实例,表示对客户端个请求的响应对象
  • userLogin:GenericValue的实例,封装了用户身份认证

另外,提供了一些get/put方法。

时间: 2024-10-16 18:32:05

Apache OFbiz MiniLang 源码解读的相关文章

Apache Beam WordCount编程实战及源码解读

概述:Apache Beam WordCount编程实战及源码解读,并通过intellij IDEA和terminal两种方式调试运行WordCount程序,Apache Beam对大数据的批处理和流处理,提供一套先进的统一的编程模型,并可以运行大数据处理引擎上.完整项目Github源码 负责公司大数据处理相关架构,但是具有多样性,极大的增加了开发成本,急需统一编程处理,Apache Beam,一处编程,处处运行,故将折腾成果分享出来. 1.Apache Beam编程实战–前言,Apache B

15、Spark Streaming源码解读之No Receivers彻底思考

在前几期文章里讲了带Receiver的Spark Streaming 应用的相关源码解读,但是现在开发Spark Streaming的应用越来越多的采用No Receivers(Direct Approach)的方式,No Receiver的方式的优势: 1. 更强的控制自由度 2. 语义一致性 其实No Receivers的方式更符合我们读取数据,操作数据的思路的.因为Spark 本身是一个计算框架,他底层会有数据来源,如果没有Receivers,我们直接操作数据来源,这其实是一种更自然的方式

Jfinal启动源码解读

本文对Jfinal的启动源码做解释说明. PS:Jfinal启动容器可基于Tomcat/Jetty等web容器启动,本文基于Jetty的启动方式做启动源码的解读和分析,tomcat类似. 入口  JFinalConfig的继承类的Main方法为入口,实例代码继承类为:DemoConfig,Main方法如下: public static void main(String[] args) { /** * 特别注意:Eclipse 之下建议的启动方式 */ JFinal.start("WebRoot&

HttpClient 4.3连接池参数配置及源码解读

目前所在公司使用HttpClient 4.3.3版本发送Rest请求,调用接口.最近出现了调用查询接口服务慢的生产问题,在排查整个调用链可能存在的问题时(从客户端发起Http请求->ESB->服务端处理请求,查询数据并返回),发现原本的HttpClient连接池中的一些参数配置可能存在问题,如defaultMaxPerRoute.一些timeout时间的设置等,虽不能确定是由于此连接池导致接口查询慢,但确实存在可优化的地方,故花时间做一些研究.本文主要涉及HttpClient连接池.请求的参数

structs2源码解读(6)之解析package标签

structs2源码解读之解析package标签 上面讨论过,在创建Dispacher对象时,调用dispacher.init()方法完成初始化,在这个方法中先创建各种配置文件的解析器(ConfigurationProvider),然后循环遍历这些解析器的register()方法解析各个配置文件.  for (final ContainerProvider containerProvider : providers)         {             containerProvider

MyBatis源码解读(3)——MapperMethod

在前面两篇的MyBatis源码解读中,我们一路跟踪到了MapperProxy,知道了尽管是使用了动态代理技术使得我们能直接使用接口方法.为巩固加深动态代理,我们不妨再来回忆一遍何为动态代理. 我相信在初学MyBatis的时候几乎每个人都会发出一个疑问,为什么明明是XXXDao接口,我没有用任何代码实现这个接口,但却能直接使用这个接口的方法.现在清楚了,动态代理.我们来写一个demo小程序来看看. 首先是一个Test.java的接口,只有一个say方法. 1 package day_16_prox

第15课:Spark Streaming源码解读之No Receivers彻底思考

本期内容: Direct Access Kafka 前面有几期我们讲了带Receiver的Spark Streaming 应用的相关源码解读.但是现在开发Spark Streaming的应用越来越多的采用No Receivers(Direct Approach)的方式,No Receiver的方式的优势: 1. 更强的控制自由度 2. 语义一致性 其实No Receivers的方式更符合我们读取数据,操作数据的思路的.因为Spark 本身是一个计算框架,他底层会有数据来源,如果没有Receive

Tomcat源码解读:我们发起的HTTP请求如何到达Servlet的

在上一节中,了解了Tomcat服务器启动的整个过程,现在来了解一下Tomcat如何接收到HTTP请求,并将请求送达至Servlet,Servlet处理后,响应信息又是如何返回给浏览器的呢?这两个问题是接下来要研究的课题,本节先研究第一个问题. 了解一点点网络知识的人,都会知道TCP连接通信是基于Socket的,上一节也有提到这点.通过上一节的说明,可以了解到Tomcat服务器在内部已经使用Endpoint类封装了Socket. 本篇会包含大量的源码解读说,由于篇幅原因,就将源码折叠起来,如果想了

(转)go语言nsq源码解读二 nsqlookupd、nsqd与nsqadmin

转自:http://www.baiyuxiong.com/?p=886 ----------------------------------------------------------------------- 上一篇go语言nsq源码解读-基本介绍  介绍了最基本的nsq环境搭建及使用.在最后使用时,我们用到了几个命令:nsqlookupd.nsqd.nsqadmin.curl及 nsq_to_file,并看到用curl命令写入的几个”hello world”被nsq_to_file命令保