UserService Definition

As discussed under Development Approach, our first iteration will focus on getting the search screen to work. Here's a mockup of this screen:

Search Screen

To support this screen, the service layer needs to provide two functions:

  1. Ability to get a list of all users in order to populate the search criteria drop-downs (Submitter and Approver)
  2. Ability to get a list of all timecards that match a specific search criteria.

UML14 vs. UML2 Collection Types

Throughout this tutorial we refer to modeling Class and Class[]. This is not necessary if using UML2, or a UML Modeling tool which supports multiplicity * for operation parameters and return types. Simply model the association relationships and parameters as multiplicity * and replace references to Class[] with Class association of multiplicity *. UML Attribute collection mapping to Java Collection interface/implementation types is based on unique/ordered UML model values. UnOrdered+NonUnique will be Collection/ArrayList, Ordered+NonUnique will List/ArrayList, UnOrdered+Unique will be Set/HashSet, and Ordered+Unique will be SortedSet/TreeSet. This will generate CollectionType<Class> collections instead of Class[] array outputs.

UserVO Value Object

Let's zoom in on the first function, i.e. get a list of all users. Let's decide that we will create a service called UserService that will provide this functionality via the method specified below:

                public interface UserService
                {
                    public Collection<UserVO> getAllUsers();
                }
                

UserVO is a value object associated with the User entity (refer to Application Architecture for a discussion of entities and value objects). At a minimum, the UserVO will need two attributes for the drop-downs to function properly: the user's id and login name. However, to make UserVO a little more reusable, let's add two more attributes to it: user's first name and last name. Based on this discussion, the UserVO should be modeled as shown on the right. UserVO

Note that we have marked the UserVO class with a stereotype of ValueObject. This tells AndroMDA that UserVO is a ValueObject as opposed to other type of model elements such as Entity or EmbeddedValue. Stereotypes determine what pattern of code AndroMDA will generate for a model element. More specifically, they determine which AndroMDA code generation templates will be triggered.

Note that we have marked one attribute to be of type Long and three attributes to be of type String. These types should not be confused with the Java types java.lang.Long and java.lang.String. In model driven architecture, the model is kept independent of technology and hence all model elements are defined in terms of platform independent types. So Long and String in this context are platform independent types. When we run AndroMDA to generate code, it will translate platform independent types into platform dependent types such as java.lang.Long and java.lang.String, and into database specific types for DDL. If we were to use the same model to generate a .NET application, AndroMDA will translate Long into long? and String into System.String, which are .NET equivalents of java.lang.Long and java.lang.String. A key advantage of the model driven approach is that your investment in the business models is preserved even as technologies come and go.

Primitive vs. Wrapped datatypes: Attributes can be modeled as primitive (lowercase) or wrapped (Initcaps). Wrapped datatypes are nullable, primitive ones are not. Standard UML datatypes and UML/Java Primitive Types from the UML Standard libraries can also be used. The attribute multiplicity lowerBound should correspond to the modeled datatype: 0 for wrapped, 1 for primitive.

Now let us enter UserVO in the empty model that was created by the AndroMDA application plugin. If you remember, this model was created at timetracker/mda/src/main/uml/timetracker.xmi. Please follow one of the links below to edit the model with the UML tool of your choice.

Now let's ask AndroMDA to generate code for UserVO:

  1. Open a Command Prompt and change your directory to C:/timetracker.
  2. Execute the command mvn install. Make sure you get a BUILD SUCCESSFUL message.
  3. Keep the Command Prompt open for future builds.

Open the folder C:/timetracker/common/target/src/org/andromda/timetracker/vo in Windows Explorer. Note that the UserVO class is generated here. Open the class and review its contents. The ValueObject stereotype triggers the generation of only one artifact in the code, namely the Java class representing the value object. Later we will see examples of stereotypes that trigger the generation of multiple artifacts.

Eclipse Users: To view your source in Eclipse, follow the steps below:

  1. Execute the command mvn install -Peclipse to generate .project and .classpath
  1. Start Eclipse.
  2. Choose Import from the File menu.
  3. Select "Existing Projects into Workspace" and click Next.
  4. In the Import dialog box, click Browse.
  5. Browse to C:/timetracker and click OK.
  6. Click Finish. The timetracker project is now available in Eclipse.

This setup procedure is required only once. In future, when you regenerate code, simply right-click on the timetracker project in Package Explorer and choose Refresh.

UserService

UserService

Now that the UserVO is defined, we can model the UserService. Based on our discussion above, the model for the UserService is shown on the right. Please follow one of the links below to edit the model with the UML tool of your choice.

Now let's ask AndroMDA to generate code for UserService:

  1. Execute the command mvn install in the Command Prompt. Make sure you get a BUILD SUCCESSFUL message.

