Objectives

  • Introduce UML Class Diagram modeling using Visual Paradigm
  • Define a simple model and implement it in Play
  • Write comprehensive unit tests to exercise the model

JPA Model Project

Start by creating a brand new Play project. Do this by determining the parent folder (most likely your workspace) and running a command prompt. Then type:

play new jpamodel

Once this has completed, change into the folder just created (jpamodel) and run the eclipsify command:

cd jpamodel
play eclipsify

You can now import the project into eclipse in the usual way.

Back in eclipse, refresh (File->Refresh) the project.

Finally, in conf/application.conf, make sure the following lines are uncommented:

db=mem

Visual Paradigm

For this step to work you will need to have installed Visual Paradigm. This is the particular product here:

... and it can be downloaded here

If you select the 'no install' version, then it can be reliably run from a memory stick in the IT labs.

You will require this Key in order to activate the application:

Key for Visual Paradigm 12: Standard Edition: 38L5V-ALM9J-7QFK6-5G293-R85Q9

The pc will need to be online when it app launches in order to validate.

Launch Visual Paradigm now, and select File->Create New Project:

This should present a screen like this:


We start by creating a new "Class Diagram":


Press OK and the class diagram editor is displayed:


Before we create any classes, we are going to make some small adjustments to the default look and feel. Select 'Tools->Projects Options->Diagramming->Shape' and make the 'default shape fill format' white (instead of blue currently):



Then select 'Class->Presentation' and set 'show attribute option' and 'show operation option' to both be 'hide all'

Drag the 'class' icon onto the canvas, call the class Club. Create another one and call it Player. Your canvas should look like this:


Save the project. Make sure you remember where you put it. A useful place is to put in inside the same folder as the play project

Initial Model for Player and Club

In your jpamodel eclipse project, create two new classes in the model package:

Club

package models;

import javax.persistence.Entity;
import play.db.jpa.Model;

@Entity
public class Club extends Model
{
  public String name;

  public Club(String name)
  {
    this.name = name;
  }
}

Player

package models;

import javax.persistence.Entity;
import play.db.jpa.Model;

@Entity
public class Player extends Model
{
  public String name;

  public Player(String name)
  {
    this.name = name;
  }
}

Now we create the unit test harnesses. In 'test/default package' create these two tests:

ClubTest

import org.junit.*;

import java.util.*;
import play.test.*;
import models.*;

public class ClubTest extends UnitTest 
{
  @Before
  public void setup()
  {
  }

  @After
  public void teardown()
  {
  }

  @Test
  public void testCreate()
  {

  }
}

PlayerTest

import org.junit.*;

import java.util.*;
import play.test.*;
import models.*;

public class PlayerTest extends UnitTest 
{
  @Before
  public void setup()
  {
  }

  @After
  public void teardown()
  {
  }

  @Test
  public void testCreate()
  {

  }
}

Run the app now in 'test' mode:

play test

...and navigate to the test runner page:

Select the Club and Player tests - and they should be green.

Also try the database interface:

Initial Tests for Player and Club

PlayerTest

In PlayerTest introduce a fixture and create some test objects:

public class PlayerTest extends UnitTest 
{
  private Player p1, p2, p3;

  @Before
  public void setup()
  {
    p1 = new Player("mike");
    p2 = new Player("jim");
    p3 = new Player("frank");
    p1.save();
    p2.save();
    p3.save();
  }

  @After
  public void teardown()
  {
    p1.delete();
    p2.delete();
    p3.delete();
  }

  @Test
  public void testCreate()
  { 
  }
}

Run the tests - and have a look at the admin interface. Can you see any player objects? No because teardown() is deleteing all of the objects. Comment out the @After annotation:

 //@After

and run the tests again. This time - if you refresh the admin page - you will see the three player objects.

Into the player class, introduce the following method (at the end):

  public String toString()
  {
    return name;
  }

Save and run the tests again. Do you see any difference? You should now see the actual name of the player in the list, as opposed to just 'Player[x]' where x is the id. Now uncomment the @After annotation, but remember you can comment it in/out occasionally if you want to inspect the objects. Being able to see them in the admin interface can be particularly useful if you are introducing new relationships between objects.

Now we will bring in utility function into the Player class:

  public static Player findByName(String name)
  {
    return find("name", name).first();
  }

Note this method is 'static'.

Now we can actually implement a test.

  @Test
  public void testCreate()
  {
    Player a = Player.findByName("mike");
    assertNotNull(a);
    assertEquals("mike", a.name);
    Player b = Player.findByName("jim");
    assertNotNull(b);
    assertEquals("jim", b.name);
    Player c = Player.findByName("frank");
    assertNotNull(c);
    assertEquals("frank", c.name);
  }

This test should pass. Change one of the stings to deliberately force an error just to be sure.

Do one more (negative) test to make sure that if we try to find an object that is not there, we get null:

  @Test
  public void testNotThere()
  {
    Player a = Player.findByName("george");
    assertNull(a);
  }

All tests should pass now.

ClubTest

Extend Club with the same utility methods as Player:

  public String toString()
  {
    return name;
  }

  public static Club findByName(String name)
  {
    return find("name", name).first();
  }  

and now we can write a similar set of tests in PlayerTest:

import org.junit.*;

import java.util.*;
import play.test.*;
import models.*;

public class ClubTest extends UnitTest 
{
  private Club c1, c2, c3;

