ofbiz学习之部署组件2

一、实现Create以及Update功能

1、在我们完成的第一张视图中,有一个“新增”按钮,它对应第三—4步的ProductPlan装饰器中的:

1
2
3
<container style="button-bar">
<link target="EditProductPlan" text="新建" style="buttontext create"/>
</container>

同时在完成的第二张视图中,有一个“修改”按钮,它对应第六—3步中的ViewProductPlan表单中的:

1
2
3
<field name="submitButton" title="修改">
<submit button-type="button"/>
</field>

这两个按钮都指向EditProductPlan请求(表单ViewProductPlan的target属性为EditProductPlan),因此,我们又得如出一辙地部署一个EditProductPlan请求和响应视图,重复性工作,直接上代码:

(1)controller.xml:

1
2
3
4
5
6
<request-map uri="EditProductPlan">
<security https="true" auth="true" />
<response name="success" type="view" value="EditProductPlan" />
</request-map>

<view-map name="EditProductPlan" type="screen" page="component://learning/widget/learningScreens.xml#EditProductPlan"/>

(2)learningScreens.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<screen name="EditProductPlan">
<section>
<actions>
<set field="ProductPlanId" from-field="parameters.ProductPlanId"/>
<entity-one entity-name="ProductPlan" value-field="ProductPlan"/>
</actions>
<widgets>
<decorator-screen name="CommonRoutingDecorator" location="component://manufacturing/widget/manufacturing/RoutingScreens.xml">
<decorator-section name="body">
<screenlet title="生产计划表">
<container style="button-bar">
<link target="FindProductPlan" text="返回" style="buttontext"/>
</container>
<include-form name="EditProductPlan" location="component://learning/widget/learningForms.xml"></include-form>
</screenlet>
</decorator-section>
</decorator-screen>
</widgets>
</section>
</screen>

(3)learningForms.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<form name="EditProductPlan" type="single" target="UpdateProductPlan" title="" default-map-name="ProductPlan">
<!-- 以下是当表单为空时,将请求交给Create -->
<alt-target use-when="ProductPlan==null" target="CreateProductPlan" />
<field name="productPlanId" title="" tooltip="*" widget-style="required"><text/></field>
<field name="itemId" tooltip="*" widget-style="required"><lookup target-form-name="LookupProductPlanItem" size="16"/></field>
<field name="principleId" tooltip="*" widget-style="required"><lookup target-form-name="LookupProductPlanPerson" size="16"/></field>
<field name="preparationId"><lookup target-form-name="LookupProductPlanPreparation" size="16"/></field>
<field name="useProject"><text/></field>
<field name="startTime"><date-time type="date"/></field>
<field name="finishTime"><date-time type="date"/></field>
<field name="submitButton" title="提交">
<submit button-type="button"/>
</field>
</form>

2、当EditProductPlan表单完成后,会提交到CreateProductPlan或UpdateProductPlan请求,而这取决于表单一开始是否为空,例如新建时表单为空提交给CreateProductPlan。由于此处涉及到对数据库的增加和更新,则需要调用services服务,笔者在此使用java实现该服务。

(1)调用服务的第一步是要在learning/servicedef/services.xml文件中声明该服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="UTF-8"?>
<services xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/services.xsd">
<description>learning Services</description>
<vendor></vendor>
<version>1.0</version>

<service name="createProductPlan" engine="java" auth="true"
location="org.ofbiz.learning.ProductPlanServices" invoke="createProductPlan">
<description>Create a ProductPlan</description>
<attribute name="productPlanId" type="String" mode="IN" optional="true" />
<attribute name="itemId" type="String" mode="IN" optional="true" />
<attribute name="principleId" type="String" mode="IN" optional="true" />
<attribute name="preparationId" type="String" mode="IN" optional="true" />
<attribute name="useProject" type="String" mode="IN" optional="true" />
<attribute name="startTime" type="java.sql.Timestamp" mode="IN" optional="true" />
<attribute name="finishTime" type="java.sql.Timestamp" mode="IN" optional="true"/>
</service>

<service name="updateProductPlan" engine="java" auth="true"
location="org.ofbiz.learning.ProductPlanServices" invoke="updateProductPlan">
<description>Update a ProductPlan</description>
<attribute name="productPlanId" type="String" mode="IN" optional="true" />
<attribute name="itemId" type="String" mode="IN" optional="true" />
<attribute name="principleId" type="String" mode="IN" optional="true" />
<attribute name="preparationId" type="String" mode="IN" optional="true" />
<attribute name="useProject" type="String" mode="IN" optional="true" />
<attribute name="startTime" type="java.sql.Timestamp" mode="IN" optional="true" />
<attribute name="finishTime" type="java.sql.Timestamp" mode="IN" optional="true"/>
</service>