Unlike the ValueObject, a Service generates a trio of classes: an interface, an abstract base class and a concrete implementation. Here are the 3 classes generated for UserService

  1. UserService.java: UserService is the interface that specifies the service methods. Since this interface is needed by client applications as well as the service implementation, it is generated in the target branch of the common project.
  2. UserServiceBase.java: UserServiceBase implements the methods specified by the UserService interface. These methods essentially do some parameter checking and then delegate the actual business functionality to "handle" methods. Handle methods are expected to be implemented manually in the UserServiceImpl class. UserServiceBase also manages transactional behavior of the application. Transactions are started on entry of service methods and finished on exit from these methods. UserServiceBase.java is generated in the target branch of the core project.
  3. UserServiceImpl.java: UserServiceImpl is a concrete extension of the UserServiceBase class. This is where developers are expected to code the main business logic for the service methods. UserServiceImpl.java is generated in the source branch of the core project.

UserService Test

In the spirit of Test Driven Development (TDD), we will write a test for the UserService even before it is implemented. The whole point of TDD is to write robust tests that verify the complete functionality of the system under test -- if the tests pass, you have good confidence that the system will function correctly; if not, then you have some work cut out for you :-). Of course, once the tests are defined, you must write implementation code to make them pass. The good part is that you need to write just enough code to make the tests pass - no more, no less! For our simple getAllUsers() method, we will write a simple test that gets all the users and verifies the returned values against what is expected.

TODO: The test described below is completely manual -- we have not yet incorporated a framework to populate the database with known values during the build process and then to verify against those values. The test framework we use (a combination of TestNG and DBUnit) is fully capable of doing that, so we will incorporate an automated test in a future version of this tutorial. For now, we will simply inspect the test results visually.

We will use TestNG for testing our service layer. TestNG is a powerful testing framework, very similar to JUnit, but offers several additional features, such as data-driven testing, to simplify the testing process. In order to make our testing process efficient, we will not test the service layer on JBoss. Instead, we will configure our tests to talk directly to the POJO services, bypassing the EJB container completely.

Add or verify that the current version of the testng test dependency is in the maven pom.xml files. In the top level pom.xml: <dependencyManagement> <dependencies> ... <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>5.14.6</version> <scope>test</scope> </dependency> And in the core/pom.xml: <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> </dependency>

Copy the entire test directory located under C:\timetracker-completed\core\src to the corresponding location in your own TimeTracker directory. After copying, delete the file core\src\test\java\org\andromda\timetracker\service\TimeTrackingServiceTest.java in your TimeTracker directory - we are not yet ready to compile this file.

Explore the core\src\test directory to get familiar with its contents. Use the steps below to guide you along and to run the test:

  1. UserServiceTest is located under test\java\org\andromda\timetracker\service. The test is pretty simple. The initializeTestSuite() method gets a reference to the UserService. This reference is used by the testGetAllUsers() method to get all users and log them. UserServiceTest is shown below for your convenience:
    public class UserServiceTest {
        private Log logger = LogFactory.getLog(UserServiceTest.class);
        private UserService userService;
        @Configuration(beforeSuite=true)
        public void initializeTestSuite() {
            // Initialize ServiceLocator
            logger.info("Initializing ServiceLocator");
            ServiceLocator locator = ServiceLocator.instance();
            locator.init("testBeanRefFactory.xml", "beanRefFactory");
            // Initialize UserService
            logger.info("Initializing UserService");
            userService = locator.getUserService();
        }
        @Test
        public void testGetAllUsers() {
            logger.info("testGetAllUsers:");
            UserVO[] users = userService.getAllUsers();
            for (int i=0; i<users.length; i++) {
                logger.info(users.getUsername());
            }
        }
    }
                            
  2. The directory test\resources contains configuration files needed for testing.
    • log4j.xml controls the logging behavior
    • testBeanRefFactory.xml enables the tests to directly talk to the services
  3. Execute the following command in the C:\timetracker directory to run the test.
    mvn -f core/pom.xml test
    Note that mvn install will also run the test, but it will do everything starting from regeneration of code. Since we have not changed the model after the last code generation, we are simply asking Maven to run the tests in the core sub-project. You will find that the test fails with the following message:
    -------------------------------------------------------
     T E S T S
    -------------------------------------------------------
    Running TestSuite
    Tests run: 2, Failures: 1, Errors: 0, Skipped: 1, Time elapsed: 0.872 sec <<< FAILURE!
    Results :
    Failed tests: 
      initializeTestSuite(org.andromda.timetracker.service.UserServiceTest)
    Tests run: 2, Failures: 1, Errors: 0, Skipped: 1
    [INFO] ------------------------------------------------------------------------
    [ERROR] BUILD FAILURE
    [INFO] ------------------------------------------------------------------------
    [INFO] There are test failures.
    Please refer to /home/walter/test_workspace/timetracker/core/target/surefire-reports for the individual test results.
    [INFO] ------------------------------------------------------------------------
    [INFO] For more information, run Maven with the -e switch
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 5 seconds
    [INFO] Finished at: Sun Apr 03 16:24:55 BRT 2011
    [INFO] Final Memory: 31M/604M
    [INFO] ------------------------------------------------------------------------
                            
    The test failed because initializeTestSuite() could not initialize the UserService correctly. The reason for this is that Spring could not find a bean called 'sessionFactory' which it was expecting. This problem will be fixed when we implement entities in the middle-tier.

What's Next?

We now have our work cut out for us. We need to implement the getAllUsers() method so it returns a UserVO[] even if there are no users in the database. Click here to start our implementation.