In this section we will implement the TimeTrackingService and make sure it passes the tests.
We will first need to enhance our domain model to support the new value objects TimecardSearchCriteriaVO and TimecardSummaryVO. Note that TimecardSearchCriteriaVO is simply a data transfer object that specifies search parameters. There is no domain entity needed to express this concept. However TimecardSummaryVO represents a timecard entity and must be added to the domain model. The diagram below shows the model of this entity.
Timecard has two associations with the User entity:
The arrows on the two associations indicate that both associations are navigable from Timecard to User, i.e. the timecard keeps references to its submitter and approver. Note that two-way navigability is indicated by not having arrows at either end of the association. The numbers and stars at the ends of the associations indicate their multiplicity. The top association indicates that a timecard can have one submitter, but a submitter can submit many timecards (shown by the "*"). Similarly the bottom association indicates that a timecard can have zero or one approver, but an approver can approve many timecards. We allow for zero approvers on a timecard so that the decision to assign an approver can be delayed until the timecard is submitted.
We have also added a method called findByCriteria() on the Timecard entity. TimeTrackingService will essentially use this method to find timecards. Note the underline on the findByCriteria() method - it represents a "classifier" scope in UML. If there was no underline on the method that would imply "instance" scope. This is an important concept in AndroMDA, so pay attention. A method with classifier scope is a general purpose utility method which operates on one or more entities. Such methods are generated in the DAO classes. A method with instance scope is a method on the entity itself and is generated in the Entity and EntityImpl classes.
We are now ready to add the Timecard entity to the TimeTracker model along with the two associations to the User entity. Please follow one of the links below to edit the model with the UML tool of your choice.
Next add a dependency from the Timecard entity to the TimecardSummaryVO as shown below. As you know, this dependency tells AndroMDA to generate conversion methods between Timecard and TimecardSummaryVO objects. The conversion methods are generated in TimecardDaoBase. However, note that TimecardSummaryVO contains attributes fetched from another entity, namely User, so we will have to override the default conversion methods to fill in these additional attributes.
Next add a dependency from TimeTrackingService to the Timecard entity as shown below. As you know, this dependency tells AndroMDA to give TimeTrackingService access to the Timecard entity.
Now let's ask AndroMDA to generate code for the Timecard entity:
Now let's start by implementing the findByCriteria() method. Add the bold lines shown in the listing below to your TimeTrackingServiceImpl class.
// 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. * TEMPLATE: SpringServiceImpl.vsl in andromda-spring cartridge * MODEL CLASS: Data::timetracker::org.andromda.timetracker::service::TimeTrackingService * STEREOTYPE: Service */ package org.andromda.timetracker.service; import java.util.Collection; import java.util.List; import org.andromda.timetracker.vo.TimecardSearchCriteriaVO; import org.andromda.timetracker.vo.TimecardSummaryVO; /** * @see org.andromda.timetracker.service.TimeTrackingService */ public class TimeTrackingServiceImpl extends TimeTrackingServiceBase { /** * @see org.andromda.timetracker.service.TimeTrackingService#findTimecards(TimecardSearchCriteriaVO) */ @SuppressWarnings("unchecked") protected Collection<TimecardSummaryVO> handleFindTimecards(TimecardSearchCriteriaVO criteria) throws Exception { @SuppressWarnings("rawtypes") List timecards = getTimecardDao().findByCriteria(criteria); getTimecardDao().toTimecardSummaryVOCollection(timecards); return (Collection<TimecardSummaryVO>)timecards; } }
We first make a call to the findByCriteria() method in TimecardDao. Remember this was a classifier scope method that we added to the Timecard entity? We will implement this method manually. findByCriteria() returns a list of matching Timecard entities. However, handleFindTimecards() needs to return an array of TimecardSummaryVO objects. So we first use a TimecardDao helper method to convert the entity list to a VO collection.
Now implement the handleFindByCriteria() method in TimecardDaoImpl.java as shown below. We also show the imports needed for this method. Note that the code uses a Hibernate criteria query to implement the search functionality. However, we will not go into the details of the implementation as it is outside the scope of this tutorial.
// Generated by: hibernate/SpringHibernateDaoImpl.vsl in andromda-spring-cartridge. // 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.domain; import java.util.List; import org.andromda.timetracker.vo.TimecardSearchCriteriaVO; import org.andromda.timetracker.vo.TimecardSummaryVO; import org.hibernate.Criteria; import org.hibernate.FetchMode; import org.hibernate.criterion.Restrictions; /** * @see Timecard */ public class TimecardDaoImpl extends TimecardDaoBase { /** * @inheritDoc */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected List handleFindByCriteria(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; } ... }
Now change the default implementation of toTimecardSummaryVO() in TimecardDaoImpl since it is not correct. Note that this method will be called by TimeTrackerService.findByCriteria() to convert Timecard entities. Currently, toTimecardSummaryVO() simply calls the corresponding method in its parent class ( TimecardDaoBase). If you remember, the default converter implementations in DaoBase classes are very simplistic. Specifically, the TimecardDaoBase class copies only Timecard attributes to TimecardSummaryVO; it does not fill in the attributes that come from related entities, namely submitterName and approverName. So modify toTimecardSummaryVO() in TimecardDaoImpl as shown below, filling in the remaining attributes.
public void toTimecardSummaryVO( Timecard source, TimecardSummaryVO target) { super.toTimecardSummaryVO(source, target); super.toTimecardSummaryVO(source, target); target.setSubmitterName(source.getSubmitter().getUsername()); if (source.getApprover() != null) { target.setApproverName(source.getApprover().getUsername()); } }
The last thing we need to do is to create the Timecard table in the database and populate it with data. Follow the steps below to accomplish this:
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values ( 1, 'APPROVED', '2011/05/15', 'Timecard 01', 1, 2); insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values ( 2, 'APPROVED', '2011/05/15', 'Timecard 02', 2, 3); insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values ( 3, 'APPROVED', '2011/05/15', 'Timecard 03', 3, 4); insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values ( 4, 'APPROVED', '2011/05/15', 'Timecard 04', 4, 1); insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values ( 5, 'REJECTED', '2011/05/22', 'Timecard 05', 1, 2); insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values ( 6, 'REJECTED', '2011/05/22', 'Timecard 06', 2, 3); insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values ( 7, 'REJECTED', '2011/05/22', 'Timecard 07', 3, 4); insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values ( 8, 'REJECTED', '2011/05/22', 'Timecard 08', 4, 1); insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values ( 9, 'SUBMITTED', '2011/05/29', 'Timecard 09', 1, 2); insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values (10, 'SUBMITTED', '2011/05/29', 'Timecard 10', 2, 3); insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values (11, 'SUBMITTED', '2011/05/29', 'Timecard 11', 3, 4); insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values (12, 'SUBMITTED', '2011/05/29', 'Timecard 12', 4, 1); insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values (13, 'DRAFT', '2011/06/05', 'Timecard 13', null, 2); insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values (14, 'DRAFT', '2011/06/05', 'Timecard 14', null, 3); insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values (15, 'DRAFT', '2011/06/05', 'Timecard 15', null, 4); insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK) values (16, 'DRAFT', '2011/06/05', 'Timecard 16', null, 1);
Well, we think that TimeTrackingService is now completely implemented. Let's execute the command below to run the tests again and see what happens:
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 2011 [INFO] Final Memory: 6M/14M [INFO] ------------------------------------------------------------------------
Great! All tests passed. Open C:\timetracker\core\timetracker-test.log to see the results. You should see the following output:
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 ...
Now that TimeTrackingService.findByCriteria() method is working, let's try to use it from the front-end. Click here to implement the search results panel of the search screen, which needs to call this method.