</services>

<sevice>标签中,name是该服务的名字,engine是服务的实现方式,location指向java类的位置,invoke指向该java类中的一个方法(函数)。<attribute>标签中,name是传进该方法中的数据项的名字,type是该数据项的类型,mode=”IN”表示传进,”OUT”表示传出。

(2)实现该java类及方法。在learning/src下,创建org/ofbiz/learning/目录,并在该目录下创建一个名为ProductPlanServices的java类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package org.ofbiz.learning;

import java.sql.Timestamp;
import java.util.Locale;
import java.util.Map;

import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.UtilProperties;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.entity.Delegator;
import org.ofbiz.entity.GenericEntityException;
import org.ofbiz.entity.GenericValue;
import org.ofbiz.service.DispatchContext;
import org.ofbiz.service.ServiceUtil;

public class ProductPlanServices {
public static final String module = ProductPlanServices.class.getName();
public static final String resource = "ProductPlanUiLabels";

/**
* create xpp Learn
* @param dctx
* @param context
* @return
*/
public static Map<String, Object> createProductPlan(DispatchContext dctx, Map<String, ? extends Object> context)
{
//获取用户信息
Locale locale = (Locale) context.get("locale");
//获取返回的Map类
Map<String, Object> result = ServiceUtil.returnSuccess();
//获取数据库操作类
Delegator delegator = dctx.getDelegator();

//获取传进来的数据项
String productPlanId = (String) context.get("productPlanId");
String itemId = (String) context.get("itemId");
String principleId = (String) context.get("principleId");
String preparationId = (String) context.get("preparationId");
String useProject = (String) context.get("useProject");
Timestamp startTime = (Timestamp) context.get("startTime");
Timestamp finishTime = (Timestamp) context.get("finishTime");

//将这些数据项放进一个Map类对象中
Map<String, Object> ProductPlanMap = UtilMisc.toMap("productPlanId", productPlanId,"itemId",itemId,"principleId",principleId);
ProductPlanMap.put("preparationId", preparationId);
ProductPlanMap.put("useProject", useProject);
ProductPlanMap.put("startTime", startTime);
ProductPlanMap.put("finishTime", finishTime);

//将Map类放入一个名为ProductPlan的数据表中,GenericValue类的对象相当于数据表中的一条记录
GenericValue GV = delegator.makeValue("ProductPlan", ProductPlanMap);
try {
//创建这条记录
GV.create(); }
catch (GenericEntityException e) {
Debug.logError(e, e.getMessage(), module);
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"Learn.create.error", new Object[] { e.getMessage() }, locale));
}
return result;
}

/**
* update Learn xpp
* @param dctx
* @param context
* @return
*/
public static Map<String, Object> updateProductPlan(DispatchContext dctx,Map<String, ? extends Object> context)
{
Locale locale = (Locale) context.get("locale");
Map<String, Object> result = ServiceUtil.returnSuccess();
Delegator delegator = dctx.getDelegator();

//获取传进的数据项
String productPlanId = (String) context.get("productPlanId");
String itemId = (String) context.get("itemId");
String principleId = (String) context.get("principleId");
String preparationId = (String) context.get("preparationId");
String useProject = (String) context.get("useProject");
Timestamp startTime = (Timestamp) context.get("startTime");
Timestamp finishTime = (Timestamp) context.get("finishTime");

//将id放进一个Map中
Map<String, Object> ProductPlanMap = UtilMisc.toMap("productPlanId", productPlanId);

try {
//通过该Map对象即ProductPlanId,寻找与该id相同的数据表中的记录
GenericValue GV = delegator.findOne("ProductPlan", ProductPlanMap , false);
//如果找不到这条记录就报错
if(UtilValidate.isEmpty(GV))
{
return ServiceUtil.returnError(UtilProperties.getMessage(resource,"ProductPlan.update.error.entity.notFound", locale));
}
//如果找到了,就把传进来的数据放到该记录中
GV.put("itemId", itemId);
GV.put("principleId", principleId);
GV.put("preparationId", preparationId);
GV.put("useProject", useProject);
GV.put("startTime", startTime);
GV.put("finishTime", finishTime);
//更新该记录
GV.store();
}catch (GenericEntityException e) {
Debug.logError(e, e.getMessage(), module);
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"Learn.update.error", new Object[] { e.getMessage() }, locale));
}
return result;
}
}

