Table of Contents

Creating entities

The next step in creating our PBEM is creating the objects we want to have stored in the database. We refer to these classes as entities. The PBEM Engine uses the Hibernate persistence framework, and requires the use of annotations as defined by the Java Persistence API. If you are not familiar with either of these technologies, we recommend reading the relevant chapter of the Java EE 5 Tutorial, as well as the Hibernate documentation and the documentation for Hibernate Annotations.

The examples here are fairly simple, but if you want to create your own PBEM, we recommend reading the above sources as well.

Before we begin

The Engine provides a couple of basic entity skeletons that represent common patterns in PBEM development. Among other things, these skeletons allow for the following functionality:

These skeletons make use of the @MappedSuperclass annotation and can be used by extending the PlayerReferenceDomainObject or GameTurnDomainObject classes. Our examples use the former class, which give us:

* An ID of type Long * A reference to the relevant Game entity (part of the Engine) * A reference to the relevant Player entity (part of the Engine) * A reference to the current turn * A reference to another entity of the same type, in the previous turn

The Score entity

The simplest entity for our example game is the Score class, which is listed below. Each player has a score that may differ each turn. In addition to the properties defined by the parent classes, the score class only has a single integer field to store in the database.

/*
 * PBEM Engine Tutorial
 * Copyright (C) 2009 Jeroen Steenbeeke
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package nl.pbemengine.tutorial.entities;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
 
import nl.pbemengine.engine.ng.base.entity.PlayerReferenceDomainObject;
 
/**
 * @author Jeroen Steenbeeke
 * 
 */
@Entity
@Table(name = "RPS_SCORE")
public class Score extends PlayerReferenceDomainObject<Score> {
	@Column(nullable = false)
	private int score;
 
	public Score() {
	}
 
	/**
	 * @return the score
	 */
	public int getScore() {
		return score;
	}
 
	/**
	 * @param score
	 *            the score to set
	 */
	public void setScore(int score) {
		this.score = score;
	}
 
	/**
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + score;
		return result;
	}
 
	/**
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (!super.equals(obj)) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		Score other = (Score) obj;
		if (score != other.score) {
			return false;
		}
		return true;
	}
}

Though this is quite a bit of code, it really is quite simple - the @Entity annotation at the start indicate that this is an Entity, and therefore should be stored in the database, and the @Table annotation tells Hibernate in which table to store it. We strongly recommend using the @Table annotation to set table names, as to avoid conflicts with other PBEMs. We also recommend prefixing all table names with a given string (such as RPS_ in our example), and to not refer to this prefix in column names.

The Choice entity

The next entity we create is the Choice class, which reflects the choice made by a player in a given turn. This entity is also fairly straightforward, but contains an enumeration that we store in the database as an integer. Note the special annotations used.

/*
 * PBEM Engine Tutorial
 * Copyright (C) 2009 Jeroen Steenbeeke
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package nl.pbemengine.tutorial.entities;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table;
 
import nl.pbemengine.engine.ng.base.entity.PlayerReferenceDomainObject;
 
/**
 * A choice made by a player in a given turn
 * 
 * @author Jeroen Steenbeeke
 */
@Entity
@Table(name = "RPS_CHOICE")
public class Choice extends PlayerReferenceDomainObject<Choice> {
	public enum Sign {
		ROCK, PAPER, SCISSORS;
	}
 
	@Column(nullable = false)
	@Enumerated(EnumType.ORDINAL)
	private Sign sign;
 
	public Choice() {
	}
 
	/**
	 * @return the sign
	 */
	public Sign getSign() {
		return sign;
	}
 
	/**
	 * @param sign
	 *            the sign to set
	 */
	public void setSign(Sign sign) {
		this.sign = sign;
	}
 
	/**
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + ((sign == null) ? 0 : sign.hashCode());
		return result;
	}
 
	/**
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (!super.equals(obj)) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		Choice other = (Choice) obj;
		if (sign == null) {
			if (other.sign != null) {
				return false;
			}
		} else if (!sign.equals(other.sign)) {
			return false;
		}
		return true;
	}
}

Experienced programmers may note the implementation of the equals() and hashCode() methods. Implementing these properly prevents all sorts of nasty errors with Hibernate, given of course that there are no circular references (which will cause Stack overflows).

Creating the tables

Since these entities will be stored in tables, the database tables need to be created. This can be done in a number of ways, but the most convenient way is to implement the install() method of the Rules class. Below is an example that will use the entity classes and generate SQL commands using reflection and the Hibernate configuration. This is by no means a robust implementation, but it does ensure the proper creation of tables regardless of the underlying database.

       /**
	 * Called when a Ruleset is loaded for the first time, will initialize the
	 * ruleset
	 * 
	 * @param session
	 *            The Hibernate session used for initialization
	 */
	@Override
	public void install(Session session) {
		// List of classes we use as entities
		Class<?>[] classes = { Score.class, Choice.class };
 
		// Generate commands for table creation
		String[] commands = getHibernateConfiguration()
				.generateSchemaCreationScript(
						Dialect.getDialect(getHibernateConfiguration()
								.getProperties()));
		// Start a transaction to make the changes atomic (if applicable)
		Transaction trans = session.beginTransaction();
 
		// Iterate through our array of classes
		for (Class<?> c : classes) {
			// See if we have a Table annotation
			Table t = c.getAnnotation(Table.class);
			// If we do, use the name from the annotation, if not, use the name
			// of the entity class
			String tableName = (t != null) ? t.name() : c.getSimpleName();
 
			// Iterate through all commands next
			for (String command : commands) {
				// If the command contains the name of the table then we execute
				// the command
				if (command.contains(tableName)) {
					// By creating an SQL query
					SQLQuery sql = session.createSQLQuery(command);
					// And executing it
					sql.executeUpdate();
				}
			}
		}
 
		// Finally, we commit the transaction
		trans.commit();
 
	}

In the next section we will explain how to create DAO classes that are automatically injected into other Engine components.