As discussed under Development Approach, our first iteration will focus on getting the search screen to work. Here's a mockup of this screen:
To support this screen, the service layer needs to provide two functions:
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.
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.
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:
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:
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.
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:
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
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:
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()); } } }
------------------------------------------------------- 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] ------------------------------------------------------------------------
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.