以上只是一个简单的操作数据库的java类,部分内容的讲解已经在该类的注释中,如果想更好地了解service服务,可以参考Apache+OFBiz+开发初学者指南.chm和ofbiz菜鸟笔记.doc。

(3)编写完类和方法后,接下来的工作非常重要:

  • 检查learning/ofbiz-component.xml中有没有以下代码:
1
2
3
<classpath type="jar" location="build/lib/*"/>

<service-resource type="model" loader="main" location="servicedef/services.xml"/>
  • 检查controller.xml中有没有以下代码:
1
<include location="component://common/webcommon/WEB-INF/common-controller.xml"/>
  • 当然,如果你是用笔者第二步提供的方法来创建learning组件,那么你可以不必检查。

(4)重中之重,右键learning/src—选择构建路径—选择做源文件夹。

(5)泰山之重,右键learning/build.xml文件—选择运行方式(Run as)—选择第二个Ant
构建—进入编辑配置界面—在目标界面选择jar(一般默认就已经选择)—点击运行,运行完成后检查learning/build下是不是有东西了(文件夹不算)。

3、在controller.xml添加:

1
2
3
4
5
6
7
8
9
10
11
12
<request-map uri="CreateProductPlan">
<security https="true" auth="true"/>
<event type="service" invoke="createProductPlan"/>
<response name="success" type="view" value="EditProductPlan"/>
<response name="error" type="view" value="EditProductPlan"/>
</request-map>
<request-map uri="UpdateProductPlan">
<security https="true" auth="true"/>
<event type="service" invoke="updateProductPlan"/>
<response name="success" type="view" value="EditProductPlan"/>
<response name="error" type="view" value="EditProductPlan"/>
</request-map>

注意请求映射中多了一个event映射,表明该请求会调用该服务(invoke对应services.xml中声明的服务),通过该服务后才到达视图部分,而映射的视图此前已经创建完成了。

4、到此,Create以及Update功能已经完成,但增删改查还差了删除功能,不过这已经是手到擒来的了。

八、Remove功能

1、通过第七步,我们可以知道,涉及数据库操作功能时,我们不需要新建视图,只需要让该请求通过event进入服务,然后返回到已有视图即可,所以在controller.xml中,我们只需要添加:

1
2
3
4
5
6
<request-map uri="RemoveProductPlan">
<security https="true" auth="true"/>
<event type="service" invoke="removeProductPlan"/>
<response name="success" type="view" value="FindProductPlan"/>
<response name="error" type="view" value="FindProductPlan"/>
</request-map>

2、然后在services.xml中添加:

1
2
3
4
5
<service name="removeProductPlan" engine="java" auth="true"
location="org.ofbiz.learning.ProductPlanServices" invoke="removeProductPlan">
<description>Remove a ProductPlan</description>
<attribute name="productPlanId" type="String" mode="IN" optional="true" />
</service>

由于删除的时候只需要知道id号,因此传进id号即可。

3、在ProductPlanServices类中添加一个删除方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static Map<String, Object> removeProductPlan(DispatchContext dctx,
Map<String, ? extends Object> context) {
Locale locale = (Locale) context.get("locale");
Map<String, Object> result = ServiceUtil.returnSuccess();
Delegator delegator = dctx.getDelegator();

String productPlanId = (String) context.get("productPlanId");

try {
GenericValue gv = delegator.findOne("ProductPlan", UtilMisc.toMap("productPlanId", productPlanId),false);
if(UtilValidate.isEmpty(gv)){
return ServiceUtil.returnError(UtilProperties.getMessage(resource,"Learn.delete.error.entity.notFound", locale));
}
gv.remove();
}catch (GenericEntityException e) {
Debug.logError(e, e.getMessage(), module);
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"Learn.delete.error", new Object[] { e.getMessage() }, locale));
}
return result;
}

4、最后,不要忘记通过build.xml对该类进行编译。另外,当对services.xml文件或java类进行修改后,要停止ofbiz再启动,否则修改无效。

结语(本文结束的几个月后所添加的)

随着笔者对ofbiz的研究时间日益长久,又有一番心得,包括如下几点:

  • 一定要学习ofbiz中自创的mililang语言————类似一种有逻辑的标签语言。该语言在ofbiz中用处很大,可以很快速地编写一个服务,且不需要编译;可以在“表单”form以及“屏幕”screen的动作action中使用。至于怎么学习,依旧参考这位大神的博客

  • 一定要时常回去看看Apache+OFBiz+开发初学者指南[Ob4.0-EN+Ob9.0-CN].chm,包含了很多知识点。

  • 服务service和事件event的区别在于:服务用来对数据库进行操作,而事件用于实现逻辑跳转、数据处理之类的功能。