14.TimeTrackingService定义
我们现在处于迭代2阶段。这个迭代的目标是要构建搜索结果界面。要刷新我们的内存,下面是搜索屏幕的模型。搜索结果界面是时间卡显示的右面的内容。
Value Objects
要支持搜索结果面板,服务层必须提供根据匹配的特定规则获取timecard列表的能力。让我们决定我们创建一个service叫做TimeTrackingService,提供需要的功能:
public interface TimeTrackingService
{
public TimecardSummaryVO[] findTimecards(TimecardSearchCriteriaVO criteria);
}
有一个TimecardSearchCriteriaVO是value object,timecard包搜索条件-如果在这个类中的一个字段被指定,这个搜索条件就被应用了;如果这个filed被保留为null,则那个查询条件不会被用到。TimecardSummaryVO是另外的一个VO,在summary information包中。基于在Search屏幕中的条件,让我们来设计这两个VO。注意,我们需要一个状态field,在所有的Value Object中包含。因此首先,创建一个项目叫做TimecardStatus在org.andromda.timetracker.domain包中。在”Domain Objects”图中创建一个类,把sterotype设置为Enumeration。像下面显示的那样,这些被增加到属性中。确认enumeration被创建在org.andromda.timetracker.domain包中。
TimecardStatus被定义了,我们能定义被findTimecards()服务方法使用的两个value object。创在org.andromda.timetracker.vo包中建立TimecardSearchCriteriaVO和TimecardSummaryVO。在”Value Objects”图中创建这些value object。在TimecardSearchCriteriaVO中的每个属性的多重性被设置为0..1-指出他们的值可以有或没有。要指定一个属性的多重性,你必须在属性上双击打开它的描述,然后选择正确的多重性。在另外的两个value objects,创建一个类叫做TimecardSummaryVO[]来表现timecard summary数组。确保这三个类被创建在org.andromda.timetracker.vo包中。
TimeTrackingService
我们现在已经有了定义我们的TimeTrackingService的任何东西。在org.andromda.timetracker.service包中创建这个服务。在”Services”图中创建并且确保服务被创建在org.andromda.timetracker.service包中。已经确保findTimecards方法的返回类型为TimecardSummaryVO[]。
现在,让我们要求AndroMDA来生成代码:
1.在命令提示符中,执行mvn install。确保你看到了BUILD SUCCESSFUL消息。验证下面的文件会被生成:
- common/target/src/org/andromda/timetracker/domain/TimecardStatus.java
- common/target/src/org/andromda/timetracker/vo/TimecardSummaryVO.java
- common/target/src/org/andromda/timetracker/vo/TimecardSearchCriteriaVO.java
- common/target/src/org/andromda/timetracker/service/TimeTrackingService.java
- core/target/src/org/andromda/timetracker/service/TimeTrackingServiceBase.java
- core/src/main/java/org/andromda/timetracker/service/TimeTrackingServiceImpl.java
TimeTrackingService Test
像UserService一样,我们首先要给TimeTrackingService写一个测试用例并且写实现来使测试通过。下面的步骤用来创建TimeTrackingServiceTest并且运行:
1.
从C:/timetracker-completed/core/src/test/java/org/andromda/timetracker/service
位置拷贝TimeTrackingServiceTest.java在你的实现的位置中。这个文件包括对findTimecards()方法的两个测试,testFindAllTimecards()和testFindTimecardsForSubmitter。确保你确认和理解这些测试。
2.
编辑在C:/timetracker/core/src/test/resources目录下的testng.xml,取消注释TimeTrackingServiceTest以便能执行。
3.
执行下面的命令,在C:/timetracker目录来运行测试。
Mvn –f core/pom.xml test
你可以看到下面的错误信息,说明了测试失败:
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running Services Test
Tests run: 3, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 0.661 sec <<< FAILURE!
Results :
Tests run: 3, Failures: 2, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] There are test failures.
[INFO] ------------------------------------------------------------------------
[INFO] For more information, run Maven with the -e switch
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 19 seconds
[INFO] Finished at: Sun Aug 06 18:38:26 EDT 2006
[INFO] Final Memory: 5M / 11M
4.让我们看测试结果,看看发生了什么。打开c:/timetracker/core/target/surefire-reports/Services Test.txt。你可以看到下面的日志:
-------------------------------------------------------------------------------
Test set: Services Test
-------------------------------------------------------------------------------
Tests run: 3, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 0.661 sec <<< FAILURE!
testFindAllTimecards Time elapsed: 0.31 sec <<< FAILURE!
java.lang.NullPointerException
at ...(TimeTrackingServiceTest.java:58)
at ...(TimeTrackingServiceTest.java:41)
testFindTimecardsForSubmitter Time elapsed: 0.01 sec <<< FAILURE!
java.lang.NullPointerException
at ...(TimeTrackingServiceTest.java:58)
at ...(TimeTrackingServiceTest.java:50)
很明显,服务当前返回了null而不是TimecardSummaryVO[]。这个问题会当我们实现服务后被纠正。
15.TimeTrackingServcie实现
在这一章,我们会实现TimeTrackingService并且确保能通过测试。
我们会首先需要增强我们的领域模型来支持新value object TimecardSearchCriteriaVO并且TimecardSummaryVO。注意TimecardSearchCriteriaVO是一个简单的数据传输对象,来指定搜索参数。没有领域实体被需要表达这个概念。然而TimecardSummaryVO代表了timecard实体并且必须被增加到领域模型中。这个图如下面的显示:
Timecard与User实体有两个关系:
1. submitter:提交timecard的人。
2. approver:审核timecard的人。
两个关联关系的箭头指出了从Timecard到Person的方向。例如,timecard保持了对submitter和approver的饮用。在关联末端的数字和型号指明了多重性。顶端的关联指出timecard能有一个submitter,但是submitter能提交多个timecard(用*显示)。相似的,地下的关系指出了有0或1个审批者,一个审批者可以审批多个timecard。我们允许0审批者,以便审批是与提交延迟的。
我们在Timecard实体中增加一个方法叫做findByCriteria()。TimeTrackingService会使用这个方法来查找timecard。注意在findByCriteria()方法的下划线-他表示在UML中的”classifier”范围。如果在方法上没有下划线,会实现”instance”范围。这是在AndroMDA中重要的概念,特别注意。
一个带classifier scope的方法是一种一般目的的工具方法,操作一个或更多的实体。这样的方法在DAO类中被生成。带instance scope的方法生成在实体自己上并且被生成在Entity和EntityImpl类中。
我们现在准备增加Timecard实体到TimeTracker模型中,请根据下面的连接来继续。
- ArgoUML (under construction)
- MagicDraw 9.x
- MagicDraw 11.5
- RSM 6
接下来从Timecard实体到TimecardSummaryVO增加依赖关系。正如你所知,这种依赖告诉AndroMDA在Timecard和TimecardSummaryVO对象间生成转换。转换方法被生成在TimecardDaoBase中。然而,注意TimecardSummaryVO包含被从另外一个实体中获取的属性,叫做User,因此我们会覆盖默认的转换方法在这些其他的属性中。
接下来从TimeTrackingService到Timecard实体增加一个依赖。正如你所知,这个依赖告诉AndroMDA让TimeTrackingService访问Timecard实体。
现在,让我们要求AndroMDA为Timecard实体生成代码:
1.在命令提示符中执行mvn install。注意构建不会成功,因为测试仍然失败,然而代码生成的部分会成功。
Eclipse User:打开AndroMDA生成的.classpath文件。注意AndroMDA不会增加TestNG到classpath路径中。增加下面的条目到到这个文件中,以确保TestNG在Classpath中。如果你不想AndroMDA覆盖已经修改了的.classpath文件,通过注释在mda/pom.xml文件中的andromdapp-maven-plugin
段就可以了。
<classpathentry kind="var" path="M2_REPO/org/testng/testng/4.7/testng-4.7-jdk15.jar"/>
现在,让我们开始实现findByCriteria()方法,增加下面显示的粗体行在生成的TimeTrackingServiceImpl类中。
// license-header java merge-point
/**
* This is only generated once! It will never be overwritten.
* You can (and have to!) safely modify it by hand.
*/
package org.andromda.timetracker.service;
import java.util.List;
import org.andromda.timetracker.vo.TimecardSummaryVO;
/**
* @see org.andromda.timetracker.service.TimeTrackingService
*/
public class TimeTrackingServiceImpl
extends org.andromda.timetracker.service.TimeTrackingServiceBase
{
/**
* @see org.andromda.timetracker.service.TimeTrackingService#findTimecards(...)
*/
protected org.andromda.timetracker.vo.TimecardSummaryVO[] handleFindTimecards(
org.andromda.timetracker.vo.TimecardSearchCriteriaVO criteria)
throws java.lang.Exception
{
List timecards = getTimecardDao().findByCriteria(criteria);
getTimecardDao().toTimecardSummaryVOCollection(timecards);
return (TimecardSummaryVO[])timecards.toArray(new TimecardSummaryVO[0]);
}
}
我们首先在TimeDao中做一个调用findByCriteria()方法。还记得这是一个我们增加到Timecard实体的经典的方法吗?我们会手动实现这个方法。findByCriteria()
方法返回一个匹配Timecard实体的列表。然而,handleFindTimecards()需要返回一个TimecardSummaryVO对象的数组,因此,我们首先使用TimecardDao帮助方法来转换实体列表为VO集合。然后我们转换集合到一个数组,返回它。
现在在TimecardDaoImpl中实现handleFindByCriteria()方法。注意,代码使用Hibernate criteria查询来实现搜索功能。因此,我们无需关注实现细节,这在本教程的范围之外。
package org.andromda.timetracker.domain;
import org.hibernate.Criteria;
import org.hibernate.FetchMode;
import org.hibernate.criterion.Restrictions;
/**
* @see org.andromda.timetracker.domain.Timecard
*/
public class TimecardDaoImpl
extends org.andromda.timetracker.domain.TimecardDaoBase
{
...
protected java.util.List handleFindByCriteria(
org.andromda.timetracker.vo.TimecardSearchCriteriaVO criteria)
{
// Create the timecard criteria
Criteria timecardCriteria = this.getSession()
.createCriteria(Timecard.class)
.setFetchMode("submitter", FetchMode.JOIN)
.setFetchMode("approver", FetchMode.JOIN);
// Add submitter criteria
if (criteria.getSubmitterId() != null) {
timecardCriteria.createCriteria("submitter")
.add(Restrictions.idEq(criteria.getSubmitterId()));
}
// Add approver criteria
if (criteria.getApproverId() != null) {
timecardCriteria.createCriteria("approver")
.add(Restrictions.idEq(criteria.getApproverId()));
}
// Add status criteria
if (criteria.getStatus() != null) {
timecardCriteria.add(Restrictions.eq("status", criteria.getStatus()));
}
// Add startDateMin criteria
if (criteria.getStartDateMin() != null) {
timecardCriteria.add(
Restrictions.ge("startDate", criteria.getStartDateMin()));
}
// Add startDateMax criteria
if (criteria.getStartDateMax() != null) {
timecardCriteria.add(
Restrictions.le("startDate", criteria.getStartDateMax()));
}
java.util.List timecards = timecardCriteria.list();
if (logger.isDebugEnabled()) {
logger.debug(timecards.size() + " timecards found");
}
return timecards;
}
...
}
现在改变在TimecardDaoImpl类的TimecardSummaryVO()方法的默认实现,因为他不正确。注意,这个方法要被TimeTrackerService.findByCriteria()来调用,用来转换Timecard实体。目前,toTimecardSummaryVO()简单调用了在父类中的方法(TimecardDaoBase)。如果你还记得,在DaoBase中的默认的转换实现非常简单化。特殊的,TimecardDaoBase类只拷贝Timecard属性到TimecardSummaryVO;他不在从相关的实体的属性中填充,叫做submitterName和approverName。因此在TimecardDaoImpl类的TimecardSummaryVO()方法中做下面的修改:
public void toTimecardSummaryVO(
org.andromda.timetracker.domain.Timecard sourceEntity,
org.andromda.timetracker.vo.TimecardSummaryVO targetVO)
{
super.toTimecardSummaryVO(sourceEntity, targetVO);
targetVO.setSubmitterName(sourceEntity.getSubmitter().getUsername());
if (sourceEntity.getApprover() != null) {
targetVO.setApproverName(sourceEntity.getApprover().getUsername());
}
}
最后一件我们需要做的事情是在数据库中创建Timecard表并且填充数据。下面的步骤会完成这项工作:
1.
在命令提示符中运行下面的命令。要求androMDA创建我们的数据库schema:
Mvn –f core/pom.xml andromdapp:schema –Dtask=create
确保看到了构建成功的字样
2.
打开MySQL Query Browser。用timetracker登录。你能看到user和timecard表。
3.
选择文件->New Script粘贴下面的脚本
insert into USERS (ID, USERNAME, FIRST_NAME, LAST_NAME)
values (1, 'nbhatia', 'Naresh', 'Bhatia');
insert into USERS (ID, USERNAME, FIRST_NAME, LAST_NAME)
values (2, 'lcoude', 'Louis', 'Coude');
insert into USERS (ID, USERNAME, FIRST_NAME, LAST_NAME)
values (3, 'ecrutchfield', 'Eric', 'Crutchfield');
insert into USERS (ID, USERNAME, FIRST_NAME, LAST_NAME)
values (4, 'cmicali', 'Chris', 'Micali');
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values ( 1, 'Approved', '
2006/05/15
', 'Timecard 01', 1, 2);
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values ( 2, 'Approved', '
2006/05/15
', 'Timecard 02', 2, 3);
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values ( 3, 'Approved', '
2006/05/15
', 'Timecard 03', 3, 4);
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values ( 4, 'Approved', '
2006/05/15
', 'Timecard 04', 4, 1);
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values ( 5, 'Rejected', '
2006/05/22
', 'Timecard 05', 1, 2);
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values ( 6, 'Rejected', '
2006/05/22
', 'Timecard 06', 2, 3);
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values ( 7, 'Rejected', '
2006/05/22
', 'Timecard 07', 3, 4);
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values ( 8, 'Rejected', '
2006/05/22
', 'Timecard 08', 4, 1);
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values ( 9, 'Submitted', '
2006/05/29
', 'Timecard 09', 1, 2);
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values (10, 'Submitted', '
2006/05/29
', 'Timecard 10', 2, 3);
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values (11, 'Submitted', '
2006/05/29
', 'Timecard 11', 3, 4);
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values (12, 'Submitted', '
2006/05/29
', 'Timecard 12', 4, 1);
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values (13, 'Draft', '
2006/06/05
', 'Timecard 13', null, 2);
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values (14, 'Draft', '
2006/06/05
', 'Timecard 14', null, 3);
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values (15, 'Draft', '
2006/06/05
', 'Timecard 15', null, 4);
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
values (16, 'Draft', '
2006/06/05
', 'Timecard 16', null, 1);
2.
在顶端靠右点击执行按钮。
好,我们认为TimeTrackingService现在完成了。让我们执行下面的命令来再次运行测试,并且看看发生了什么:
C:/timetracker>mvn -f core/pom.xml test
...
...
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running Services Test
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.272 sec
Results :
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 30 seconds
[INFO] Finished at: Fri Aug 11 21:05:53 EDT 2006
[INFO] Final Memory:
6M
/
14M
[INFO] ------------------------------------------------------------------------
棒极了!所有的测试都通过了。打开文件C:/timetracker/core/timetracker-test.log,
21:05:46.850 INFO - Initializing ServiceLocator
21:05:46.870 INFO - Initializing UserService
21:05:51.767 INFO - Initializing ServiceLocator
21:05:51.767 INFO - Initializing TimeTrackingService
21:05:51.858 INFO - testFindAllTimecards:
...
21:05:52.819 DEBUG - 16 timecards found
21:05:52.859 INFO - Submitter Approver Status Start Date
21:05:52.869 INFO - nbhatia cmicali Approved
05/15/06
21:05:52.869 INFO - nbhatia cmicali Rejected
05/22/06
21:05:52.869 INFO - nbhatia cmicali Submitted
05/29/06
21:05:52.869 INFO - nbhatia null Draft
06/05/06
21:05:52.869 INFO - lcoude nbhatia Approved
05/15/06
21:05:52.869 INFO - lcoude nbhatia Rejected
05/22/06
21:05:52.869 INFO - lcoude nbhatia Submitted
05/29/06
21:05:52.869 INFO - lcoude null Draft
06/05/06
21:05:52.869 INFO - ecrutchfield lcoude Approved
05/15/06
21:05:52.869 INFO - ecrutchfield lcoude Rejected
05/22/06
21:05:52.869 INFO - ecrutchfield lcoude Submitted
05/29/06
21:05:52.869 INFO - ecrutchfield null Draft
06/05/06
21:05:52.869 INFO - cmicali ecrutchfield Approved
05/15/06
21:05:52.869 INFO - cmicali ecrutchfield Rejected
05/22/06
21:05:52.869 INFO - cmicali ecrutchfield Submitted
05/29/06
21:05:52.869 INFO - cmicali null Draft
06/05/06
21:05:52.879 INFO - testFindTimecardsForSubmitter:
...
21:05:52.959 DEBUG - 4 timecards found
21:05:52.959 INFO - Submitter Approver Status Start Date
21:05:52.969 INFO - nbhatia cmicali Approved
05/15/06
21:05:52.969 INFO - nbhatia cmicali Rejected
05/22/06
21:05:52.969 INFO - nbhatia cmicali Submitted
05/29/06
21:05:52.969 INFO - nbhatia null Draft
06/05/06
...