技术文档 第一部分、ofbiz 表现 一、理解 MVC 模式 当涉及大量商业逻辑项目的时候,我们需要考虑什么?如何分离用户界面和后台操作?如何避 免将商业逻辑混淆于一般的流程控制中?作为企业信息系统, 就需要考虑很多类似的问题。 源源不断的客户新需求, 要进行功能修改和扩充, 但是因为程序的高耦合,改动将变得非常困 难,导致项目成本何风险增加。 而且,往往维护人员与开发人员不是同一个人, 即使有详尽的文 档,也很难理清程序里纵横交错的联系。 所以贯彻 Model-View-Controller(MVC)模 式的设计, 在 设计阶段首先杜绝此类问题, 是一个非常好的方法。 1、MVC 理论描述 所谓 MVC 模式,指的是一种划分系统功能的方法,它将一个系统划分为三个部分: 模型(Model): 封装的是数据源和所有基于对这些数据的操作。在一个组件中, Model 往往表示组件的状态和操作状态的方法。 视图(View):封装的是对数据源 Model 的一种显示。一个模型可以由多个视图, 而一个视图理论上也可以同不同的模型关联起来。 控制器(Control): 封装的是外界作用于模型的操作。通常,这些操作会转发到 模型上,并调用模型中相应的一个或者多个方法。一般 Controller 在 Model 和 View 之间起 到了沟通的作用,处理用户在 View 上的输入,并转发给 Model。这样 Model 和 View 两者 之间可以做到松散耦合,甚至可以彼此不知道对方,而由 Controller 连接起这两个部分。 模型,即相关的数据,它是对象的内在属性;视图是模型的外在表现形式,一个模型可以对应 一个或者多个视图,视图还具有与外界交互的功能; 控制器是模型与视图的联系纽带,控制器提取 通过视图传输进来的外部信息转化成相应事件,然后由对应的控制器对模型进行更新; 相应的,模 型的更新与修改将通过控制器通知视图,保持视图与模型的一致性。 下图(图 1.1)描述了这三者 之间的关系: 2、系统设计 系统属于浏览器/服务器模型(Browser/Server)。一般的,客户通过浏览器发送 HTTP 请求给 服务器端 Web 服务器,Web 服务器接收该请求并且进行相应处理,然后将处理后的结果返回到客 户的浏览器中。在客户端,浏览器中呈现的正是该系统的视图部分。视图的作用就是提供给客户一 个可视化的界面,并且允许客户输入一些数据来与服务器端程序交互。 对客户来说,他只能看到视图,而模型和控制器对他则是透明的。在这里 Web 服务器仅仅 起到提供 HTTP 服务的作用。Web 服务器将客户提交的 HTTP 请求交给后方的 Jsp、Servlet 引擎, 并且进一步交给其中的控制器来处理。控制器按照从 xml 配置文件中提取的请求映射表将该请求映 射到相应的处理器(Handler);处理器对模型进行更新、修改等操作,处理完后返回结果给控制器; 控制器根据结果通知视图做相应变化,并且选择相应视图返回给客户。下图(图 1。2)说明了这一 协作过程。 3、OFBiz 中 MVC 模式体现 OFBIZ 的 Web 应用框架严格遵循 MVC 模式。 OFBizMVC 中 Model 有它的封装业务逻辑的事 件和服务承担. Control 有 controller 承担, View 有传统的 jsp, 和 FreeMarker,JPublish, Beanshell 承担。 这里我主要说明 Control (Model, View将在相应技术的模块阐述). 在OFBiz框架中, Controller是一组 管理 web 表示层对象, 其目的是将业务逻辑和表示层完全地分离开来。 1)过滤器(Context Security Filter) Servlet API 2.3 中最重大的改变是增加了 filters,filters 能够传递 request 或者修改 response。 Filters 并不是 servlets;它们不能产生 response。你可以把过滤器看作是还没有到达 servlet 的 request 的预处理器,或从 servlet 发出的 response 的后处理器。一句话,filter 代表了原先的"servlet chaining" 概念,且比"servlet chaining"更成熟。 filter 能够: 在 servlet 被调用之前截取 servlet 在 servlet 被调用之前查看 request 提供一个自定义的封装原有 request 的 request 对象,修改 request 头和 request 内容 提供一个自定义的封装原有 response 的 response 对象,修改 response 头和 response 内 Context Security Filter (CSF) 在 /WEB-INF/web.xml 定义,用来限制访问 web 应用程序的文件. 具体参看下面的例子. filter 配置如下: ContextSecurityFilterContextSecurityFilterorg.ofbiz.content.webapp.control.ContextSecurityFilterallowedPaths/control:/index.html:/index.jsp:/default.html:/default.jsp:/imageserrorCode403ContextSecurityFilter/* 2)Control Servlet 控制程序流程 Control Servlet 是所有请求过程的核心. 当收到一个请求时,servlet 首先设置系统环境信息, 初始化 request, session , 实体代表(采用了"业务代表"的 设计模式, 我们将在关于实体引擎文章中介 绍), Service Dispatcher, and Security Handler 存放在 ServletContext. 随后 control server 将 request 交 给了 Request Handler 处理. Request Handler 处理完成后,返回给 control servlet ,整个请求过程就结束 了。 3)Handler 对请求的处理过程 Request Handler 利用 RequestManager 帮助类收集在定义在/WEB-INF/controller.xml 中的请 求映射(mapping) . 映射建立了 URI 和 VIEW 的对应关系。 URI 又和 Event 紧密相连,Events 用来 处理可以直接通过实体引擎,或者调用服务的方式处理业务逻辑。 当 Request Handler 收到一个请求后,首先察看请求映射 (mapping),如果没有发现对应的当 前请求定义或者请求映射,返回错误。 如果察看成功,调用 Security Handler 检查当前的请求是否 需要验证和使用这个 请求的用户身份验证。如果当前的用户没有验证,Request Handler 直接将请求 转向到登录页面。 成功验证之后, Request Handler 寻找当前请求的事件 Event。并将请求转交给 EventHandler. 当事件处理完成之后, 又将 reuqest 移交给 ViewHandler. 如果 view 类型是 url,直接 重定向到 url, 否则根据不同的 view 类型。调用不同的 view handler 处理(如:JPublishViewHandler, RegionViewHandler, FreeMarkerViewHandler)。 请求映射定义实例: Verify a user is logged in. 如上所示, 事件返回" success" 调用 view: main 。如果这里的 type="request", 返回成功以后, 自动调用另外的请求。 这就是所谓的"请求嵌套"。 二、JPublish 合成表示层 1、经典的合成器--JPublish JPublish 是一个功能强大的 web 发布系统. 它的目的也是最大的特点是将 web 开发人员的角色 严格并清晰的区分开来。JPublish 支持多种模版引 擎, 包括 Apache Velocity, FreeMarker 和 WebMacro. JPublish 也支持各种脚本语言, 如: Python, BeanShell, and JavaScript。 JPublish 是绝对是 一个经典的"合成器", 平滑的扩展性让人赞叹。 JPublish 定义了多种角色用户: 应用程序设计师(Application designer)-- 主要是通过模板来为一个网站和 这个网站的不同部分进行统一的设计, 构架。 内容策划(content producer)-- 负责处理内容库(content repository)中的文 档或其他信息。内容策划在板面设计中通常使用一套有限的设计元素(比如粗体或斜 体)。 特定业务领域的程序员(Domain-specific programmer)-- 负责 build 特定领 域的 Java 代码,这可能是 JavaBeans,或者与 Web 应用程序相关的普通 Java 类。 集成程序员(Integration programmer ) -- 负责新建一个将特定代码和 Web site 连接起来的逻辑,该逻辑体现为 JPublish 的用于 scripts 自动化任务的 action system。 有了这种用户角色的清晰分离,实际上做到了 内容, 程序逻辑,和表示逻辑的分离。 大多数开发者往往会将所有的任务放在一起,而没有将它们更好地组织起来。笔者之前所在的 项目组也遇到了此类的问题:项目开始时,似乎已经做了很好的设计。然后将功能化成很多块,每 个人负责一块。但是到了项目后期发现,需求无奈变更,代码变得很糟糕。 程序员往往为了解决一 个问题,乱塞代码。导致原有的设计变形。结果项目难以维护,不停地 refactor。导致这样的后果是 因为在开发阶段没有将开发人员的职能清晰的分离开来。调试程序时,总是将表示逻辑和程序逻辑, 整合过程一同考虑。 这样代码的质量必然下降。导致最后为解决问题牺牲了代码的质量。 而使用这种基于角色的工具会使你不得不将这些任务分开并重新合成,我相信这一点一定会使 你的程序变得更好更高效。 "Now everyone can do their specific task and put it all together。" 2、template + script 我们知道 JPublish 将 template, script 合成在一起。OFBiz 选择了 FreeMarker 和 Beanshell. view的表示逻辑写在FreeMarker, 程序逻辑写在BeanShell中, 中间还有一个pagedef 文件,将 ftl 和 bsh 结合起来。 这里还有一个好处要提一下,就是通过 pagedef 配置一个 ftl 对应多 个 bsh, 那么就可以做到 bsh 文件封装的程序逻辑的重用, 换一句话说就是一个 bsh 文件可以在多 个地方使用。 所以完成一个 jpublish 类型的 view 实现,至少需要编写三个文件. ftl 文件, pagedef, bsh, 在不同的目录切换,某种意义上增加了工作量。 3、JPublish 和 MVC JPublish 遵循 MVC 吗? 回答是否定的. 笔者曾在网上看到“JPublish 提供了 MVC 架构” ,其实这是错误的。 从 MVC 模式的构成来, JPublish 完全不是。 JPublish 没有 control, pagedef 只是将 template 和 srcipt 合成起来。每次在 template web page 被执行之前首先执行对应的 script. 不存在控制流程的功能。在 OFBiz 中, JPublish 担任着 view 的 三、区块(Region)指南 1、简介 区块工具(或叫框架)是一个复合视图模式(在 Enterprise Architecture Patterns 描述的各种视图) 的实现,同时也是基于 David Geary 著作的 Advanced Java Server Pages 的一种实现。在 www.ofbiz.org 的 Docs & Books 页面可以得到有关这本书更多的信息。 区块由许多命名节(准备插入到一个模板)组成的一个区域。区块和节在每个 webapp 的 WEB-INF/regions.xml 文件里进行配置。通过使用一个类型为 region 的视图,区块能与基于 web 应 用的 OFBizControl Servlet 协同工作。 在 JSP 文件中,可以对区块进行声明,并且通过使用 regions.tld 文件里的标记把区块包含进来。 这些标记也可以用在区块模板中,用来在模板的指定位置包含命名节。 2、定义区块 1)一个基本的区块定义 首先让我们看一下在 WEB-INF/regions.xml 文件中的一个实际区块的定义: Application Page define 标记用来声明一个命名区块。区块名在 id 属性指定。这是使用 JSP 模板的基本区块。你 也可以定义一个从其他区块继承来的区块,并可以从下一节开始使用。在模板属性中指定模板全路 径,并且是相对 webapp 的根路径的一个 webapp 资源,就象一个 webapp 里的 JSP 或 Servlet 一样。 模板文件可以插入的内容是常规文本,或其他文本,这些文本被插入在不同的地方。在一个区 块中内容就是作为一个节而被插入。在区块模板中,节由名称进行标识,并且在配置一个区块时, 通过 put 标记对准备插入到每个节的内容进行定义。 put 标记的 section 属性保存节名称。content 属性的值用来确定准备插入到节中的内容。内容还 可以嵌入到 put 标记的 body 里,类似于上面示例的标题节。这里指的是直接内容。缺省地,content 属性值是用来定位当前 webapp 的某个资源的。 节类型在 type 属性中设定,并且缺省类型是 default。这些类型将在下面讨论。 注意:如果一个节被包含到一个模板中,却没有在区块定义中为它指定内容,那么当模板被使 用时不会插入任何东西。 2)一个继承的区块 下面是一个通过继承其他区块来定义区块的例子。 Login Page 这个定义没有使用定义标记中的模板属性,而是使用区块属性和指定被继承区块来代替。使用 的模板与父区块模板相同,并且继承节的 put 标记中所有设定。子区块的 put 标记设定将覆盖父区块 的设定。 在这种情况,login 区块从 MAIN_REGION 继承而来。MAIN_REGION 区块采用这种格式命名 表示不直接使用,而是用来被 login 继承。我们发现采用这样的约定很有用。 Login 区块与 MAIN_REGION 布局相同,而"content"节内容将被覆盖,因此,可以在主内容区 域( main content area)使用 login.jsp 页面。为了给页面指定一个自定义标题,title 也要被覆盖。 3)在一个区块内的区块 下面的示例是一个主区块(main region)某节的内容,为了编写这种新风格的页面,通过继承 主区块定义了一个区块。下面还包括一个区块示例,它从新风格的页面继承而来,并且覆盖了主内 容区域和 title 节内容。这个示例能反映真实世界的状况。 Show Cart 4)不同类型的节 只有几种类型的节总是可用。除了这些类型外,你还可以使用 controller.xml 文件里定义的任何 类型的 Control Servlet View。在示例中包括有 http、velocity 等。 总是可用类型是:default、region、resource、direct。 direct 是最简单的类型。put 标记的 content 属性值采用这种类型,或直接把 put 标记之 body 中 的字符串插入到模板中。 使用 region 类型可以使得内容能既可以是区块,也可以是作为节内容包含到别的区块中的区块。 resource 类型用来指定当前 webapp 相关资源(一个 JSP、Servlet 或其他 webapp 资源)的 content 属性值。 default 类型是唯一不具 direct 意义的类型。当类型设定为 default(或者没有指定类型)时,将 在区块名列表中查找 content 属性值,如果找到就把该区块作为节的内容。如果 content 属性值不是 一个区块名,那么 content 属性值就被假定是一个资源,并且区块框架会在当前 webapp 中查找该命 名资源。 3、创建区块模板 1)简介 创建一个区块模板最简单的方式是:创建一个使用区块标记库的 JSP(在区块中使用节)。JSPs 模板能象任何其他 JSP 一样使用,但是最好保持他们的简单,而把更复杂内容放到 webapp 资源 (webapp 资源当作节内容包含)里。 正象一个边注,"stack"区块保存在一个 request 属性里,理论上,通过它可以创建一个 servlet 来 作模板,或者为 velocity 开发一个工具,或者把其他模板机制作为区块模板。 2)标签 使用区块的第一步是,象下面的 JSP 一样用标记声明准备使用区块标记库: <%@ taglib uri='regions' prefix='region' %> "regions" uri 是来自 web.xml 文件的一个声明(采用标准方式声明)。 象下面一样,你可以指定在哪里插入每节内容,或在哪里使用 region:render 标记: 在区块标记库中还有其他一些标记,能用来定义区块(象 regions.xml 文件里 XML 元素一样), 并且可以指定到区块的每节内容 四、JSP 标签库指南 1、简介 OFBiz 的 JSP 标记库由一组自定义标记组成,利用这些标记可以很容易地使用 OFBiz 核心框架 的其他构件。 然而有一些基本的条件性、循环控制、i18n 和其他通用标记也能在更通用的标记库如 Jakarta 中 找到。我们也包含这些标记主要是为了使用方便,并且在缺省情况下 OFBiz 的这些标记对用户更友 好。 下面是 OFBiz 的标记表和一些基本参考信息。在文档的其他内容将详细解释每个标记。 标记名 Body 内容 标记类 TEI 类 url JSP UrlTag contenturl JSP ContentUrlTag if JSP IfTag unless JSP UnlessTag iterator JSP IteratorTag IteratorTEI iteratorNext JSP IterateNextTag IterateNext TEI iteratorHasNext JSP IteratorHasNextTag format JSP FormatTag print empty PrintTag field empty EntityFieldTag entityfield empty EntityFieldTag inputvalue empty InputValueTag i18nBundle JSP I18nBundleTag i18nMessage JSP I18nMessageTag i18nMessageArgum ent empty I18nMessageArgumentTa g service JSP ServiceTag param empty ParamTag object JSP ObjectTag ObjectTEI 2、URL 标记 1)ofbiz:url 如果有必要,可以添加控制路径和 URL 编码。这是为了使让同一个 webapp 和 body 可以动态地 链接到页面,意味着包含"/"符号,然后请求名在 controller.xml 文件中定义。参数也将包含在这个标 记里。 这个标记将自动地插入当前 webapp 和控制 servlet 的安装点。 这个标记也可以创建一个安全的(HTTPS)URL(一个指到服务器安全端口的 URL)自动地从 不安全区域进入到安全区域,并且将创建一个简单或非安全的(HTTP)URL 来自动地从站点的安 全区域转到不安全区域。这将在 url.properties 文件里进行配置。 这个标记没有属性。 2)ofbiz:contenturl 为这个页面引用的内容建立一个 URL。这些通常是些静态内容,如存储在不同服务器上的图象。 所以,如果必要的话,可以为静态内容创建一个完整的 URL,同时要考虑内容是被安全地(用 HTTPS) 引用,还是不安全地(用 HTTP)引用。 这个标记这也在 url.properties 文件里进行配置,象核心配置向导里描述那样。这个标记没有属 性 3、条件性标记 1)ofbiz:if 如果该属性有值存在,并且满足设定的 size 和 value 条件时,本标记就可以用来处理标记的 body 内容。 属 必 Exp 描述 性名 须? rVal? na me Y Y 页面、请求、会话的名称,或用来对比的应用属性名(本属性里面的值叫做命名属性) typ e N N 对象的类型。不必要指定这个属性,因为标记在运行时会测试对象的类型。只有当缺省行为无法满足你的需要时才有必要使用 本属性 val ue N Y 如果设定本属性,那么命名属性值将与这个值进行比较。可以使用<%=...%>语法,来引用 JSP 页面里声明的现有对象 siz e N N 如果指定这个值,那么将与命名属性值的大小比较。这适用于大部分类型应用,如 Collections, Maps, Strings 等 2)ofbiz:unless 与 if 标记作用正好相反。换句话说,如果命名属性值不存在,或者命名属性值存在但不满足 size 和 value 条件时,那么 body 内容是被估计出来的。 unless 标记的属性与 if 标记的属性相同。这些属性的详细信息参阅上面 if 标记内容。 4、循环控制标记 1)ofbiz:iterator Iterator 标记用来在一个命名属性的 collection 对象之元素集合中进行循环。独立元素将放在 pageContext 属性里,并且在 JSP 的 Java 上下文(用在 scriptlets 中)中可用。 属性名 必 须? Exp rVal? 描述 name Y N 元素集合名。可以用在页面、请求、会话或应用上下文(本属性里面的值叫做命名属性) propert y N N 把独立元素的属性名和 Java 变量名作为 iterator 进行循环 type N N 集合的元素类型。必须指定才能创建 Java 上下文变量。缺省值为 org.ofbiz.core.entity.GenericValue,它是基于 OFBiz 框架应用中 更常见的对象之一 expand Map N N 如果 expandMap 为真,那么 iterator 元素必须实现 Map 接口。Map 入口的关键字必须是字符串。如果设置把 Map 关键字作为属 性值,那么每个 map 入口将放在 pageContext 内。值必须是 true 或 false。缺省为 false offset N Y 如果设定本属性,那么循环将从设定的偏移值开始,而不是从集合的启始点开始 limit N Y 如果设定本属性,那么只作有限次循环。换句话说,这限制了从 iterator 提取元素的个数 子元素名 多 少 描述 iterateNext 0 对多 下面 说明 iteratorHasN ext 0 对多 下面 说明 2)ofbiz:iteratorNext 在 iterator 标记的集合里取得下一个元素。 属性 名 必 须? Exp rVal? 描述 name N N 用来放元素。缺省是 iterator 标记的 property 属性指定的名 type N N 集合的元素类型。必须指定才能创建 Java 上下文变量。缺省值为 org.ofbiz.core.entity.GenericValue,它是基于 OFBiz 框架应用中 更常见的对象之一 expan dMap N N 如果 expandMap 为真,那么 iterator 元素必须实现 Map 接口。Map 入口的关键字必须是字符串。如果设置把 Map 关键字作为属 性值,那么每个 map 入口将放在 pageContext 内。值必须是 true 或 false。缺省为 false 3)ofbiz:iteratorHasNext 当 iterator 标记还有下一个入口时,才对 body 进行处理。这个标记没有属性。 5、数据表示标记 1)ofbiz:format 根据本标记的 body 值对日期、数值、货币进行格式化。它有一个属性用来指定文本将如何被格 式化。 属 性名 必 须? Expr Val? 描述 typ e N N 确定可用作 body 值的类型将如何格式化。值必须是“货币”、“数值”或“日期”。如果没指定类型将原样输出 2)ofbiz:print 打印来自 pageContext 的一个属性。设定为 null,则打印缺省值;如果连缺省值也没指定,那么 什么都不打印。 属 性名 必 须? Expr Val? 描述 attri bute Y Y 准备打印的属性名 def N Y 找不到指定的属性时,缺省打印的值 ault 3)ofbiz:field 在指定属性里,根据字段显示一个正确格式化的字符串。 属性名 必须? ExprVal? 描述 attribute Y N 打印包含这个字段的属性名 type N N (可选地)指定打印字段的类型。可以是“货币”或任何 Java 字段类型。如果没指定,那么标记将自动确定 Java 类型 default N Y 找不到指定的字段时,缺省打印的值 prefix N N 如果结果信息不为空,那么在字段值前面打印这个字符串 suffix N N 如果结果信息不为空,那么在字段值后面打印这个字符串 4)ofbiz:entityfield 在指定属性的一个实体中,根据字段显示一个本地化正确的字符串。当 prefix 和 suffix 属性值 不为空时,其内容将在字段值之前/之后打印。 属 性名 必 须? Exp rVal? 描述 attr ibute Y N 打印包含字段的实体的属性名 fiel d Y N 打印命名实体的字段名 typ e N N (可选地)指定打印字段的类型。可以是“货币”或任何 Java 字段类型。如果没指定,那么标记将自动确定 Java 类型,并且从实体 引擎的字段定义得到附加类型信息。通过这些信息,货币就会自动地格式化成货币 def ault N Y 找不到指定的字段时,缺省打印的值 pre fix N N 如果结果信息不为空,那么在字段值前面打印这个字符串 suff ix N N 如果结果信息不为空,那么在字段值后面打印这个字符串 5)ofbiz:inputvalue 从实体字段或请求参数给输入框输出的一个字符串。检查 entityAttr 是否存在,根据指定的属性 值来确定使用实体字段还是请求参数。 如果被 tryEntityAttr 属性引用的 Boolean 对象值为 false,那么总是试图使用请求参数,忽略实体 字段。 采用一个非常简单的 toString 来进行格式化。 在上下文属性中根据 entityAttr 里的名称查找到的对象,可以是一个 GenericValue 对象或 Map 接口的一个实现。 属性名 必 须? ExprVal? 描述 field Y N 打印命名实体的字段名 param N N 与这个值相应的 URL 参数名。如果为 null 或空,那么 参数将与字段相同 entityAttr Y N 打印包含字段的实体的属性名 tryEntityAttr Y N 包含一个布尔值的上下文属性名,指定是否试图使用指 定的实体。如果属性名为 false,则使用请求参数;如果属 性名为 true,将使用实体字段值 default N Y 指定实体字段或参数没找到时,打印的缺省值 fullattrs N N 如果 fullattrs 属 性 设 置 为 true , 那 么 将 用 [name="{param}" value="{value}"]代替[value]进行输出。 fullattrs 缺省为 false 6、国际化标记 1)ofbiz:i18nBundle 用来设定和加载一个国际化信息包。如果指定 id,那么包将通过 id 的值来确定。缺省地,所有 信息都从包(由 i18nBundle 标记指定)取得,换句话说 i18nMessage 标记将嵌套在 bundle 标记内。 属性 名 必 须? Exp rVal? 描述 Id N Y 一个 ID,随后可以用在 i18nMessage 标记的 bundleId 属性中,用来引 用包 base Name Y Y 信息包的资源基础名。通常是一个.properties 文件的名,但指定没 有.properties 扩展名 2)ofbiz:i18nMessage 从一个包或特定包(由主键标识)打印一个国际化信息。使用嵌套的 i18nMessageArgument 标 记来接受信息参数。 属 性名 必 须? Exp rVal? 描述 Ke y Y Y 包中信息的主键或 ID bun dleId N Y 从中提取信息的包的 ID。如果不指定,则使用 i18nBundle 包 3)ofbiz:i18nMessageArgument 给 i18nMessage 标记指定一个参数。arguments 将根据指定的顺序编号。 属 性名 必 须? Exp rVal? 描述 v alue Y Y 插入到信息的参数值。可以使用<%=...%>语法来使用 Java 方法上下文变 量(Java method context variable) 7、服务标记 1)ofbiz:service Service 标记采用同步或异步模式调用命名服务。根据 resultTo 属性的设定,结果可以放在页面、 请求、会话或应用里。缺省为页面。 为了设定参数和返回值有关信息,可以嵌套使用 param 标记。将在下面进行描述。 关于服务的更多信息,请看服务引擎指南和服务引擎配置指南。 属 性名 必 须? Exp rVal? 描述 na me Y Y 调用的服务名 m ode N Y 调用服务的方式-同步或异步。必须是 sync 或 async,缺省为 sync res ultTo N Y 指定结果输出的的范围。把服务的 OUT 属性/参数放进本属性中。值必 须是 page、request、session 或 application。缺省是 page 2)ofbiz:param param 标记用来设定传递给服务的参数的相关信息,并且可以用来指定返回值的某些信息。 属 性名 必 须? Exp rVal? 描述 na me Y Y 服务参数名 val ue N Y 分配给 IN 参数的值。可以使用<%=...%>语法来使用 Java 方法上下文变 量(Java method context variable) mo de N Y 指定参数的方式。取值必须是 IN、OUT 或 INOUT。缺省为 IN。如果设 置为 OUT 或 INOUT,那么 alias 属性可以用来覆盖在 service 标记指定范围 里上下文属性的缺省关键字 ma p N Y 为一个 Map 指定一个名字,用来在上下文属性里进行查找。如果设定了 这个属性,那么它将用来在 map 里查找一个值。如果不设定这个属性,那么 属性将用来在上下文属性里查找一个值 attr ibute N Y 指定一个名称供查找之用。它是用来代替 value 属性的,value 属性用来 直接为这个参数指定传递给服务的值。找到的属性值将传递给满足这个参数 的服务 ali as N Y 用来指定主键(把 OUT 或 INOUT 参数放进上下文属性时使用)。缺省 为 name 属性的值 8、其他标记 1)ofbiz:object 使上下文属性(页面、请求、会话或应用)里的对象在 JSP 的 Java 方法上下文里可用,这样它 可以用在 Java scriptlets 中。 属 性名 必 须? Expr Val? 描述 nam e Y N 要创建的 Java 方法上下文变量(Java method context variable)名 prop erty N N 带值的上下文属性名,如果不指定,则与 name 属性相同 type N N 对象的 Java 类型。如果不指定,缺省为 org.ofbiz.core.entity.GenericValue 第二部分、ofbiz 服务 一、服务引擎指南 1、简介 服务框架是 OFBiz 2.0 新增加的功能。服务定义为一段独立的逻辑程序,当多个服务组合 在一起时可完成不同类型的业务需求。服务有很多类型:Workflow, Rules, Java, SOAP, BeanShell 等。Java 类型的服务更像一个静态方法实现的事件,然而使用服务框架就不会局限在 Web 应用 程序中。服务需要使用 Map 传入参数,结果同样从 Map 中返回。这样很妙,因为 Map 可以被 序列化并保存或者通过 HTTP(SOAP)传输。服务通过服务定义来定义并指派给具体的服务引擎。 每个服务引擎 通过适当的方式负责调用服务定义。因为服务没有和 web 应用程序帮定在一起, 就允许在没有响应对象可用时仍可以执行。这就允许在指定时间由 工作调度程序 在后台调用 服务。 服务能调用其他服务。因此,将多个小的服务串联起来实现一个大的任务使重用更容易。 在不同应用程序中使用的服务可以通过创建全局服务定义文件(只能创建一个)或者一个应用程 序的特定服务(这样的服务受限制且只能用于这个应用程序)。 当在 web 应用程序中使用时,服务可以用于 web 事件,这允许时间在服务框架中(stay small?) 并重用现成的逻辑。同样,服务可以定义成 'exportable',允许外部程序访问。目前,SOAP EventHandler 允许服务通过 SOAP 来产生。其他形式的远程访问将来会加入到框架中。 2、Service Dispatcher Service Dispatcher 将需要处理的服务分配给适当的服务引擎,在那里服务被调用。每个 Entity Delegator 都有一个具体的 ServiceDispatcher 。应用程序中如果有多个 delegator 也应该 有多个 ServiceDispatcher 。通过 LocalDispatcher 访问 ServiceDispatcher 。可能有多个 LocalDispatcher 关联到一个 ServiceDispatcher 上。每个 LocalDispatcher 都是唯一命名的并包含 自己的一些服务定义。当创建 LocalDispatcher 的一个实例,一个 DispatchContext 实例也会被 创建并被传给服务引擎。 一个 LocalDispatcher 和一个应用程序关联。应用程序永远不会直接和 ServiceDispatcher 对话。LocalDispatcher 包含一个 API 用来调用服务,这些服务通过 ServiceDispather 发送。然而, 应用程序可能运行于不同的线程和实际的 ServiceDispatcher 中, ,However, applications may be running in different threads then the actual ServiceDispatcher, so it is left to the LocalDispatcher to keep a DispatchContext which among other things keeps a reference to the applications classloader. 3、Dispatch Context DispatchContext 在 LocalDispatcher 实例之上由他创建的。这是运行时 dispatcher 上下文环 境。它包含每个 dispatcher 处理服务的必须信息。这个上下文包含到每个服务定义文件的引用, The DispatchContext is created by the LocalDispatcher upon instantiation. This is the runtime dispatcher context. It contains necessary information to process services for each dispatcher. This context contains the reference to each of the service definition files, the classloader which should be used for invocation, a reference to the delegator and its dispatcher along with a 'bag' of user defined attributes. This context is passed on to each service when invoked and is used by the dispatcher to determine the service's model. 4、服务引擎 是服务实际被调用的地方。每个服务定义都要定义一个引擎名。引擎名定义在 servicesengine.xml 文件中当调用时通过 GenericEngineFactory 实现。支持第三方引擎,但是必 须实现 GenericEngine 接口。参照实体引擎配置指南看定义引擎的详细信息。调用同步和异步 服务是引擎的工作。使用 Job Scheduler 调用异步服务的引擎可以从 GenericAsyncEngine 派生得 到。 5、服务定义 服务定义在服务定义文件中。有全局(global )定义文件,所有服务派遣者都可以调用,同时 也有只和单一服务派遣者相关联单独服务定义文件。当 LocalDispatcher 被创建,他会传递指向 服务定义文件的 Arils 的一个集合。这些文件由 XML 写成,并定义了调用一个服务的必须信 息。请参照相关 DTD 文件。 Services are defined in Service Definition Files. There are global definition files used for all service dispatchers as well as individual files associated only with a single dispatcher. When a LocalDispatcher is created it is passed a Collection of Arils which point to these definition files. These files are composed using XML and defined the necessary information needed to invoke a service. The DTD of this file can be found here. 服务定义有一个唯一名字,明确的服务引擎名,明确定义的输入输出参数。下面是个例子。 Authenticate a username/password; create a UserLogin object SERVICE 元素: name - 服务的唯一名字。 engine - 服务引擎的名字 (在 servicesengine.xml 中定义) location - 服务类的包或其位置。 invoke - 服务的方法名。 auth - 服务是否需要验证(true/false) export - 是否通过 SOAP/HTTP/JMS (true/false) 访问。 validate - 是否对下面属性的名字和类型进行验证(true/false) IMPLEMENTS 元素: sevice - 这个服务实现的服务的名字。所有属性都被继承。 ATTRIBUTE 元素: name - 这个属性的名字 type - 对象的类型 (String, java.util.Date, 等。) mode - 这个参数是输入、输出或输入输出类型。(IN/OUT/INOUT) optional - 这个参数是否可选(true/false) *下划线标注的值是默认值。 由上面可以看出服务名是 userLogin,使用 java 引擎。这个服务需要两个必须的输入参数: login.username 和 login.password。必须的参数在服务调用之前会做检验。如果参数和名字及对 象类型不符服务就不会被调用。参数是否应该传给服务定义为 optional。服务调用后,输出参 数也被检验。只有需要的参数被检验,但是,如果传递了一个没有定义为可选的参数或者必须 的参数没有通过校验,将会导致服务失败。这个服务没有要求输出参数,因此只是简单返回。 6、用法 服务框架内部的用法非常简单。在 Web 应 用 程 序 中 , LocalDispatcher 被保存在 ServletContext 中,ServletContext 可以在事件中通过访问 Session 对象来访问。对不是基于 web 的应用程序仅仅创建了一个 GenericDispatcher。(在 web.xml 中可以找到) GenericDelegator delegator = GenericDelegator.getGenericDelegator("default"); LocalDispatcher dispatcher = new GenericDispatcher("UniqueName", delegator); 现在我们有了 dispatcher ,可以用来调用服务。为了调用这个服务,为 context 创建一个 Map 包含一个输入参数 message, 然后调用这个服务: Map context = UtilMisc.toMap("message","This is a test."); Map result = null; try { result = dispatcher.runSync("testScv", context); } catch (GenericServiceException e) { e.printStackTrace(); } if (result != null) System.out.println("Result from service: " + (String) result.get("resp")); 现在查看控制台看测试服务的回复信息。 *** The test service is located in core/docs/examples/ServiceTest.java you must compile this and place it in the classpath. 安排一个服务在稍晚点时间运行或者重复使用: // This example will schedule a job to run now. Map context = UtilMisc.toMap("message","This is a test."); try { long startTime = (new Date()).getTime(); dispatcher.schedule("testScv", context, startTime); } catch (GenericServiceException e) { e.printStackTrace(); } // This example will schedule a service to run now and repeat once every 5 seconds a total of 10 times. Map context = UtilMisc.toMap("message","This is a test."); try { long startTime = (new Date()).getTime(); int frequency = RecurrenceRule.SECONDLY; int interval = 5; int count = 10; dispatcher.schedule("testScv", context, startTime, frequency, interval, count); } catch (GenericServiceException e) { e.printStackTrace(); } 二、高级特性 服务引擎中加入了很多'高级'特性,在下面有例子、定义及信息。 1、接口 interface 服务引擎实现了在定义服务时可以共享同样的参数。一个接口服务不能被调用, 而是为其他服务继承而定义的。每个接口服务都需要用 interface 引擎来定义: A test interface service **注意到 location 和 invoke 和在 DTD 中定义为必须的,因此用做 interface 时使用空 串。 现在定义一个服务来实现这个接口 A test service which implements testInterface testExample1 服务将会和 testInterface 服务拥有完全一样的需要或可选的属性。任何实 现 testInterface 的服务都将继承其参数/属性。如果需要给指定的服务增加附加属性,可以在 implements 标签后面跟上 attribute 标签。可以在 implements 标签后面重定义某个属性达到 重写一个属性的目的。 2、ECAs ECA (Event Condition Action) 更象是一个触发器。当一个服务被调用时,会执查看是否为 这个事件定义任何 ECAs 。在验证之前,检验之前,事件在实际调用之前,在输出参数校验之 前,在事务提交之前或者在服务返回之前包含进来。然后每个条件都会进行验证,如果全部返 回为真,定义的动作就会执行。一个动作就是一个服务,该服务的参数必须已经存在于服务的 上下文中。每个 ECA 可以定义的条件数或者动作数没有限制。 eca 标签: 属性名 需 要? 描述 service Y ECA 关联的服务名 event Y ECA 在哪个事件上或之前执行。事件有:auth, in-validate, out-validate, invoke, commit, 或者 return。 run-on- error N 当有错误时是否执行 ECA (默认为 false) eca 元素应该有 0 或多个 condition/condition-field 元素,1 或多个 action 元素。 condition 标签 属性名 需 要? 描述 map-n ame N 本服务上下文属性的名字,包含要检验的字段名字组成的 Map。如果没有指定这个域名,就会使 用服务上下文环境(env-name)。 field-n ame Y 要比较的 map 的名字。 operat or Y 指定比较操作符,必须为 less, greater, less-equals, greater-equals, equals, not-equals, 或 contains 当中的一个。 value Y 字段要比较的值。必须为 String,但是可以转换成其他类型。 type N 用来进行比较的数据类型。必须是 String, Double, Float, Long, Integer, Date, Time, 或 Timestamp 当中的一个。 format N 指定当将 String 转换成其他类型(主要是 Date, Time 和 Timestamp)时使用的格式说明。 condition-field 标签 属性名 需 要? 描述 map-nam e N 本服务上下文属性的名字,包含要检验的字段名字组成的 Map。如果没有指定这个域名,就 会使用服务上下文环境(env-name)。 field-nam e Y 要比较的 map 的名字。 operator Y 指定比较操作符,必须为 less, greater, less-equals, greater-equals, equals, not-equals, 或 contains 当中的一个。 to-map-n ame N 本服务上下文属性的名字,包含要与之比较的字段名字组成的 Map。如果为空就会使用上面 的 map-name,如果 map-name 也为空,就会使用服务下文环境(env-name)。 to-field-n ame N 要与之比较的 Map 中要比较的字段名,如果为空默认为上面 field-name。 type N 用来进行比较的数据类型。必须是 String, Double, Float, Long, Integer, Date, Time, 或 Timestamp 当中的一个。如果没指定默认为 String。 format N 指定当将 String 转换成其他类型(主要是 Date, Time 和 Timestamp)时使用的格式说明。 action 标签 属性名 需 要? 描述 service N 本动作(action)要调用的服务名。 mode Y 调用服务的方式,可以是 sync 或 async。async actions 将不会更新 context 即使 result-to-context 设置为 true. result-to-co ntext N action 服务的执行结果是否更新服务的上下文(context),默认为 true。 ignore-error N 是否忽略本 action 服务导致的错误,如果否原始服务就会失败。默认为 true。 persist N action 服务 store/run。可以为 true 或 false。 只有当 mode 属性为 async 时 才生效。默认为 false。 3、服务组 服务组是由多个服务组成的服务集,当调用初始化组服务时应该运行。使用组服务定义文 件定义一个组服务,包含这个组服务所有服务需要的参数/属性。location 属性不需要,invoke 属 性定义了要运行的组服务名。当这个组服务被调用时,组中定义的所有服务都会被调用。 组的定义很简单,他包含一个拥有多个 service 元素的 group 元素。group 元素包含一个 name 属性和一个 mode 属性, mode 用来定义服务怎么执行。service 元素更像 ECA 中的 action 元素,不同之处在于 result-to-context 属性的默认值。 group 标签 属性名 需 要? 描述 name Y 要调用的服务组的名字。 send- mode N 这些服务被调用的方式。可为:none, all, first-available, random, 或 round-robin。默认 为 all。 service 标签 属性名 需 要? 描述 service N 这个服务组要调用的服务名。 mode Y 这个服务被调用的方式。可为 sync 或 async。async 将不会更新 context 即使 result-to-context 设置为 true。 result-to-co ntext N 组服务执行的结果是否更新服务的上下文(context),默认为 false.。 4、路由服务(Route services) 路由服务使用路由服务引擎定义。当一个路由服务被调用时,不会执行调用,但是所有定 义的 ECA 会在适当事件中运行。这种类型的服务不常用,但是通过利用 ECA 服务选项可以路 由( 'route') 到其他服务。 5、HTTP 服务 使用 HTTP 服务是调用定义在其他系统上远程服务的一种方法。本地定义应该和远程定义 一致,但是引擎应该是 http,location 应该是 httpService 事件在远程系统上运行的完全 URL, 方法应该是远程系统上被调用运行的服务名。远程系统必须有挂在 HTTP 服务上公允的 httpService 事件。默认情况下,commonapp web 应用程序有用来接收服务请求的这样的事件。 在远程系统上的服务必须将 export 属性设为 true 允许远程调用。HTTP 服务本质就是同步的。 6、JMS 服务 JMS 服务和 HTTP 服务很相似,除了服务请求被发送到 JMS topic/queue。engine 属性应 该设置为 jms,location 属性因该设置为在 serviceengine.xml 文件中定义的 JMS 服务名(服务 配置)。方法应该是你请求要执行的远程系统上的 JMS 服务名。本质就是异步的。 三、服务引擎配置指南 1、简介 本篇文章描述服务引擎的设置。开始介绍总体思想,然后介绍 serviceengine.xml 的每部分并结 识可用的元素及其用法。serviceengine.xml 文件为不同用途提供有例子,文件位于 ofbiz/commonapp/etc/serviceengine.xml。 服务引擎的设置通过一个叫做 serviceengine.xml 的简单 XML 文件来完成,必须位于 classpath 某 处。 2、验证 authorization 标签设置服务授权需要调用的服务。这个标签只有一个属性 service-name;属 性值应该是用来授权的服务名。默认定义为使用通用 OFBiz userLogin 服务。 3、线程池 工作调度器(job scheduler)异步调用工作/服务。它包含池化的线程和几个请求线程。thread-pool 标签用来配置每个线程怎么操作。有如下属型可用。 属性名 需 要? 描述 Ttl Y 每个请求线程的存活时间。达到时间线程将被销毁。 wait-mill is Y 每个请求线程在检查通过运行前休眠的时间。 jobs Y 每个请求线程在销毁之前可运行的工作数。 min-thre ads Y 线程池中保持的请求线程的最小数。 max-thr eads Y 线程池中将会创建请求线程的最大数。 poll-ena bled Y 为'true'scheduler 就会 poll 数据库来调度工作。 poll-db- millis Y 如果线程池可用,本属性用来定义池化线程运行的频 率。 4、引擎定义 每一个 GenericEngine 接口的实现都需要在服务定义中定义,engine 标签有如下属性: 属 性名 需 要? 描述 n ame Y 服务引擎的名字。必须唯一。 cl ass Y GenericEngine 接口的实 现类。 5、资源加载器 resource-loader 标签用来设置一个指定的资源加载器以在其他地方加载 XML 文件和其他资 源。有如下属性: 属性名 需 要? 描述 name Y 资源加载器的名字。用于其他标签的 'loader' 属性。 class Y 通 用 抽 象 类 org.ofbiz.core.service.config.ResourceLoader 的 扩 展 类 。 可 用 类 包 括 FileLoader, UrlLoader, 和 ClasspathLoader,同类 ResourceLoader 位于同一个包中。 prepend -env N Java 环境属性的名称。用来放在全部路径(full location)比较靠前的地方,在前缀前面。可选。 prefix N 当拼装全部路径(full location)时,放在前面的字符串。可选。如果使用了 prepended 环境 属性,将会置于其后并位于每个指定资源位置的前面。 6、全局服务 global-services 标签用来定义服务定义文件的位置。有如下属性: 属 性名 需 要? 描述 loa der Y 前面 resource-loader 标签定义的资源加载 器。 loc ation Y 指明资源加载器加载资源要使用的文件的位置。 7、服务组 service-groups 标签用来定义服务组定义文件的位置。有如下属性: 属 性名 需 要? 描述 loa der Y 前面 resource-loader 标签定义的资源加载 器。 loc ation Y 指明资源加载器加载资源要使用的文件的位置。 8、ECAs service-ecas 标签用来定义服务条件触发动作定义文件的位置。有如下属性: 属 性名 需 要? 描述 loa der Y 前面 resource-loader 标签定义的资源加载 器。 loc ation Y 指明资源加载器加载资源要使用的文件的位置。 9、JMS jms-service 标签为 JMS 定义服务的位置。 属性名 需 要? 描述 name Y JMS 服务的名字,在服务定义中作为 location 的值。 send- mode Y 向定义的服务发送的模式有:none, all, first-available, random, round-robin, 或 least-load。 jms-service 可以包含一个或多个 server 标签, server 标签有如下属性: 属性名 需 要? 描述 jndi-server-n ame Y 在 jndiservers.xml 文件中定义的 JNDI 服务名 字。 jndi-name Y 在 JNDI 中为 JMS 工厂定义的名字。 topic-queue Y 主题或队列( topic or queue)的名字。 type Y JMS 类型可能为主题或队列( topic or queue)。 username Y 连接主题/队列(topic/queue)的用户名。 password Y 连接主题/队列(topic/queue)的密码。 listen Y 设置是否对主题/队列(topic/queue)起用监听。 在 jndiservers.xml 文件中定义的 jndi-server 应该指出 JMS 客户端 APIs 的位置。根据定义的 JMS 类型来决定使用 TopicConnectionFactory 或 QueueConnectionFactory 。JNDI 名字应该指出在 JNDI 中包含连接工厂实例的对象名字。 三、编程指南 四、编程参考 第三部分、ofbiz 实体 一、实体引擎配置指南 1、介绍 本篇文章描述实体引擎配置。先介绍整体思想,然后分别 entityengine.xml 文件各部分的可用元 素及用法。这个文件为不同的用途提供了一些例子,文件位于 ofbiz/commonapp/etc/entityengine.xml。 通过一个简单的 XML 文件 entityengine.xml 来配置实体引擎。该文件必须位于 classpath 某 处。这个文件用来定义维持服务需要的参数,比如 EJB 服务参数、JDBC 服务参数。他也用来指定 使用那个实体模型、实体组及服务用到的字段类型模型文件,OFBiz 发版默认的配置文件在 ofbiz/commonapp/etc/entityengine.xml 处可找到。 使用实体引擎的每个应用程序都需要通过一个 GenericDelegator 类的实例来达到目的。如果新 产生一个实例,delegator 的名字必须传给静态工厂方法以传给 GenericDelegator 构造函数。delegator 名称用来在 entityengine.xml 中查找相关设置。entityengine.xml 包含将每个实体组映射到这个 实体组 GenericHelper 的配置信息。GenericHelper 是一个接口,必须为每种数据源 (JDBC,EJB,SOAP,HTTP 等等)实现这个借口的一个 Helper 类。 每个特定的 GenericHelper 的设置信息都在 entityengine.xml 文件的 datsource 元素指定。对 JDBC 帮助类,包括数据库连接参数,比如 JNDI 数据源参数或包括数据库驱动程序名、JDBCURL、 用户名、密码的 JDBC 参数。一个 EJB 帮助类将会包含 JNDI 参数,比如上下文相关的 URL、初始 的 上 下 文 工 厂 , 及 URL 包 前缀 (suchasthecontextproviderURL,theinitialcontextfactory,andtheURLpackageprefixes)。 每个 delegator 使用一个实体模型和一个实体模型加载器(entitymodelreader)和一个实体组加载器 (entitygroupreader)。 GenericDelegator 是实体引擎服务中最主要的访问途经。每个服务请求被分派到和实体对应的 帮助类,服务被请求用来和这个实体的实体组通信。OFBiz 实体模型默认的实体组 XML 文件可以在 ofbiz/mmonapp/entitydef/entitygroup.xml.中找到。 2、资源加载器 resource-loader 标签用来配置指定的资源加载器以在其他地方加载 XML 文件和其他资源。有 如下属性: 属性名 需要? 描述 name Y 资源加载器的名称。在其他标签的'loader'属性中使用。 class Y 要使用的类,从抽象类 org.ofbiz.core.entity.config.ResourceLoader 派生来。可用 的有 FileLoader、UrlLoader、ClasspathLoader,和 ResourceLoader 在同一个包里。 prepend -env N Java 环境属性的名称。用来放在全部路径(fulllocation)比较靠前的地方,在前缀前面。 可选。 prefix N 当拼装全部路径(fulllocation)时,放在前面的字符串。可选。如果使用了 prepended 环境属性,将会置于其后并位于每个指定资源位置的前面。 3、JTA 元素 为 支 持 JTA, 实 体 引 擎 需 要 访 问 javax.transaction.UserTransaction , 也 可 以 访 问 javax.transaction.TransactionManager 接口,为使用 TXManager 实 现 的 。 这 些 通 过 类 org.ofbiz.core.entity.TransactionFactory 实现。 ForJTAsupporttheEntityEngineneedsaccesstothejavax.transaction.UserTransactionandoptionall ythejavax.transaction.TransactionManagerinterfaceimplementationsfortheTXManagerbeingused.The seareretrievedthroughtheorg.ofbiz.core.entity.TransactionFactoryclass.Thisclassusestheclassspeci fiedwiththeclassattributeofthetransaction-factory.这个类可能会依据 OFBiz 运行使用的应用程序 服务器或事务管理器的不同而改变。 默认的 TX 管 理 器 使 用 Tyrex ,来自 Exolab(www.exolab.org) 。 Tyrex 的 工 厂 类 是 org.ofbiz.core.entity.transaction.TyrexFactory 。同样 Weblogic 有 一 个 专 门 的 工 厂 类 : org.ofbiz.core.entity.transaction.WeblogicFactory,他需要修改以包含 Weblogic 详细准确的代 码(stuff)并和位于 classpath 上的 weblogic.jar 一起编译。 最有用处的事务工厂类是 org.ofbiz.core.entity.transaction.JNDIFactory 类。这个类使用 entityengine.xml 中附加元素来定位 JNDI 中的 UserTransaction 和 TransactionManager 对象。 user-transaction-jndi 和 transaction-manager-jndi 标签用来指定对象在 JNDI 中的名称以及要 使用 JNDI 服务的名称。两个标签都有两个必须的属性:jndi-server-name 和 jndi-name。一个例 子:UserTransaction 对象的 jndi-name 值为 java:comp/UserTransaction,TransactionManager 对象 的 jndi-name 值为 java:comp/TransactionManager。 4、Delegator 元素 GenericDelagator 通过一个使用一个包含 delegator 名称的 String 类型的参数的工厂类方法产生。 delegator 名称用来在 entityengine.xml 文件中查找 delegator 标签。 属性名 需 要? 描述 name Y Delegator 名称,使用这个名称用来查找这个标 签。 entity-model-re ader Y delegator 使用的 entity-model-reader 名称。 entity-group-re ader Y delegator 使用的 entity-group-reader 名称。 delegator 标签必须包含一个或多个 group-map 标签来为 delegator 从实体引擎加载器知道每组实 体指定一个数据源。delegator 使用这个文件为所有实体分组。当对一个实体的操作发生时,delegator 会查找实体组及和实体组通信的数据源帮助类来执行低层数据源操作。采用这种技术实现应用程序, 就不需要知道对给定的实体由哪个帮助类负责访问,由此可以实现使用一个简单的配置将一个实体 或很多组实体指派给不同的数据源。 5、实体模型 XML 文件 entity-model-reader 标签用来为每个指定的实体模型加载器。标签的 name 属性用来指定实体 模型加载器的名字。每个加载器可能从多个资源中加载,其中每个资源使用标签中的 resource 标签 来指定。 每个 resource 标签有两个必须的标签:loader 用来指定使用哪个资源加载器,location 指定了资 源加载器内部加载资源使用的位置。 6、实体组 XML 文件 entity-group-reader 标签用来设置每个指定的实体组加载器。标签的 name 属性用来指名实体 组加载器的名字。实体组加载器使用一个单独的 XML 文件来获取实体组的映射信息。 这个标签有两个需要的属性:loader 用来指定使用哪个资源加载器,location 指定了资源加载器内 部加载资源使用的位置。 7、字段类型 XML 文件 field-type 标签用来配置每个指定的字段类型。标签的 name 属性用来指名字段类型的名字。 实体组加载器使用一个单独的 XML 文件来获取字段类型信息。 这个标签有两个需要的属性:loader 用来指定使用哪个资源加载器,location 指定了资源加载 器内部加载资源使用的位置。 8、数据源元素 很多数据源都可以使用 datasource 标签设置,每个数据源对应一个数据源。这个标签有如下属 性,很多包含如下字属性。 Manydatasourcescanbeconfiguredusingonedatasourcetagforeachdatasource.Thistaghasthefollowinga ttributes,andmaycontainthefollowingsub-elements: 属性名 需要? 描述 name Y 数据源的名字 helper-class Y 可能有许多类型的数据源帮助类,主要的一个是 JDBC/DAO 帮助类。 可以实现 org.ofbiz.core.entity.GenericHelper 接口编写自己 的帮助类。JDBC/DAO 帮助类就是 org.ofbiz.core.entity.GenericHelperDAO。 field-type-name Y 要使用的字段类型的名字。必须和前面 field-type 标签定义的字段类型 名字匹配。 check-on-start N 启动时是否检查数据源?必须为 true 或 false,默认为 true。 add-missing-on-start N 当启动时检查数据源时,是否要添加不存在的实体?必须为 true 或 false,默认为 false。 use-foreign-keys N 对"one"类型的关系,是否使用/创建外键?必须为 true 或 false,默认 为 true。 use-foreign-key-indices N 是否对外键使用/创建索引(也就是外键上的索引)?注意到这个属性起作 用并不需要创建外键,索引只为定义为"one"类型的关系创建。必须为 true 或 false,默认为 true。 check-fks-on-start N 启动时是否检查外键,当需要时是否添加?必须为 true 或 false,默认 为 false。有些数据库会花费很长时间,并且不会返回所有外键列表,结果导 致重复的外键会添加到数据库中。 check-fk-indices-on-sta rt N 启动时是否检查外键索引,当需要时是否添加?必须为 true 或 false, 默认为 false。 use-pk-constraint-nam es N 是否为主键使用约束名字?有些数据库对此有问题,但是如果给个名字 就回工作正常。必须为 true 或 false,默认为 true。 constraint-name-clip-le ngth N 指定约束名的最大长度。超出的将被裁掉。这样做时当心重复的约束名。 必须为整数,默认为 30。 fk-style N 指定外键语法(syntax)类型:命名为外键约束或仅仅外键。大多数数据 库使用 name_constraint 语法,但是 SAPDB 这样做会出现异常,可能还 有其他数据库也会这样。必须为"name_constraint"或"name_fk",默认为 "name_constraint"。 use-fk-initially-deferred N 指定在许多数据库中,当创建外键时使用 INITIALLYDEFERRED 选项 是否可用。不是所有数据库都支持这个选项。当可用且数据库支持时,外键 检查只有在事务提交时才会进行,这和在事务中检查外键恰恰相反。必须为 true 或 false,默认为 true。 join-style N 指定当在 view-entity 操作中做表联接时使用的语法。许多数据库采用 标准的 ANSIJOIN,但是在此之前 theta 联接更通用。支持两个 theta 连接 类型:Oracle 和 MSSQL。必须为"ansi","theta-oracle"或"theta-mssql"。 默认为"ansi"。 子元素名 多 少? 描述 sql-load- path 0 到 多 个 用来指定目录的完整路径列表,用来查找 XML 和 SQL 文件在 install 页导入到数据源中 UsedtospecifyalistoffullpathstodirectoriesthatwillbesearchedforXMLandSQLfilestoimportintothedatasourcebytheinstallpageintheWebToolswebapp.Eachtaghastwoattributes:pathforthepathlocation,andprepend-envtooptionallyspecifyaJavaenvironmentpropertytoprependtothespecifiedpath. inline-jd bc 0 或 1 用来指定由 Tyrex 使用的 JDBC 参数或者当 Tyrex 不可用时直接加载驱动程序(非常慢)。必须为 DAOHelper 指定 inline-jdbc 或者 jndi-jdbc。 jndi-jdbc 0 或 1 用来指定指定 jndi-server 和 jndi-name 来从 JNDI 获取一个连接或 XAConnection。必须为 DAOHelper 指定 inline-jdbc 或者 jndi-jdbc。 ANY 0 或 1 datasource 标签中的任意标签,来为其他 GenericHelper 的实现类指定参数。只有在 DTD 文件中作了修改来描述他们,在加载时才会检查这些标签。 inline-jdbc 标签有如下属性: 属性名 需 要? 描述 jdbc-driver Y 数据库 JDBC 驱动程序类。 jdbc-uri Y 用来指定数据库的类型和位置的 URI。 jdbc-usern ame Y 连接数据库用的用户名。 jdbc-pass Y 用户名的密码。 word isolation-le vel N 用来指定 Tyrex 事物隔离级别(isolationlevel),标准 JDBC 事务隔离级别 有: 'ReadCommitted' 'ReadUncommitted' 'RepeatableRead' 'Serializable'(default) jndi-jdbc 标签有如下属性: 属性名 需 要? 描述 jndi-server-n ame Y 要用的 JNDI 服务的名字。 jndi-name Y 连接或 XAC 连接对象在 JNDI 中的名 字。 从 JNDI 重新得到的数据源应该使用连接池技术并可使用事务,如果 JTA 事物启动的话。这可 能是 DataSource 对象或者 XADataSource 对象。 如果没有指定 JNDI 元素,连接工厂就会从 entityengine.xml 文件中取到,并试图使用 Tyrex 作 为事物管理和连接池。如果 Tyrex 不可用实体引擎将会在每次接收到请求时创建一个连接,这时将 会有警告信息。 二、实体应用指南 ofbiz 实体引擎是管理实体和实体数据的一系列工具和模式。在这里实体是指 用 field(字段)和 relation(与其他实体的关系)定义的一组数据。实体定义依 据标准 RDBMS(关系数据库管理系统)中的 Entity-Relation 模型来建立。实体引擎 的目标是简化实体数据的使用,包括实体的定义、维护、优化、及和实体有关的功 能。 实体引擎使用了一些能够被大多数应用系统所识别的应用模式、中间模式(集 成),ofbiz 中也使用了许多表现层的模式,这些模式只在 servlet 控制器中使用, 实体引擎不使用。实体引擎中可使用的模式包括:业务委派(Business Delegate), 数值对象(Value Object),复合实体( Composite Entity), 数值对象集合器(Value Object Assembler),服务定位器 Service Locator, 数据访问对象(Data Access Object),这些模式的不断完善和其他模式的实现都在计划之当中。 这些模式在 sun 公司 Alur, Crupi,和 Malks 所写的 “Core J2EE Patterns” 中有描述,你也可以在网上,通过查找这两位作者发表的文章获取相关信息。 另外TheServerSide公司的Floyd Marinescu将要发表的"EJB Design Patterns"书中,也有 几种可用的模式。它包括数据传递HashMap和概括属性访问,对于产生唯一关键字,我们采用 一种称之为"Ethernet Key Generation"的模式,作为我们的初试模式,它具有冲突检测机制,可 以保证多台服务器使用同一个数据库以一个数据库独立的方式获取一组唯一性的关键字。 实体引擎的主要目标是,尽量除去交易性应用系统中许多地方要使用的、特殊 的实体持久化代码。我们承认对于报表类,或者与之相似的系统,提取出的持久化 的方法有所不同,但对于每天都要做的交易性应用系统,实体引擎可以节省大量的 开发工作量,并且大量地减少系统中与数据持久化有关的错误。这类应用系统包括 电子商务中的帐务系统、配送系统、库存管理系统、人力资源系统等等所有系统。 这些工具可用于报表系统和分析系统,但该工具不包含适合于,经常出现的由用户 定义的、使用方便的查询。 为了得到尽量小而精的数据实体代码,所有的数值对象都为数据汇集 generic, generic 是一个将数据实体的域值按照名称进行存取的 Map 对象。 依据字符串名称,使用域(fields)方法对实体域进行存取,以的得到或存储 所需要的数据,并鉴别字符串名称是否实体中的一个域。这种灵活的坏处是减少了 实体引擎和应用程序之间的配合。这种配合在特定的 XML 文件中进行说明。 取代编写特定的代码,实体的定义将从 entitymodel*.XML 文件中读入,并由实 体引擎强制规定应用系统和数据源之间的访问规则,这里所指的数据源可以是数据 库,也可以是其他的数据源。使用 XML 定义的实体中,每一个实体都有唯一的名称 和域,他们和数据库的表和字段一一对应。在实体的定义中,每个域都有一个与数 据库字段相类似的类型说明,根据这个类型可以查处它的 java 类型和数据库类型。 在实体文件 entitymodel*.XML 中还定义了实体和实体的关系。关系定义中包含一个 相关连的数据库表和关系类型(one or many),及关系的关键字值对。关系还可以 定义一个标识,以区别实体关系中的其他关系。 在一个抽象层次上使用实体引擎,实体的应用代码可以很容易的建立和修改。 这些代码使用实体接口 APIs 与实体进行交互,这些实体可以有多种方式进行持久 化,而不需要修改应用代码。比如只要修改应用程序的实体配置文件,就可以将通 过 JDBC 的数据库连接,转换到 EJB 服务器和实体 Bean 来进行数据的持久化。也可 在同一个框架中使用用户自己特定的数据源,比如 legacy systems over HTTP,或 者使用一些自定义的消息服务。 实体模型 The first thing to do when starting work with a new entity is to define or model that entity. In the OFBiz Entity Engine this is done through two XML files, one for entity modeling and the other for field type modeling. There are links to the XML DTDs for these documents in the Related Documents section above. The reason that these two files are separated is that field type definitions can be database specific. In other words, for a given entity model definition various field type definitions may exist for different databases. When a persistence server is defined the field type model XML file to be used for that server is specified. The main entity model XML files for Open For Business can be found in ofbiz/commonapp/entitydef/. Originally all of the entities were in the entitymodel.xml file, but now they are separated into various files in addition to the entitymodel.xml file. They are all named after the following pattern: entitymodel_*.xml The MySQL field type model XML file for Open For Business can be found in ofbiz/commonapp/entitydef/fieldtypemysql.xml. There are other database specific field type files for Postgres, Hypersonic, Oracle, et cetera. From the entity model files and the field type files database tables can be created automatically through the checkDataSource routine on the GenericHelper inteface. This can be done automatically on startup or through the tools in WebTools. While tables can be created automatically, data must be loaded from data files. These files can be either SQL scripts or XML Entity Engine files. All of the type information and other pre-loaded information such as statuses, enumerations, geo data, etc., are located in XML Entity Engine files in ofbiz/commonapp/db/. These files can be located and loaded automatically by the install.jsp page in WebTools. This page looks in the directories specified in the entityengine.xml file for a given entity group name and finds all .xml and .sql files. These are listed and confirmation is requested by the page. Clicking on the Yes, Load Now link will cause these files to attempt to be loaded. Error messages will appear in the page as well as on the console or in log files. Data files can also be loaded one at a time by specifying the full path of the .sql or .xml file in the load a single file form. While on the topic, XML Entity Engine files can also be imported and exported through the import & export pages in WebTools. As mentioned above an entity consists of a set of fields and a set of relationships to other entities. In the XML entity definitions each of these are specified in addition to attributes of the entity itself such as the entity name, the corresponding table name, the package name for the entity, and meta data about the entity such as the author, a copyright notice, a description, and so forth. Here is an example of an XML entity definition: This is a pretty simple entity that demonstrates a few small points. The meta data at the top is all optional, and if left unspecified will default to meta data defined for the entire entity model file. For example the "description" element was left out, so the ModelReader will use the description specified at the top of the XML file if one exists. The package-name is used to organize the entities, and specify a default location for any code that would be entity specific. This becomes extremely useful when you have an entity model with hundreds of entities. Notice that while the field primaryKeyFieldOne has a column name specified, none of the other fields do. The col-name and the table-name elements are optional. They can be derived from the field name or entity name through widely used conventions. These conventions dramatically simplify the definition of entities. Table and column names are written in caps with underscores separating the words like SAMPLE_ENTITY. Entity and field names are written using the Java conventions for class and field names where all letters are lowercase except for the first letter of each word, which is uppercase. Entity names correspond to Java classes to the first letter is upper case but field names correspond to member fields of a class so the first letter is lower case. For example: SampleEntity and fieldOne correspond to SAMPLE_ENTITY and FIELD_ONE. Multiple primary key columns can be specified using multiple tags specifying the names of the primary key fields. Field types are specified using a type string, which is defined in a fieldtypemodel XML file specified by the fieldtypemodel.dtd XML Data Type Definition. Each type maps to a Java type and an SQL type. Because of this separation different fieldtypemodel XML files can be specified for different databases allowing an entitymodel XML file to work with the various databases. In addition, validators can be specified for a field type or for any field which denote that the named validator should be called when data is input. These validators are defined in the class org.ofbiz.core.util.UtilValidate and follow the definition pattern: [boolean isValid(String in);]. Multiple relations can exist for each entity. Each relation is either of type 'one' or of type 'many' depending on the cardinality of the relation. If the type is 'one' then the key-map elements must fully specifiy the primary key of the related entity. If the type is many, the key-map elements do not need to have any relation to the primary key of the related entity. If multiple relations to the same related entity are used for a given entity, a title must be specified to make the relation name unique. By this convention the relation name is defined as [title][rel-table-name]. For the two SampleEntity relations their names are SelfSampleEntity and AllOneSampleEntity. For OtherSampleEntity there is no title, so the relation name is simply OtherSampleEntity, or the name of the related entity. Key Maps are used to define how the related entity will be looked up. Each key map specified a field name (field-name) and a related field name (rel-field-name). If the two column names are the same, the rel-field-name does not have to be specified. View Entity Modeling 特别地,当一个实体只有一个关系的时候,我们可以定义一个虚拟的视图 virtual,view。这个视图和 otalce 及其他常用的关系数据库的视图一致,视图实 体允许你联合、连接两个实体去建立一个新的实体。新视图实体中域的名称使用原 来实体中域的别名。视图实体中所使用的实体,使用 view-links 根据实体的关键字 将实体连接成 key_map,这和上面的关系定义类似。 视图数据和常规的实体一致,他们包含有名称、包、描述、版本、作者等。但 不包含数据库表的名称,因为他们没有对应的实体源。视图实体和其他的实体一样 也必须在实体组 XML 文件中进行定义。 Normal entities that will become part of the view entity are referred to as member entities. Each member entity is given an alias and is referred to by that alias for the rest of the definition. This allows a single entity to be linked in multiple times. Rather than specifying fields with a view entity you specify aliases. Each alias is effectively a field in usage and is defined by being mapped to a field on an aliases member entity. If the field name is that same as the alias name, there is no need to specify it. In other words if no field name is specified the alias name will be used as the field name to map to on the member entity with the specified entity-alias. View links are used to specify the links between the member-entities of the view. They link one entity-alias to another and use key-maps just like relations. Here the field-name specifies the name of the field on the aliased entity and the rel-field-name the field on the related aliased entity. As with many other things, the rel-field-name is optional if it is the same as the field-name. View Entities have primary keys just like normal entities. The Entity Engine automatically determines which aliases are primary keys by looking at the field that they alias. The primary key for a view entity should include all primary key fields of each member entity of the view. This means that all of the primary key fields must be aliased, but fields that are used to link the entities need only be aliased once. For example, if an OrderHeader entity has an orderId primary key and an OrderLine entity has an orderId primary key and an orderLineSeqId primary key, and the orderId was the mapped key that links the two entities, then the view entity would have two primary keys: orderId and orderLineSeqId. In this case orderId would only be aliased once since by definition the orderIds from each entity will always be the same since that is how the entities are linked (or joined). Relationships are specified the same way with view entities as they are with normal entities. That key-map attributes are still called field-name and rel-field-name but in the case of view entities the field name is actually the alias name that will be looked up. The Entity Engine API The Entity Engine classes in the package org.ofbiz.core.entity define the API used to interact with Entity Engine entity data. From a users point of view only three classes really need to be understood. They are GenericDelegator, GenericValue and GenericPK. The GenericDelegator class, usually used with the instance name 'delegator', is used to do create, find, store and other operations on a GenericValue object. Once a GenericValue object is created it will contain a reference to the delegator that created it and through this reference it knows how to store, remove and do other operations without requiring a program to invoke methods on the delegator itself. I've been trying to think of how best to present information about this API, but short of writing a number of documents about the specific usage of each piece, there is not much that is useful that I could write here. I recommend reading through the JavaDocs for the core module, specifically the org.ofbiz.core.entity package, and browsing through the eCommerce and other applications which make heavy use of this API. A few quick notes to help you get started might be in order. Rather than trying to construct a GenericValue or a GenericPK yourself, you should use the makeValue and makePK methods on the GenericDelegator. These create an object without persisting it and allow you to add to it and create or store it later using their own create method, or calling the create method on the delegator object. Value instances can be retrieved from the database with the findByPrimaryKey methods, or a collection can be retrieved using the findAll or findByAnd methods. There are two main types of findByAnd methods. Each type has a number of variations that may include the use of a cache, and may accept an orderBy field list. The two main types accept different field lists. One accepts a Map of fields and finds entities by anding together expressions where each named field must equal the corresponding value in the map. The other type of findByAnd accepts a list of EntityExpr objects which are used to specify small expressions that will be anded together. Each EntityExpr specifies a field name, an operation, and a value for the field. To create (or insert) values into the database, use the create method on the GenericValue or GenericDelegator objects. To store (or update) existing values, use the store method on the GenericValue or GenericDelegator objects. For storing multiple entities the GenericDelegator class has a method called storeAll which takes many GenericValue instances and stores them in the same transaction. Actually, to say that it stores them is incorrect. It checks to see if they exist and if so does an update, if not it does an insert. This may be optimized in the future for speed by allowing you to specify whether it should be inserted or updated based on prior knowledge of the existence if that entity. Note that this is a DIFFERENT behavior that the store method, which just does an update. Removal of entities is done through the remove method on either the delegator, or the GenericValue. The EntityCondition object Originally the EntityExpr object was meant to be nestable to allow for more flexible queries, but was never completed. Also, even if you could next it the types of queries you could run would be limited because you couldn't have two ANDs (for instance) inside a set of parenthesis. To address these issues, and complete the EntityExpr implementation the EntityCondition abstract object has been introduced along with the EntityExprList and EntityFieldMap objects which both extend EntityCondition. The EntityExpr object has also been changed to extend EntityCondition. The EntityExprList and EntityFieldMap objects are pretty simple, they are created with a List or Map, respectively, and an EntityOperator to specify operator used to join the members of these containers, generally AND or OR. The EntityExpr class now has two primary constructors: one for comparing a field to a value (the String, EntityOperator, Object constructor), and one for comparing two EntityCondition objects (the EntityCondition, EntityOperator, EntityCondition constructor). A findByCondition method is now available that accepts an EntityCondition argument (as well as some other useful arguments) and this EntityCondition can be an EntityExpr, EntityExprList or EntityFieldMap. The code inside the Entity Engine has changed somewhat because these EntityCondition objects now created their own WHERE clauses. This is a nice architectural point because other custom EntityConditions could also be created and used as desired by a savvy developer. The EntityListIterator object The EntityListIterator class implements the ListIterator interface for convenience, but also has other methods that are necessary for it's operation, like a close() method for when you are finished. This object allows you to iterate through query results efficiently in both directions by keeping a reference to the ResultSet that comes back from the query. This makes is possible to use the cursor feature in the database and especially for large queries uses memory much more efficiently. This object constructs GenericValue objects on the fly rather than creating a bunch all at once, so if you need to export the results of a huge query to a file or something, it can be done without a massive amount of memory. JTA Support The Entity Engine JTA Support is simple to use, but has a few complications in configuration. The support runs through an API and a Factory class so that no direct contact with the particular JTA implementation is necessary. The TransactionFactory class can be used to get the two main objects needed for JTA use: UserTransaction and TransactionManager. The current implementation supports the Tyrex JTA/JTS implementation. To use a different implementation simply change the TransactionFactory class, everything else uses that. That's the tricky configuration part, if you aren't using Tyrex. For Tyrex make sure a domain configuration XML file called tyrexdomain.xml must be on the running classpath. To demarcate transactions you can use the TransactionUtil class which wraps the UserTransaction class and only throws GenericEntityExceptions, well, and runtime exceptions. The basic methods needed are begin(), commit(), and rollback(), but the rest are included and can be very useful. A transaction is attached to the current thread so it is not necessary to pass it around all over the place. After beginning a transaction make sure it is always either committed or rolled back. This is normally done by committing at the end of a try block and and rolling back in each catch block. You can also use the standard UserTransaction object by getting one from the TransactionFactory. Core Web Tools The WebTools web application contains a number of useful tools for working with the entity engine. These include a cross linked reference to the entity definitions, a tool for editing entity and relation definitions, and a JSP which acts as an XML template and also saves the XML entity definitions to their corresponding files. There is also a JSP that acts as a front end to the routines which checks the current state of the database table definitions and reports any differences. Where possible tables or columns which are missing can be added to the database. This is the same routine which optionally runs when the server loads and optionally creates missing tables and columns. The entity code generator has been removed from the project, or deprecated if you will, because of the next generation entity tool which is the entity engine described herein. There are still some occasions where the use of templates to create entity specific code or other text is still useful, and in fact, necessary. One example of this is the JSP which creates the XML for the entity definitions from those definitions, and is used for writing out the XML after entity definitions have changed. Other uses for templates include manually generating database specific table creation SQL and quick start JSPs and event handlers which allow for finding, viewing and editing entity specific data. These can be used as starting points for task specific applications. Where entity data editing is not task specific, but instead is entity specific, the Entity Data Maintenance pages in WebTools can be used. They are dynamic pages that rely on the in memory entity definitions to create forms for the entering of data, and event for handing the entered data (including validators specified in the entity definition), and finding specific entities by any of the fields on the entity, or finding any relation entity instances for a given entity instance. For instance, when viewing the OrderHeader entity all of the relations to that entity can be viewed as well, including links to edit and view them. These related entities would include OrderType (one relation), OrderLine (many relation), and many others. Data in the database can be imported from and exported to Entity Engine XML files in the import and export pages. Importing data causes corresponding entity instances to either be created or updated, depending on whether or not they already exist. The Export page allows you to specify which entities you want to export the data for by using a big list of check boxes, one for each entity. For more granular control over exported data, the Entity Data Maintenance pages mentioned above would be the place to look (not yet finished though...). 1、介绍 二、编程指南 在 ofbiz 中对实体的访问(数据库)是通过 delegate 对象来进行的,而 delegate 对象是 GenericDelegator 类的一个实例,他包含有关实体操作的方法和属性。 1、delegator 对象的获取 在 JSP 中使用 在 severlet 或 event 中使用 GenericDelegatordelegator=(GenericDelegator)request.getAttribute("delegator"); 通过一个已知的数值对象获取 delegator,方法为 GenericDelegatordelegator=userLogin.getDelegator(); 手工建立 GenericDelegatordelegator=GenericDelegator.getGenericDelegator("default") 2、 数据访问 1)以 delegate 对象进行数据访问 插入使用 create 方法,在插入之前一般要用 makeValue 方法,建立一个数值对象, 然后进行插入,典型的语句为 GenericValuepartyRole=delegator.makeValue("PartyRole",UtilMisc.toMap("partyId",partyId,"rol eTypeId",context.get("roleTypeId"))); partyRole.create(); 删除 remove 方法,remove 一般的用法为 partyRole=delegator.findByPrimaryKey("PartyRole",UtilMisc.toMap("partyId",partyId,"roleTyp eId",context.get("roleTypeId"))); partyRole.remove(); 使用 store 方法,包括(store 和 storeall)典型的语句为 delegator.storeAll(storeAll); store 存储一个数值对象,而 storeall 存储用 List 组织起来的一组数值对象。 Storeall 的用法说明: ----------------------------------- 通过 findByPrimaryKey 在一个实体中查找出符合条件的一条数值对象 GenericValue orderHeader = delegator.findByPrimaryKey ("OrderHeader", UtilMisc.toMap("orderId", orderId)); 对数值对象中的某个值进行修改 orderHeader.set("statusId", statusId); 创建另外一个不同实体的数值对象(当然也可以采用相同的实体)。 changeFields.put("orderStatusId", delegator.getNextSeqId("OrderStatus").toString()); changeFields.put("statusId", statusId); changeFields.put("orderId", orderId); changeFields.put("orderItemSeqId", orderItem.getString("orderItemSeqId")); changeFields.put("statusDatetime", UtilDateTime.nowTimestamp()); GenericValue orderStatus = delegator.makeValue("OrderStatus", changeFields); 使用 setPKFields ,setNonPKFields 建立一个数值对象 roleType = delegator.makeValue("RoleType", null); roleType.setPKFields(context); roleType.setNonPKFields(context); roleType = delegator.create(roleType); 将数值对象,放到 List 中 List toBeStored = new ArrayList(); toBeStored.add(orderHeader); toBeStored.add(orderStatus); 将数值对象,存储到数据实体中 delegator.storeAll(toBeStored); ----------------------------------------- 查找使用 find 方法,包括 findall 、 findAllByPrimaryKeys 、 findByAnd , findByCondition、findByLike、findByOr、findByPrimaryKey、findListIteratorByCondition 依据某数值对象的关系,查找关联信息,可以使用 getRelated 方法。包括 getRelated、 getRelatedByAnd、getRelatedDummyPK、getRelatedOne、getRelatedOrderBy 2)依据数值对象进行访问 在现有的数值对象上可以进行下列操作, 根据关系查找关联信息 getRelated,包括 getRelated、getRelatedByAnd、 getRelatedDummyPK、getRelatedMulti、getRelatedOrderBy。 刷新本数值对象 refresh 保存本数值对象 store,主要用于修改后的保存 删除数值对象 remove,包括删除本数值对象 remove 和删除某个关联的数值对 象 removeRelated 在现有数值对象上的操作是通过调用 三、编程参考 第四部分、工作流 一、工作流引擎指南 1、简介 OFBiz 工作流引擎基于 WfMC 和 OMG 规范(看相关文档可以了解这些规范的信息)。它是服务 框架的成员之一,与 EntityEngine 紧密集成。工作流引擎把 entitymodel_workflow.XML 文件找到的 实体用作定义信息,而把 entitymode_workeffort 文件找到的实体用作运行时刻存储。一个流程或任 务(activity)都是实时的。因此,工作流引擎不是运行在一个线程上,而只是简单的一组 API 和通 用对象在处理流程。当工作流发生改变时,引擎马上就处理这个变化,处理结束后,引擎返回结果。 因此,当一个应用垮了(或系统重启),重启时工作流接着从停下的位置继续执行。 工作流引擎不是为一个 web 站点的处理流程而设计的。这是一个普遍的错误概念。web 站点的 流转由控制 Servlet 处理。工作流是为了达到一个目标而进行的手动和自动任务(activitie)处理。 OFBiz 工作流引擎把 XPDL 作为自己的流程定义语言。这是一个开放的标准,十分灵活。在 XPDL 规范没有明确或留给厂商实现的地方,我们在 XPDL 扩展节说明。 2、XPDL 扩展 工作流使用 XPDL 进行设计。本文档不讨论 XPDL 细节,这些内容在 WfMC 有详细解释,请看 相关文档。 在 WfMC 规范里,留了很多属性(attribute)给厂商使用。下面是我们在使用的 XPDL 扩展: 任务(ACTIVITY)扩充属性 acceptAllAssignments-这个扩展属性告诉工作流引擎在一个任务开始之前 的所有委派都必须接受。当一个任务有多个参与者,并且要求在任务开始前每个参与 者必须接受自己收到的委派时,本属性将会很有用。(缺省值:NO) completeAllAssignments-类似于 acceptAllAssignments 属性,告诉工作流引 擎在一个任务完成之前的必须完成所有的委派。例如,在某个薪资任务中,要求所有 的员工必须提交自己的时间表后才能进行下一步动作。(缺省值:NO) limitService-当定义一个任务时,可以设定一个时间限制,表示分配给该任 务时间总和。一个服务可以被设定成当某个任务没有在规定的时间完成时启动起来。 (缺省值:不设定) limitAfterStart-这个属性告诉工作流引擎,限制检查在任务开始之后发生。 当把属性设定为 NO 时,限制从委派创建开始检查,而不是从任务开始。(缺省值: YES) canStart-告诉工作流引擎,如果本扩展属性设定为 yes 的任务都能够初始化 工作流。因此,工作流不但可以从第一个任务开始启动,而且可以从任何本属性设定 为 yes 的任务开始启动。 实现/工具扩充属性和实际参数 runAsUser-这个扩展属性将告诉 TOOL(如果是过程)以这个用户来运行定义的 服务。取用户的 userLoginId 作为属性值。缺省情况下不传递用户登录对象。 ActualParameters: 表达式:可以使用 ActualParameters 里的表达式对上下文属性与服务参数 进行映射。你必须用 expr:来注释一个表达式。例如: expr:orderNumber=orderId先将上下 文属性 orderId 映射到一个叫 orderNumber 的内存缓冲变量,然后,如果你接着写: orderNumber表示参数 orderNumber 将被传递到值为 orderId 的服务中。 工作任务 ID:你可以把 workEffortId 作为一个实际参数,这个参数映射为当 前任务的主关键字。 缺省启动任务(ACTIVITY) 缺省地,一个工作流从任务列表的第一个任务开始启动,因为列表中的第一个任务为缺省启动 任务。这意味着当一个工作流正常启动时,将首先运行第一个任务,然后在根据路由(transition) 继续流转。最常见的方式是调用一个工作流时,可能从许多不同的起点开始调用。在这种情况下, 采用客户端 API 来启动合适的任务。路由(transition)将从这个起点继续下去。只有任务的'canStart' 扩展属性设定为 yes 的任务才可以初始化一个工作流。 3、客户端 API OFBiz 工作流引擎的客户端 API 由一组全局服务和 factory 类组成。这个 factory 类叫 org.ofbiz.core.workflow.WfFactory,能用来创建一个工作流构件(process/activity/assignment)的新实 例,或者定位一个已存在的实例。实例被储存在工作任务实体中,并且在必要时装入内存。在 org.ofbiz.core.workflow.WorkflowServices 里的这组全局服务被设置能够很容易生成、接受和完成委派 /任务。虽然使用这些服务不是必须的,但是强烈建议你使用。复习 JavaDocs 可以让你了解到这些类。 工作任务(workeffort)的 web 应用就是一个工作流 web 客户端的例子。通过这个界面,你可以 接受到当前委派给你的组别/角色的工作委派,并可以编辑其状态。如果你想尝试一下,请单击 http://localhost:8080/workeffort.。 4、工作流任务 手动和自动任务的组合让一个工作流变得功能十分强大。OFBiz 工作流引擎当前已经实现无 (NO)、路由(ROUTE)、工具:过程(TOOL:Procedure)、子流程等类型任务。工作:应用 (TOOL:Application)类型任务目前仍未实现,但是将来版本会实现。 无(NO)-如字面意义显示一样,表示没有任务。这用来描述一个'手动'任务。 路由(ROUTE)-一个路由活动是利用路由(transition)简单地转移到其他任务 中。 工具(TOOL)- 应用(Application): 调用一个外部应用程序-目前暂未实现。 过程(Procedure): OFBiz 工作流引擎把过程当作一个内部服务调用来实现。 子流程(SUB-FLOW)-同步或异步地创建和运行一个子流程。 当前 OFBiz 工作流引擎版本并没有实现 LOOPS,但将来版本会实现。 5、用法 在使用工作流引擎之前,你必须先准备一个预计调用的流程,流程要按 XPDL 格式进行设计。 一旦流程设计完成,需要把 XPDL 文件导入到工作流实体中。你可以通过 web 界面的 webtools 工具 来完成这些工作。工作流被导入后,就可以被执行了。调用一个工作流最容易的方法是把它定义成 一个服务。为工作流创建一个服务,并把引擎类型设定为'workflow',然后就可以象运行任何其他服 务一样启动一个工作流。 6、文档注释 我们知道本文档不够详细,为此我们向你道歉。然而,我们大部分时间都在写代码,因此,不 能全力写文档。虽然我们也认识到好文档是有必要的,但是我们的资源终究有限。如果你或了解的 其他任何人愿意花时间来为 OFBiz 写文档,请根据本文档顶部的 e-mail 地址联系我们。多谢。 二、编程指南 1、 建立流程状态实体 开发一个流程首先要建立一个,记录流程状态的实体,比如在 ofbiz 的流程例子中 建立了一个 OrderHeader 的实体,这个实体最起码要有三个基本属性描述,一个是 partyId,另外一个是 roletypeId,还有一个是被控对象的 Id。 PartyId 和 RoletypeId 指明流程中某个流程的执行者,被控对象 Id 指明要控制的对 象。比如在公文流转中控制的是公文。在 ofbiz 的定单管理中控制的是定单。 2、 编写流程活动的服务 流程在执行过程中,依据 PartyId、RoletypeId 和被控对象的 Id,并根据环境参数, 执行特定的动作,这个动作可以由开发人员具体编写。并将执行的状态保存到流程状 态实体中。 3、 对于 MANUAL 类型的服务,开发人员编写特定的应用,在应用执行完成后重新执行流 程。 三、编程参考 本部分结合 ofbiz 中定单管理中的例子来阐述 1、在定单管理中,若一个定单结束则定单进入流程控制, 1)、流程的触发 定单流程是在在清空购物车后由 clearcart 请求触发的,其定义在 Controll.xml 描述,结 构如下: ------------------------------------------------------------------------------------------ --------------------------------------------------------------------------- 清 空 购 物 车 后 发 出 initiateOrderWorkflow 请求, 而 initiateOrderWorkflow 请求触发 CheckOutEvents 类中 initiateOrderWorkflow 方法的执行 initiateOrderWorkflow 启动流程. 2)、流程的启动 流程启动是由 initiateOrderWorkflow 类中 initiateOrderWorkflow 的方法来启动的,具体过程 如下 -------------------------------------------------------------------- public static String initiateOrderWorkflow(HttpServletRequest request,HttpServletResponse response){ GenericDelegator delegator=(GenericDelegator)request.getAttribute("delegator"); LocalDispatcher dispatcher=(LocalDispatcher)request.getAttribute("dispatcher"); StringorderId=(String)request.getAttribute("order_id"); GenericValue orderHeader=null; try{ orderHeader=delegator.findByPrimaryKey("OrderHeader",UtilMisc.toMap("orderId",orderId)); }catch(GenericEntityExceptione){ Debug.logError(e,"Problemsgettingorderheader",module); request.setAttribute(SiteDefs.ERROR_MESSAGE,"