  @Before
  public void setup()
  {
    c1 = new Club("tramore");
    c2 = new Club("dunmore");
    c3 = new Club("fenor");
    c1.save();
    c2.save();
    c3.save();
  }

  @After
  public void teardown()
  {
    c1.delete();
    c2.delete();
    c3.delete();
  }

  @Test
  public void testCreate()
  {
    Club a = Club.findByName("tramore");
    assertNotNull(a);
    assertEquals("tramore", a.name);
    Club b = Club.findByName("dunmore");
    assertNotNull(b);
    assertEquals("dunmore", b.name);
    Club c = Club.findByName("fenor");
    assertNotNull(c);
    assertEquals("fenor", c.name);
  }

  @Test
  public void testNotThere()
  {
    Club a = Club.findByName("george");
    assertNull(a);
  }
}

These tests should run successfully. Experiment with commentin out the @After annotation and seeing the actual objects in the admin interface.

Modeling a Player / Club Relationship

Returning to Visual Paradigm - you may notice a red line under each class. It says 'Very bad' if you click on it. Do so now, and in a subsequent dialog you will have an opportunity to disable this 'Quality Checker' completely We will not be using this feature.

On the pallette on the left, select the 'association' element and use it to connect Club and Player. It should look like this:

Select the association (the line), and locate the following panel (which should look like this:

Now edit as follows:

For Role A:

For Role B:

If this works, then the class diagram should look like this:

We can now implement this relationship. In Eclipse, modify Club as follows:

public class Club extends Model
{
  //... 
  @OneToMany(cascade=CascadeType.ALL)
  public List<Player> players;

  public Club(String name)
  {
    this.name = name;
    this.players = new ArrayList<Player>();
  }
  //...
  public void addPlayer(Player player)
  {
    players.add(player);
  }
  //...
}

This establishes the relationship as modeled in UML.

Testing a Player / Club Relationship

We will focus these tests in the ClubTest class. Expand the fixture:

  private Player p1, p2, p3;

and in the setup(), create some players and add some of them (not all) to the 'c1' club:

  @Before
  public void setup()
  {
    p1 = new Player("mike");
    p2 = new Player("jim");
    p3 = new Player("frank");
    c1 = new Club("tramore");
    c2 = new Club("dunmore");
    c3 = new Club("fenor");

    c1.addPlayer(p1);
    c1.addPlayer(p2);

    c1.save();
    c2.save();
    c3.save();
  }

We can now write a new test to see if the 'tramore' has the players "mike" and "jim":

  @Test
  public void testPlayers()
  {
    Club tramore = Club.findByName("tramore");

    assertEquals (2, tramore.players.size());

    Player mike  = Player.findByName("mike");
    Player jim   = Player.findByName("jim");
    Player frank = Player.findByName("framk");

    assertTrue  (tramore.players.contains(mike));
    assertTrue  (tramore.players.contains(jim));
    assertFalse (tramore.players.contains(frank));
  }  

This test should pass.

We might be interested in seeing how the database models the relationship. Comment out the @After annotation, and also all of the @Test annotations except one (the last one). Run the test, and then explore the Player objects in the @db interface. See if you can make sense of them. You may wish to do this occasionally, but be sure to comment in all the annotations, and perhaps also restart the application as leaving these annotations out will cause serious anomalies in the database.

Now introduce the following test:

  @Test
  public void testRemovePlayer()
  {
    Club tramore = Club.findByName("tramore");
    assertEquals(2, tramore.players.size());

    Player mike  = Player.findByName("mike");
    assertTrue(tramore.players.contains(mike));
    tramore.players.remove(mike);
    tramore.save();

    Club c = Club.findByName("tramore");
    assertEquals(1, c.players.size());

    mike.delete();
  }

Read and absorb the logic of this test. As an experiment, comment out the final line (deleting mike). Then look to the @db interface and note that mike is still in the database after the test have run.

Note also that if we leave the deletion of mike commented out, and re-run the tests - then other tests start to fail. The only cure now will be to restart the application.

Bidirectional Club / Player Relationship

We might like each player to also 'know' which club he/she belongs to. In fact his is probably a more natural relationship for these model elements.

In Visual Paradigm, select the association between Club and Player again, its properties may look like this:

Adjust Role A (the role at the Club end) by giving it a name, changing its navigability and multiplicity paramaters as shown here:

The class diagram will now look like this:

Contrast this with the earlier version:

Implementing this variant of the relationship requires additional attributes and annotations in both Player and Club.

Player:

  @ManyToOne
  public Club club;

Club

  @OneToMany(mappedBy="club", cascade=CascadeType.ALL)
  public List<Player> players;

The name 'club' must match in both the annotation and the attribute. We also need to make the following adjustment to the Club.addPlayer method:

  public void addPlayer(Player player)
  {
    player.club = this;
    players.add(player);
  }

This method ensures that the relationship is correctly established.

No adjustment to the model can be considered complete without a unit test. Introduce the following test to ClubTest:

  @Test
  public void testPlayerClub()
  {
    Player mike  = Player.findByName("mike");
    assertNotNull (mike.club);
    assertEquals ("tramore", mike.club.name);
  }

Exercises

This is an extension of the model to include:

  • Divisions
  • Sponsors

You should be able to:

  • Replicate this model in Visual Paradigm.
  • Incorporate the new classes into the jpamodel project
  • Build some unit tests to exercises the model