Add JPA implementation based on NamedQueries

git-svn-id: https://svn.apache.org/repos/asf/continuum/branches/continuum-jpa-evenisse@652556 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/TODO.txt b/TODO.txt
new file mode 100644
index 0000000..ded53ef
--- /dev/null
+++ b/TODO.txt
@@ -0,0 +1,5 @@
+
+TODO for Continuum JPA store implementation
+-------------------------------------------
+
+1)  Review JPA annotated Entities.  
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..3704867
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,55 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.continuum</groupId>
+  <artifactId>continuum-model-jpa</artifactId>
+  <packaging>jar</packaging>
+  <version>1.0-SNAPSHOT</version>
+  <name>continuum-model-jpa</name>
+  <url>http://maven.apache.org</url>
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.4</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.openjpa</groupId>
+      <artifactId>openjpa</artifactId>
+      <version>1.0.1</version>
+    </dependency>
+    <dependency>
+      <groupId>hsqldb</groupId>
+      <artifactId>hsqldb</artifactId>
+      <version>1.8.0.7</version>
+      <scope>test</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring</artifactId>
+      <version>2.5</version>      
+    </dependency>
+    
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-test</artifactId>
+      <version>2.5</version>      
+    </dependency>
+    
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>1.5</source>
+          <target>1.5</target>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/src/main/java/org/apache/continuum/dao/api/GenericDao.java b/src/main/java/org/apache/continuum/dao/api/GenericDao.java
new file mode 100644
index 0000000..af7cb41
--- /dev/null
+++ b/src/main/java/org/apache/continuum/dao/api/GenericDao.java
@@ -0,0 +1,34 @@
+package org.apache.continuum.dao.api;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.orm.ObjectRetrievalFailureException;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
+ * @version $Id$
+ */
+public interface GenericDao<T>
+{
+    Class<T> getPersistentClass();
+
+    T findById( long id )
+        throws ObjectRetrievalFailureException;
+
+    @SuppressWarnings("unchecked")
+    List<T> findAll();
+
+    @SuppressWarnings("unchecked")
+    List<T> findByNamedQueryAndNamedParams( Class<T> entityClass, String queryName, Map<String, Object> params )
+        throws DataAccessException;
+
+    T findUniqByNamedQueryAndNamedParams( Class<T> entityClass, String queryName, Map<String, Object> params );
+
+    T saveOrUpdate( T entity );
+
+    void delete( long id );
+
+    void delete( T entity );
+}
diff --git a/src/main/java/org/apache/continuum/dao/impl/DaoFactoryImpl.java b/src/main/java/org/apache/continuum/dao/impl/DaoFactoryImpl.java
new file mode 100644
index 0000000..2d05d18
--- /dev/null
+++ b/src/main/java/org/apache/continuum/dao/impl/DaoFactoryImpl.java
@@ -0,0 +1,28 @@
+package org.apache.continuum.dao.impl;
+
+import org.apache.continuum.dao.api.GenericDao;
+import org.apache.continuum.model.project.Project;
+import org.apache.continuum.model.project.ProjectGroup;
+import org.apache.continuum.model.project.ProjectNotifier;
+
+/**
+ * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
+ * @version $Id$
+ */
+public class DaoFactoryImpl
+{
+    public GenericDao<ProjectGroup> createProjectGroupDao()
+    {
+        return new GenericDaoJpa<ProjectGroup>( ProjectGroup.class );
+    }
+
+    public GenericDao<Project> createProjectDao()
+    {
+        return new GenericDaoJpa<Project>( Project.class );
+    }
+
+    public GenericDao<ProjectNotifier> createNotifierDao()
+    {
+        return new GenericDaoJpa<ProjectNotifier>( ProjectNotifier.class );
+    }
+}
diff --git a/src/main/java/org/apache/continuum/dao/impl/GenericDaoJpa.java b/src/main/java/org/apache/continuum/dao/impl/GenericDaoJpa.java
new file mode 100644
index 0000000..dd66907
--- /dev/null
+++ b/src/main/java/org/apache/continuum/dao/impl/GenericDaoJpa.java
@@ -0,0 +1,106 @@
+package org.apache.continuum.dao.impl;
+
+import org.apache.continuum.dao.api.GenericDao;
+import org.apache.continuum.model.CommonPersistableEntity;
+import org.springframework.dao.DataAccessException;
+import org.springframework.orm.ObjectRetrievalFailureException;
+import org.springframework.orm.jpa.JpaCallback;
+import org.springframework.orm.jpa.support.JpaDaoSupport;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceException;
+import javax.persistence.Query;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
+ * @version $Id$
+ */
+public class GenericDaoJpa<T extends CommonPersistableEntity>
+    extends JpaDaoSupport
+    implements GenericDao<T>
+{
+    private Class<T> entityClass;
+
+    public GenericDaoJpa()
+    {
+    }
+
+    public GenericDaoJpa( Class<T> type )
+    {
+        entityClass = type;
+    }
+
+    public Class<T> getPersistentClass()
+    {
+        return entityClass;
+    }
+
+    public T findById( long id )
+        throws ObjectRetrievalFailureException
+    {
+        return getJpaTemplate().find( getPersistentClass(), id );
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<T> findAll()
+    {
+        String q = "SELECT z FROM " + entityClass.getSimpleName() + " z";
+        return (List<T>) getJpaTemplate().find( q );
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<T> findByNamedQueryAndNamedParams( Class<T> entityClass, String queryName, Map<String, Object> params )
+        throws DataAccessException
+    {
+        return (List<T>) getJpaTemplate().findByNamedQueryAndNamedParams( queryName, params );
+    }
+
+    @SuppressWarnings("unchecked")
+    public T findUniqByNamedQueryAndNamedParams( Class<T> entityClass, final String queryName,
+                                                 final Map<String, Object> params )
+        throws DataAccessException
+    {
+        JpaCallback cb = new JpaCallback()
+        {
+            public Object doInJpa( EntityManager entityManager )
+                throws PersistenceException
+            {
+                Query q = entityManager.createNamedQuery( queryName );
+                for ( String key : params.keySet() )
+                {
+                    q.setParameter( key, params.get( key ) );
+                }
+                return q.getSingleResult();
+            }
+        };
+        return (T) getJpaTemplate().execute( cb );
+    }
+
+    @Transactional
+    public T saveOrUpdate( T entity )
+    {
+        if ( null == entity.getId() )
+        {
+            getJpaTemplate().persist( entity );
+        }
+        else
+        {
+            entity = getJpaTemplate().merge( entity );
+        }
+        return entity;
+    }
+
+    @Transactional
+    public void delete( long id )
+    {
+        delete( findById( id ) );
+    }
+
+    public void delete( T entity )
+    {
+        getJpaTemplate().remove( entity );
+    }
+}
diff --git a/src/main/java/org/apache/continuum/model/CommonCreatedEntity.java b/src/main/java/org/apache/continuum/model/CommonCreatedEntity.java
new file mode 100644
index 0000000..6b8264d
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/CommonCreatedEntity.java
@@ -0,0 +1,45 @@
+/**
+ * 
+ */
+package org.apache.continuum.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.MappedSuperclass;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@MappedSuperclass
+public abstract class CommonCreatedEntity extends CommonPersistableEntity
+{
+
+    /**
+     * Date the entity was created.
+     */
+    @Temporal( TemporalType.TIMESTAMP )
+    @Column( name = "DATE_CREATED" )
+    private Date dateCreated;
+
+    /**
+     * @return the dateCreated
+     */
+    public Date getDateCreated()
+    {
+        return dateCreated;
+    }
+
+    /**
+     * @param dateCreated
+     *            the dateCreated to set
+     */
+    public void setDateCreated( Date dateCreated )
+    {
+        this.dateCreated = dateCreated;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/CommonPersistableEntity.java b/src/main/java/org/apache/continuum/model/CommonPersistableEntity.java
new file mode 100644
index 0000000..0907b3d
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/CommonPersistableEntity.java
@@ -0,0 +1,90 @@
+/**
+ * 
+ */
+package org.apache.continuum.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.MappedSuperclass;
+
+/**
+ * Common persistable entity.
+ * 
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@MappedSuperclass
+public abstract class CommonPersistableEntity implements Serializable
+{
+
+    /**
+     * Unique persistence identifier.
+     * <p>
+     * This is <code>null</code> if not persisted.
+     */
+    @Id
+    @GeneratedValue( strategy = GenerationType.IDENTITY )
+    @Column( name = "ID" )
+    private Long id;
+
+    /**
+     * @return the id which is the unique persistence identifier.
+     */
+    public Long getId()
+    {
+        return id;
+    }
+
+    /**
+     * @param id
+     *            Unique persistence identifier to set.
+     */
+    public void setId( Long id )
+    {
+        this.id = id;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ( ( id == null ) ? 0 : id.hashCode() );
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals( Object obj )
+    {
+        if ( this == obj )
+            return true;
+        if ( obj == null )
+            return false;
+        if ( getClass() != obj.getClass() )
+            return false;
+        CommonPersistableEntity other = (CommonPersistableEntity) obj;
+        if ( id == null )
+        {
+            if ( other.id != null )
+                return false;
+        }
+        else if ( !id.equals( other.id ) )
+            return false;
+        return true;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/CommonUpdatableEntity.java b/src/main/java/org/apache/continuum/model/CommonUpdatableEntity.java
new file mode 100644
index 0000000..d7f71a8
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/CommonUpdatableEntity.java
@@ -0,0 +1,70 @@
+/**
+ * 
+ */
+package org.apache.continuum.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.MappedSuperclass;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.Version;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@MappedSuperclass
+public abstract class CommonUpdatableEntity extends CommonCreatedEntity
+{
+
+    /**
+     * Date the entity was last updated.
+     */
+    @Temporal( TemporalType.TIMESTAMP )
+    @Column( name = "DATE_UPDATED" )
+    private Date dateUpdated;
+
+    /**
+     * Version for optimistic locking.
+     */
+    @Version
+    @Column( name = "OBJ_VERSION" )
+    private long objectVersion;
+
+    /**
+     * @return the dateUpdated
+     */
+    public Date getDateUpdated()
+    {
+        return dateUpdated;
+    }
+
+    /**
+     * @param dateUpdated
+     *            the dateUpdated to set
+     */
+    public void setDateUpdated( Date dateUpdated )
+    {
+        this.dateUpdated = dateUpdated;
+    }
+
+    /**
+     * @return the version
+     */
+    public long getObjectVersion()
+    {
+        return objectVersion;
+    }
+
+    /**
+     * @param version
+     *            the version to set
+     */
+    public void setObjectVersion( long version )
+    {
+        this.objectVersion = version;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/project/BuildDefinition.java b/src/main/java/org/apache/continuum/model/project/BuildDefinition.java
new file mode 100644
index 0000000..d7d7274
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/project/BuildDefinition.java
@@ -0,0 +1,97 @@
+package org.apache.continuum.model.project;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+import org.apache.continuum.model.system.Profile;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@Entity
+@Table(name = "BUILD_DEFINITION")
+public class BuildDefinition
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field defaultForProject
+     */
+    @Basic
+    @Column(name = "FLG_DEFAULT_PROJECT", nullable = false)
+    private boolean defaultForProject = false;
+
+    /**
+     * Field goals
+     */
+    @Basic
+    @Column(name = "GOALS")
+    private String goals;
+
+    /**
+     * Field arguments
+     */
+    @Basic
+    @Column(name = "ARGUMENTS")
+    private String arguments;
+
+    /**
+     * Field buildFile
+     */
+    @Basic
+    @Column(name = "BUILD_FILE")
+    private String buildFile;
+
+    /**
+     * Field buildFresh
+     */
+    @Basic
+    @Column(name = "FLG_BUILD_FRESH", nullable = false)
+    private boolean buildFresh = false;
+
+    /**
+     * Field description
+     */
+    @Basic
+    @Column(name = "DESCRIPTION")
+    private String description;
+
+    /**
+     * Field type
+     */
+    @Basic
+    @Column(name = "TYPE")
+    private String type;
+
+    /**
+     * Field schedule
+     */
+    @OneToOne
+    private Schedule schedule;
+
+    /**
+     * Field profile
+     */
+    @OneToOne
+    private Profile profile;
+
+    /**
+     * Field alwaysBuild
+     */
+    @Basic
+    @Column(name = "FLG_ALWAYS_BUILD")
+    private boolean alwaysBuild = false;
+
+    /**
+     * Field template
+     */
+    @Basic
+    @Column(name = "FLG_TEMPLATE")
+    private boolean template = false;
+
+}
diff --git a/src/main/java/org/apache/continuum/model/project/BuildDefinitionTemplate.java b/src/main/java/org/apache/continuum/model/project/BuildDefinitionTemplate.java
new file mode 100644
index 0000000..154a941
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/project/BuildDefinitionTemplate.java
@@ -0,0 +1,116 @@
+package org.apache.continuum.model.project;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import java.util.List;
+
+/**
+ * Template which contains some buildDefinitions
+ *
+ * @version $Id$
+ */
+@Entity
+@Table(name = "BUILD_DEFINITION_TEMPLATE")
+public class BuildDefinitionTemplate
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field name
+     */
+    @Basic
+    @Column(name = "NAME", nullable = false)
+    private String name;
+
+    /**
+     * Field continuumDefault
+     */
+    @Basic
+    @Column(name = "FLG_CONTINUUM_DEFAULT", nullable = false)
+    private boolean continuumDefault = false;
+
+    /**
+     * Field type
+     * <p/>
+     * TODO: Enum?
+     */
+    @Basic
+    @Column(name = "TEMPLATE_TYPE")
+    private String type;
+
+    /**
+     * Field buildDefinitions
+     */
+    @OneToMany
+    private List<BuildDefinition> buildDefinitions;
+
+    /**
+     * @return the name
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * @param name the name to set
+     */
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+    /**
+     * @return the continuumDefault
+     */
+    public boolean isContinuumDefault()
+    {
+        return continuumDefault;
+    }
+
+    /**
+     * @param continuumDefault the continuumDefault to set
+     */
+    public void setContinuumDefault( boolean continuumDefault )
+    {
+        this.continuumDefault = continuumDefault;
+    }
+
+    /**
+     * @return the type
+     */
+    public String getType()
+    {
+        return type;
+    }
+
+    /**
+     * @param type the type to set
+     */
+    public void setType( String type )
+    {
+        this.type = type;
+    }
+
+    /**
+     * @return the buildDefinitions
+     */
+    public List<BuildDefinition> getBuildDefinitions()
+    {
+        return buildDefinitions;
+    }
+
+    /**
+     * @param buildDefinitions the buildDefinitions to set
+     */
+    public void setBuildDefinitions( List<BuildDefinition> buildDefinitions )
+    {
+        this.buildDefinitions = buildDefinitions;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/project/BuildResult.java b/src/main/java/org/apache/continuum/model/project/BuildResult.java
new file mode 100644
index 0000000..64be193
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/project/BuildResult.java
@@ -0,0 +1,336 @@
+package org.apache.continuum.model.project;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+import org.apache.continuum.model.scm.ScmResult;
+import org.apache.continuum.model.scm.TestResult;
+
+import javax.persistence.Basic;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * This class is a single continuum build.
+ *
+ * @version $Id$
+ */
+@Entity
+@Table(name = "BUILD_RESULT")
+public class BuildResult
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field project
+     */
+    @ManyToOne
+    @JoinColumn(name = "ID_PROJECT")
+    private Project project;
+
+    /**
+     * Field buildDefinition
+     */
+    @OneToOne
+    @JoinColumn(name = "ID_BUILD_DEFINITION")
+    private BuildDefinition buildDefinition = null;
+
+    /**
+     * Field buildNumber
+     */
+    @Basic
+    @Column(name = "BUILD_NUMBER")
+    private int buildNumber = 0;
+
+    /**
+     * Field state.
+     * <p/>
+     * TODO: This is a candidate for enum.
+     */
+    @Basic
+    @Column(name = "RESULT_STATE")
+    private int state = 0;
+
+    /**
+     * Field trigger
+     * <p/>
+     * TODO: enum?
+     */
+    @Basic
+    @Column(name = "RESULT_TRIGGER")
+    private int trigger = 0;
+
+    /**
+     * Field startTime
+     */
+    @Temporal(TemporalType.TIME)
+    @Column(name = "START_TIME", nullable = false)
+    private Date startTime;
+
+    /**
+     * Field endTime
+     */
+    @Temporal(TemporalType.TIME)
+    @Column(name = "END_TIME", nullable = false)
+    private Date endTime;
+
+    /**
+     * Field error
+     */
+    @Basic
+    @Column(name = "ERROR")
+    private String error;
+
+    /**
+     * Field success
+     */
+    @Basic
+    @Column(name = "FLG_SUCCESS", nullable = false)
+    private boolean success = false;
+
+    /**
+     * Field exitCode
+     */
+    @Basic
+    @Column(name = "EXIT_CODE", nullable = false)
+    private int exitCode = 0;
+
+    /**
+     * Field scmResult
+     */
+    @OneToOne(fetch = FetchType.EAGER)
+    private ScmResult scmResult;
+
+    /**
+     * Field testResult
+     */
+    @OneToOne(fetch = FetchType.EAGER)
+    private TestResult testResult;
+
+    /**
+     * Field modifiedDependencies
+     */
+    @OneToMany(cascade = CascadeType.ALL)
+    @JoinTable(name = "PROJECT_DEPENDENCY", joinColumns = @JoinColumn(name = "BUILD_ID"),
+               inverseJoinColumns = @JoinColumn(name = "ID_PROJECT_DEPENDENCY"))
+    private List<ProjectDependency> modifiedDependencies;
+
+    /**
+     * @return the project
+     */
+    public Project getProject()
+    {
+        return project;
+    }
+
+    /**
+     * @param project the project to set
+     */
+    public void setProject( Project project )
+    {
+        this.project = project;
+    }
+
+    /**
+     * @return the buildDefinition
+     */
+    public BuildDefinition getBuildDefinition()
+    {
+        return buildDefinition;
+    }
+
+    /**
+     * @param buildDefinition the buildDefinition to set
+     */
+    public void setBuildDefinition( BuildDefinition buildDefinition )
+    {
+        this.buildDefinition = buildDefinition;
+    }
+
+    /**
+     * @return the buildNumber
+     */
+    public int getBuildNumber()
+    {
+        return buildNumber;
+    }
+
+    /**
+     * @param buildNumber the buildNumber to set
+     */
+    public void setBuildNumber( int buildNumber )
+    {
+        this.buildNumber = buildNumber;
+    }
+
+    /**
+     * @return the state
+     */
+    public int getState()
+    {
+        return state;
+    }
+
+    /**
+     * @param state the state to set
+     */
+    public void setState( int state )
+    {
+        this.state = state;
+    }
+
+    /**
+     * @return the trigger
+     */
+    public int getTrigger()
+    {
+        return trigger;
+    }
+
+    /**
+     * @param trigger the trigger to set
+     */
+    public void setTrigger( int trigger )
+    {
+        this.trigger = trigger;
+    }
+
+    /**
+     * @return the startTime
+     */
+    public Date getStartTime()
+    {
+        return startTime;
+    }
+
+    /**
+     * @param startTime the startTime to set
+     */
+    public void setStartTime( Date startTime )
+    {
+        this.startTime = startTime;
+    }
+
+    /**
+     * @return the endTime
+     */
+    public Date getEndTime()
+    {
+        return endTime;
+    }
+
+    /**
+     * @param endTime the endTime to set
+     */
+    public void setEndTime( Date endTime )
+    {
+        this.endTime = endTime;
+    }
+
+    /**
+     * @return the error
+     */
+    public String getError()
+    {
+        return error;
+    }
+
+    /**
+     * @param error the error to set
+     */
+    public void setError( String error )
+    {
+        this.error = error;
+    }
+
+    /**
+     * @return the success
+     */
+    public boolean isSuccess()
+    {
+        return success;
+    }
+
+    /**
+     * @param success the success to set
+     */
+    public void setSuccess( boolean success )
+    {
+        this.success = success;
+    }
+
+    /**
+     * @return the exitCode
+     */
+    public int getExitCode()
+    {
+        return exitCode;
+    }
+
+    /**
+     * @param exitCode the exitCode to set
+     */
+    public void setExitCode( int exitCode )
+    {
+        this.exitCode = exitCode;
+    }
+
+    /**
+     * @return the scmResult
+     */
+    public ScmResult getScmResult()
+    {
+        return scmResult;
+    }
+
+    /**
+     * @param scmResult the scmResult to set
+     */
+    public void setScmResult( ScmResult scmResult )
+    {
+        this.scmResult = scmResult;
+    }
+
+    /**
+     * @return the testResult
+     */
+    public TestResult getTestResult()
+    {
+        return testResult;
+    }
+
+    /**
+     * @param testResult the testResult to set
+     */
+    public void setTestResult( TestResult testResult )
+    {
+        this.testResult = testResult;
+    }
+
+    /**
+     * @return the modifiedDependencies
+     */
+    public List<ProjectDependency> getModifiedDependencies()
+    {
+        return modifiedDependencies;
+    }
+
+    /**
+     * @param modifiedDependencies the modifiedDependencies to set
+     */
+    public void setModifiedDependencies( List<ProjectDependency> modifiedDependencies )
+    {
+        this.modifiedDependencies = modifiedDependencies;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/project/ContinuumModelloMetadata.java b/src/main/java/org/apache/continuum/model/project/ContinuumModelloMetadata.java
new file mode 100644
index 0000000..06fb15b
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/project/ContinuumModelloMetadata.java
@@ -0,0 +1,23 @@
+package org.apache.continuum.model.project;
+
+// Model class imports
+
+/**
+ * Generated ModelloMetadata class for Continuum.
+ * 
+ * @author Mr Modello
+ */
+public class ContinuumModelloMetadata
+{
+    private String modelVersion;
+
+    public String getModelVersion()
+    {
+        return modelVersion;
+    }
+
+    public void setModelVersion( String modelVersion )
+    {
+        this.modelVersion = modelVersion;
+    }
+}
diff --git a/src/main/java/org/apache/continuum/model/project/ContinuumProjectState.java b/src/main/java/org/apache/continuum/model/project/ContinuumProjectState.java
new file mode 100644
index 0000000..889586c
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/project/ContinuumProjectState.java
@@ -0,0 +1,102 @@
+package org.apache.continuum.model.project;
+
+/**
+ * null
+ * 
+ * @version $Revision$ $Date$
+ */
+public class ContinuumProjectState implements java.io.Serializable
+{
+
+    /**
+     * Field name
+     */
+    private String name;
+
+    // -----------/
+    // - Methods -/
+    // -----------/
+
+    /**
+     * Get null
+     */
+    public String getName()
+    {
+        return this.name;
+    } // -- String getName()
+
+    /**
+     * Set null
+     * 
+     * @param name
+     */
+    public void setName( String name )
+    {
+        this.name = name;
+    } // -- void setName(String)
+
+    public final static int NEW = 1;
+
+    public final static int OK = 2;
+
+    public final static int FAILED = 3;
+
+    public final static int ERROR = 4;
+
+    public final static int BUILDING = 6;
+
+    public final static int CHECKING_OUT = 7;
+
+    public final static int UPDATING = 8;
+
+    public final static int WARNING = 9;
+
+    public final static int CHECKEDOUT = 10;
+
+    // TODO: maybe move these to another class
+    public static final int TRIGGER_FORCED = 1;
+
+    // TODO: remove
+    public static final int TRIGGER_SCHEDULED = 0;
+
+    public static final int TRIGGER_UNKNOWN = TRIGGER_SCHEDULED;
+
+    public String getI18nKey()
+    {
+        return "org.apache.continuum.project.state." + name;
+    }
+
+    public boolean equals( Object object )
+    {
+        if ( !( object instanceof ContinuumProjectState ) )
+        {
+            return false;
+        }
+
+        ContinuumProjectState other = (ContinuumProjectState) object;
+
+        return name.equals( other.name );
+    }
+
+    public int hashCode()
+    {
+        return name.hashCode();
+    }
+
+    public String toString()
+    {
+        return name;
+    }
+
+    private String modelEncoding = "UTF-8";
+
+    public void setModelEncoding( String modelEncoding )
+    {
+        this.modelEncoding = modelEncoding;
+    }
+
+    public String getModelEncoding()
+    {
+        return modelEncoding;
+    }
+}
diff --git a/src/main/java/org/apache/continuum/model/project/Project.java b/src/main/java/org/apache/continuum/model/project/Project.java
new file mode 100644
index 0000000..02bb17d
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/project/Project.java
@@ -0,0 +1,599 @@
+package org.apache.continuum.model.project;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+import org.apache.continuum.model.scm.ScmResult;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.NamedQuery;
+import java.util.List;
+
+/**
+ * A Project registered in the system.
+ *
+ * @version $Id$
+ */
+@Entity
+@Table(name = "PROJECT")
+@NamedQueries(
+{
+    @NamedQuery(name = "Project.findAll", query = "SELECT p from Project as p"),
+    @NamedQuery(name = "Project.find", query = "SELECT p from Project as p WHERE p.groupId = :groupId AND p.artifactId = :artifactId AND p.version = :version")
+})
+
+public class Project
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field groupId
+     */
+    @Basic
+    @Column(name = "GROUP_ID")
+    private String groupId;
+
+    /**
+     * Field artifactId
+     */
+    @Basic
+    @Column(name = "ARTIFACT_ID")
+    private String artifactId;
+
+    /**
+     * Artifact version
+     */
+    @Basic
+    @Column(name = "VERSION")
+    private String version;
+
+    /**
+     * Field executorId
+     */
+    @Basic
+    @Column(name = "EXECUTOR_ID")
+    private String executorId;
+
+    /**
+     * Field name
+     */
+    @Basic
+    @Column(name = "NAME")
+    private String name;
+
+    /**
+     * Field description
+     */
+    @Basic
+    @Column(name = "DESCRIPTION")
+    private String description;
+
+    /**
+     * Field url
+     */
+    @Basic
+    @Column(name = "URL")
+    private String url;
+
+    /**
+     * Field scmUrl
+     */
+    @Basic
+    @Column(name = "SCM_URL")
+    private String scmUrl;
+
+    /**
+     * Field scmTag
+     */
+    @Basic
+    @Column(name = "SCM_TAG")
+    private String scmTag;
+
+    /**
+     * Field scmUsername
+     */
+    @Basic
+    @Column(name = "SCM_USERNAME")
+    private String scmUsername;
+
+    /**
+     * Field scmPassword
+     */
+    @Basic
+    @Column(name = "SCM_PASSWORD")
+    private String scmPassword;
+
+    /**
+     * Field scmUseCache
+     */
+    @Basic
+    @Column(name = "FLG_SCM_USE_CACHE", nullable = false)
+    private boolean scmUseCache = false;
+
+    /**
+     * Field state.
+     * <p/>
+     * TODO: Review! This is a candidate for an enum type.
+     */
+    @Basic
+    @Column(name = "STATE")
+    private int state = 1;
+
+    /**
+     * Field oldState
+     * <p/>
+     * TODO: Review! This is a candidate for an enum type.
+     */
+    @Basic
+    @Column(name = "OLD_STATE")
+    private int oldState = 0;
+
+    /**
+     * Field latestBuildId
+     */
+    @Basic
+    @Column(name = "LATEST_BUILD_ID")
+    private int latestBuildId = 0;
+
+    /**
+     * Field buildNumber
+     */
+    @Basic
+    @Column(name = "BUILD_NUMBER")
+    private int buildNumber = 0;
+
+    /**
+     * Field workingDirectory
+     */
+    @Basic
+    @Column(name = "WORKING_DIRECTORY")
+    private String workingDirectory;
+
+    /**
+     * Collection of {@link BuildResult}s for this {@link Project} instance.
+     */
+    @OneToMany(mappedBy = "project")
+    private List<BuildResult> buildResults;
+
+    /**
+     * Field checkoutResult
+     */
+    @OneToOne
+    @JoinColumn(name = "ID_CHECKOUT_RESULT")
+    private ScmResult checkoutResult;
+
+    /**
+     * Field developers
+     * <p/>
+     * TODO:
+     */
+    @OneToMany
+    private List<ProjectDeveloper> developers;
+
+    /**
+     * Field parent
+     */
+    @OneToOne
+    @JoinColumn(name = "ID_PARENT")
+    private ProjectDependency parent;
+
+    /**
+     * Field dependencies
+     */
+    @OneToMany
+    private List<ProjectDependency> dependencies;
+
+    /**
+     * Field projectGroup
+     */
+    @ManyToOne
+    @JoinColumn(name = "ID_PROJECT_GROUP")
+    private ProjectGroup projectGroup;
+
+    /**
+     * Field notifiers
+     */
+    @OneToMany
+    private List<ProjectNotifier> notifiers;
+
+    public void addNotifier( ProjectNotifier n )
+    {
+        notifiers.add( n );
+    }
+
+    public void removeNotifier( ProjectNotifier notifier )
+    {
+        notifiers.remove( notifier );
+    }
+
+    /**
+     * @return the groupId
+     */
+    public String getGroupId()
+    {
+        return groupId;
+    }
+
+    /**
+     * @param groupId the groupId to set
+     */
+    public void setGroupId( String groupId )
+    {
+        this.groupId = groupId;
+    }
+
+    /**
+     * @return the artifactId
+     */
+    public String getArtifactId()
+    {
+        return artifactId;
+    }
+
+    /**
+     * @param artifactId the artifactId to set
+     */
+    public void setArtifactId( String artifactId )
+    {
+        this.artifactId = artifactId;
+    }
+
+    /**
+     * @return the version
+     */
+    public String getVersion()
+    {
+        return version;
+    }
+
+    /**
+     * @param version the version to set
+     */
+    public void setVersion( String version )
+    {
+        this.version = version;
+    }
+
+    /**
+     * @return the executorId
+     */
+    public String getExecutorId()
+    {
+        return executorId;
+    }
+
+    /**
+     * @param executorId the executorId to set
+     */
+    public void setExecutorId( String executorId )
+    {
+        this.executorId = executorId;
+    }
+
+    /**
+     * @return the name
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * @param name the name to set
+     */
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+    /**
+     * @return the description
+     */
+    public String getDescription()
+    {
+        return description;
+    }
+
+    /**
+     * @param description the description to set
+     */
+    public void setDescription( String description )
+    {
+        this.description = description;
+    }
+
+    /**
+     * @return the url
+     */
+    public String getUrl()
+    {
+        return url;
+    }
+
+    /**
+     * @param url the url to set
+     */
+    public void setUrl( String url )
+    {
+        this.url = url;
+    }
+
+    /**
+     * @return the scmUrl
+     */
+    public String getScmUrl()
+    {
+        return scmUrl;
+    }
+
+    /**
+     * @param scmUrl the scmUrl to set
+     */
+    public void setScmUrl( String scmUrl )
+    {
+        this.scmUrl = scmUrl;
+    }
+
+    /**
+     * @return the scmTag
+     */
+    public String getScmTag()
+    {
+        return scmTag;
+    }
+
+    /**
+     * @param scmTag the scmTag to set
+     */
+    public void setScmTag( String scmTag )
+    {
+        this.scmTag = scmTag;
+    }
+
+    /**
+     * @return the scmUsername
+     */
+    public String getScmUsername()
+    {
+        return scmUsername;
+    }
+
+    /**
+     * @param scmUsername the scmUsername to set
+     */
+    public void setScmUsername( String scmUsername )
+    {
+        this.scmUsername = scmUsername;
+    }
+
+    /**
+     * @return the scmPassword
+     */
+    public String getScmPassword()
+    {
+        return scmPassword;
+    }
+
+    /**
+     * @param scmPassword the scmPassword to set
+     */
+    public void setScmPassword( String scmPassword )
+    {
+        this.scmPassword = scmPassword;
+    }
+
+    /**
+     * @return the scmUseCache
+     */
+    public boolean isScmUseCache()
+    {
+        return scmUseCache;
+    }
+
+    /**
+     * @param scmUseCache the scmUseCache to set
+     */
+    public void setScmUseCache( boolean scmUseCache )
+    {
+        this.scmUseCache = scmUseCache;
+    }
+
+    /**
+     * @return the state
+     */
+    public int getState()
+    {
+        return state;
+    }
+
+    /**
+     * @param state the state to set
+     */
+    public void setState( int state )
+    {
+        this.state = state;
+    }
+
+    /**
+     * @return the oldState
+     */
+    public int getOldState()
+    {
+        return oldState;
+    }
+
+    /**
+     * @param oldState the oldState to set
+     */
+    public void setOldState( int oldState )
+    {
+        this.oldState = oldState;
+    }
+
+    /**
+     * @return the latestBuildId
+     */
+    public int getLatestBuildId()
+    {
+        return latestBuildId;
+    }
+
+    /**
+     * @param latestBuildId the latestBuildId to set
+     */
+    public void setLatestBuildId( int latestBuildId )
+    {
+        this.latestBuildId = latestBuildId;
+    }
+
+    /**
+     * @return the buildNumber
+     */
+    public int getBuildNumber()
+    {
+        return buildNumber;
+    }
+
+    /**
+     * @param buildNumber the buildNumber to set
+     */
+    public void setBuildNumber( int buildNumber )
+    {
+        this.buildNumber = buildNumber;
+    }
+
+    /**
+     * @return the workingDirectory
+     */
+    public String getWorkingDirectory()
+    {
+        return workingDirectory;
+    }
+
+    /**
+     * @param workingDirectory the workingDirectory to set
+     */
+    public void setWorkingDirectory( String workingDirectory )
+    {
+        this.workingDirectory = workingDirectory;
+    }
+
+    /**
+     * @return the buildResults
+     */
+    public List<BuildResult> getBuildResults()
+    {
+        return buildResults;
+    }
+
+    /**
+     * @param buildResults the buildResults to set
+     */
+    public void setBuildResults( List<BuildResult> buildResults )
+    {
+        this.buildResults = buildResults;
+    }
+
+    /**
+     * @return the checkoutResult
+     */
+    public ScmResult getCheckoutResult()
+    {
+        return checkoutResult;
+    }
+
+    /**
+     * @param checkoutResult the checkoutResult to set
+     */
+    public void setCheckoutResult( ScmResult checkoutResult )
+    {
+        this.checkoutResult = checkoutResult;
+    }
+
+    /**
+     * @return the developers
+     */
+    public List<ProjectDeveloper> getDevelopers()
+    {
+        return developers;
+    }
+
+    /**
+     * @param developers the developers to set
+     */
+    public void setDevelopers( List<ProjectDeveloper> developers )
+    {
+        this.developers = developers;
+    }
+
+    /**
+     * @return the parent
+     */
+    public ProjectDependency getParent()
+    {
+        return parent;
+    }
+
+    /**
+     * @param parent the parent to set
+     */
+    public void setParent( ProjectDependency parent )
+    {
+        this.parent = parent;
+    }
+
+    /**
+     * @return the dependencies
+     */
+    public List<ProjectDependency> getDependencies()
+    {
+        return dependencies;
+    }
+
+    /**
+     * @param dependencies the dependencies to set
+     */
+    public void setDependencies( List<ProjectDependency> dependencies )
+    {
+        this.dependencies = dependencies;
+    }
+
+    /**
+     * @return the projectGroup
+     */
+    public ProjectGroup getProjectGroup()
+    {
+        return projectGroup;
+    }
+
+    /**
+     * @param projectGroup the projectGroup to set
+     */
+    public void setProjectGroup( ProjectGroup projectGroup )
+    {
+        this.projectGroup = projectGroup;
+    }
+
+    /**
+     * @return the notifiers
+     */
+    public List<ProjectNotifier> getNotifiers()
+    {
+        return notifiers;
+    }
+
+    /**
+     * @param notifiers the notifiers to set
+     */
+    public void setNotifiers( List<ProjectNotifier> notifiers )
+    {
+        this.notifiers = notifiers;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/project/ProjectDependency.java b/src/main/java/org/apache/continuum/model/project/ProjectDependency.java
new file mode 100644
index 0000000..8acbc6f
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/project/ProjectDependency.java
@@ -0,0 +1,89 @@
+package org.apache.continuum.model.project;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@Entity
+@Table(name = "PROJECT_DEPENDENCY")
+public class ProjectDependency
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field groupId
+     */
+    @Basic
+    @Column(name = "GROUP_ID")
+    private String groupId;
+
+    /**
+     * Field artifactId
+     */
+    @Basic
+    @Column(name = "ARTIFACT_ID")
+    private String artifactId;
+
+    /**
+     * Field version
+     */
+    @Basic
+    @Column(name = "VERSION")
+    private String version;
+
+    /**
+     * @return the groupId
+     */
+    public String getGroupId()
+    {
+        return groupId;
+    }
+
+    /**
+     * @param groupId the groupId to set
+     */
+    public void setGroupId( String groupId )
+    {
+        this.groupId = groupId;
+    }
+
+    /**
+     * @return the artifactId
+     */
+    public String getArtifactId()
+    {
+        return artifactId;
+    }
+
+    /**
+     * @param artifactId the artifactId to set
+     */
+    public void setArtifactId( String artifactId )
+    {
+        this.artifactId = artifactId;
+    }
+
+    /**
+     * @return the version
+     */
+    public String getVersion()
+    {
+        return version;
+    }
+
+    /**
+     * @param version the version to set
+     */
+    public void setVersion( String version )
+    {
+        this.version = version;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/project/ProjectDeveloper.java b/src/main/java/org/apache/continuum/model/project/ProjectDeveloper.java
new file mode 100644
index 0000000..66532ed
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/project/ProjectDeveloper.java
@@ -0,0 +1,48 @@
+package org.apache.continuum.model.project;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@Entity
+@Table(name = "PROJECT_DEVELOPER")
+public class ProjectDeveloper
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field scmId
+     */
+    @Basic
+    @Column(name = "SCM_USERNAME")
+    private String scmId;
+
+    /**
+     * Field name
+     */
+    @Basic
+    @Column(name = "NAME")
+    private String name;
+
+    /**
+     * Field email
+     */
+    @Basic
+    @Column(name = "EMAIL")
+    private String email;
+
+    /**
+     * Field continuumId
+     */
+    @Basic
+    @Column(name = "CONTINUUM_ID", nullable = false)
+    private int continuumId = 0;
+
+}
diff --git a/src/main/java/org/apache/continuum/model/project/ProjectGroup.java b/src/main/java/org/apache/continuum/model/project/ProjectGroup.java
new file mode 100644
index 0000000..d6d2067
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/project/ProjectGroup.java
@@ -0,0 +1,189 @@
+package org.apache.continuum.model.project;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@Entity
+@Table(name = "PROJECT_GROUP")
+@NamedQueries(
+{
+    @NamedQuery ( name = "ProjectGroup.findProjectGroup", query = "SELECT pg FROM ProjectGroup as pg WHERE pg.groupId = :groupId"),
+    @NamedQuery ( name = "ProjectGroup.findProjects", query = "SELECT pg.projects FROM ProjectGroup as pg WHERE pg = :projectGroup")
+})
+public class ProjectGroup
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field groupId
+     */
+    @Basic
+    @Column(name = "GROUP_ID")
+    private String groupId;
+
+    /**
+     * Field name
+     */
+    @Basic
+    @Column(name = "NAME")
+    private String name;
+
+    /**
+     * Field description
+     */
+    @Basic
+    @Column(name = "DESCRIPTION")
+    private String description;
+
+    /**
+     * Field projects
+     */
+    @OneToMany(mappedBy = "projectGroup")
+    private List<Project> projects;
+
+    /**
+     * Field notifiers
+     */
+    @OneToMany
+    private List<ProjectNotifier> notifiers;
+
+    /**
+     * Field buildDefinitions
+     */
+    @OneToMany
+    private List<BuildDefinition> buildDefinitions;
+
+    public void addProject( Project p )
+    {
+        projects.add( p );
+    }
+
+    public void addNotifier( ProjectNotifier n )
+    {
+        notifiers.add( n );
+    }
+
+    public void removeProject( Project p )
+    {
+        projects.remove( p );
+    }
+
+    public void removeNotifier( ProjectNotifier notifier )
+    {
+        notifiers.remove( notifier );
+    }
+
+    /**
+     * @return the groupId
+     */
+    public String getGroupId()
+    {
+        return groupId;
+    }
+
+    /**
+     * @param groupId the groupId to set
+     */
+    public void setGroupId( String groupId )
+    {
+        this.groupId = groupId;
+    }
+
+    /**
+     * @return the name
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * @param name the name to set
+     */
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+    /**
+     * @return the description
+     */
+    public String getDescription()
+    {
+        return description;
+    }
+
+    /**
+     * @param description the description to set
+     */
+    public void setDescription( String description )
+    {
+        this.description = description;
+    }
+
+    /**
+     * @return the projects
+     */
+    public List<Project> getProjects()
+    {
+        if ( null == this.projects )
+        {
+            this.projects = new ArrayList<Project>();
+        }
+        return projects;
+    }
+
+    /**
+     * @param projects the projects to set
+     */
+    public void setProjects( List<Project> projects )
+    {
+        this.projects = projects;
+    }
+
+    /**
+     * @return the notifiers
+     */
+    public List<ProjectNotifier> getNotifiers()
+    {
+        return notifiers;
+    }
+
+    /**
+     * @param notifiers the notifiers to set
+     */
+    public void setNotifiers( List<ProjectNotifier> notifiers )
+    {
+        this.notifiers = notifiers;
+    }
+
+    /**
+     * @return the buildDefinitions
+     */
+    public List<BuildDefinition> getBuildDefinitions()
+    {
+        return buildDefinitions;
+    }
+
+    /**
+     * @param buildDefinitions the buildDefinitions to set
+     */
+    public void setBuildDefinitions( List<BuildDefinition> buildDefinitions )
+    {
+        this.buildDefinitions = buildDefinitions;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/project/ProjectNotifier.java b/src/main/java/org/apache/continuum/model/project/ProjectNotifier.java
new file mode 100644
index 0000000..3e563eb
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/project/ProjectNotifier.java
@@ -0,0 +1,119 @@
+package org.apache.continuum.model.project;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import java.util.Map;
+
+/**
+ * Configures one method for notifying users/developers when a build breaks.
+ * <p/>
+ * TODO: Review this to use discriminators for different Notifier extensions.
+ *
+ * @version $Id$
+ */
+@Entity
+@Table(name = "PROJECT_NOTIFIER")
+@NamedQueries
+({
+    @NamedQuery( name = "Notifier.findAllFromProject", query = "SELECT p.notifiers from Project as p WHERE p.id = :projectId"),
+    @NamedQuery( name = "Notifier.findAllFromProjectGroup", query = "SELECT pg.notifiers from ProjectGroup as pg WHERE pg.id = :projectGroupId")
+})
+public class ProjectNotifier
+    extends CommonUpdatableEntity
+{
+    /**
+     * Field type
+     * <p/>
+     * TODO: This is a candidate for enum type.
+     */
+    @Column(name = "GROUP_ID")
+    private String type = "mail";
+
+    /**
+     * Field from
+     */
+    @Column(name = "NOTIFIER_ORIGIN", nullable = false)
+    private int from = 0;
+
+    /**
+     * Field enabled
+     */
+    @Column(name = "FLG_ENABLED", nullable = false)
+    private boolean enabled = true;
+
+    /**
+     * Field recipientType
+     */
+    @Column(name = "RECIPIENT_TYPE", nullable = false)
+    private int recipientType = 0;
+
+    /**
+     * Field sendOnSuccess
+     */
+    @Column(name = "FLG_SEND_ON_SUCCESS", nullable = false)
+    private boolean sendOnSuccess = true;
+
+    /**
+     * Field sendOnFailure
+     */
+    @Column(name = "FLG_SEND_ON_FAILURE", nullable = false)
+    private boolean sendOnFailure = true;
+
+    /**
+     * Field sendOnError
+     */
+    @Column(name = "FLG_SEND_ON_ERROR", nullable = false)
+    private boolean sendOnError = true;
+
+    /**
+     * Field sendOnWarning
+     */
+    @Column(name = "FLG_SEND_ON_WARNING", nullable = false)
+    private boolean sendOnWarning = true;
+
+    /**
+     * Field configuration.
+     * <p/>
+     * TODO: Map!
+     */
+    @Transient
+    private Map configuration;
+
+    /**
+     * TODO: Map! Enum?
+     */
+    public static final int FROM_PROJECT = 1;
+
+    /**
+     * TODO: Map! Enum?
+     */
+    public static final int FROM_USER = 2;
+
+    /**
+     * Method toString
+     */
+    public java.lang.String toString()
+    {
+        StringBuffer buf = new StringBuffer();
+        buf.append( "id = '" );
+        buf.append( getId() + "'" );
+        return buf.toString();
+    } // -- java.lang.String toString()
+
+    public boolean isFromProject()
+    {
+        return from == FROM_PROJECT;
+    }
+
+    public boolean isFromUser()
+    {
+        return from == FROM_USER;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/project/Schedule.java b/src/main/java/org/apache/continuum/model/project/Schedule.java
new file mode 100644
index 0000000..fcdc8c7
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/project/Schedule.java
@@ -0,0 +1,158 @@
+package org.apache.continuum.model.project;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@Entity
+@Table(name = "BUILD_SCHEDULE")
+public class Schedule
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field active
+     */
+    @Basic
+    @Column(name = "FLG_ACTIVE", nullable = false)
+    private boolean active = false;
+
+    /**
+     * Field name
+     */
+    @Basic
+    @Column(name = "NAME", nullable = false)
+    private String name;
+
+    /**
+     * Field description
+     */
+    @Basic
+    @Column(name = "DESCRIPTION")
+    private String description;
+
+    /**
+     * Field delay
+     */
+    @Basic
+    @Column(name = "SCHEDULE_DELAY")
+    private int delay = 0;
+
+    /**
+     * Field maxJobExecutionTime
+     */
+    @Basic
+    @Column(name = "MAX_JOB_EXECUTION_TIME")
+    private int maxJobExecutionTime = 3600;
+
+    /**
+     * Field cronExpression
+     */
+    @Basic
+    @Column(name = "CRON_EXPRESSION")
+    private String cronExpression;
+
+    /**
+     * @return the active
+     */
+    public boolean isActive()
+    {
+        return active;
+    }
+
+    /**
+     * @param active the active to set
+     */
+    public void setActive( boolean active )
+    {
+        this.active = active;
+    }
+
+    /**
+     * @return the name
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * @param name the name to set
+     */
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+    /**
+     * @return the description
+     */
+    public String getDescription()
+    {
+        return description;
+    }
+
+    /**
+     * @param description the description to set
+     */
+    public void setDescription( String description )
+    {
+        this.description = description;
+    }
+
+    /**
+     * @return the delay
+     */
+    public int getDelay()
+    {
+        return delay;
+    }
+
+    /**
+     * @param delay the delay to set
+     */
+    public void setDelay( int delay )
+    {
+        this.delay = delay;
+    }
+
+    /**
+     * @return the maxJobExecutionTime
+     */
+    public int getMaxJobExecutionTime()
+    {
+        return maxJobExecutionTime;
+    }
+
+    /**
+     * @param maxJobExecutionTime the maxJobExecutionTime to set
+     */
+    public void setMaxJobExecutionTime( int maxJobExecutionTime )
+    {
+        this.maxJobExecutionTime = maxJobExecutionTime;
+    }
+
+    /**
+     * @return the cronExpression
+     */
+    public String getCronExpression()
+    {
+        return cronExpression;
+    }
+
+    /**
+     * @param cronExpression the cronExpression to set
+     */
+    public void setCronExpression( String cronExpression )
+    {
+        this.cronExpression = cronExpression;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/scm/ChangeFile.java b/src/main/java/org/apache/continuum/model/scm/ChangeFile.java
new file mode 100644
index 0000000..e8aba50
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/scm/ChangeFile.java
@@ -0,0 +1,91 @@
+package org.apache.continuum.model.scm;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@Entity
+@Table(name = "CHANGE_FILE")
+public class ChangeFile
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field name
+     */
+    @Basic
+    @Column(name = "NAME")
+    private String name;
+
+    /**
+     * Field revision
+     */
+    @Basic
+    @Column(name = "REVISION")
+    private String revision;
+
+    /**
+     * Field status
+     * <p/>
+     * TODO: Enum?
+     */
+    @Basic
+    @Column(name = "STATUS")
+    private String status;
+
+    /**
+     * @return the name
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * @param name the name to set
+     */
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+    /**
+     * @return the revision
+     */
+    public String getRevision()
+    {
+        return revision;
+    }
+
+    /**
+     * @param revision the revision to set
+     */
+    public void setRevision( String revision )
+    {
+        this.revision = revision;
+    }
+
+    /**
+     * @return the status
+     */
+    public String getStatus()
+    {
+        return status;
+    }
+
+    /**
+     * @param status the status to set
+     */
+    public void setStatus( String status )
+    {
+        this.status = status;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/scm/ChangeSet.java b/src/main/java/org/apache/continuum/model/scm/ChangeSet.java
new file mode 100644
index 0000000..c556cb0
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/scm/ChangeSet.java
@@ -0,0 +1,116 @@
+package org.apache.continuum.model.scm;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@Entity
+@Table(name = "CHANGE_SET")
+public class ChangeSet
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field author
+     */
+    @Basic
+    @Column(name = "AUTHOR")
+    private String author;
+
+    /**
+     * Field comment
+     */
+    @Basic
+    @Column(name = "COMMENT_TEXT")
+    private String comment;
+
+    /**
+     * Field date
+     */
+    @Temporal(TemporalType.TIME)
+    @Column(name = "DATE", nullable = false)
+    private Date date;
+
+    /**
+     * Field files
+     */
+    @OneToMany
+    private List<ChangeFile> files;
+
+    /**
+     * @return the author
+     */
+    public String getAuthor()
+    {
+        return author;
+    }
+
+    /**
+     * @param author the author to set
+     */
+    public void setAuthor( String author )
+    {
+        this.author = author;
+    }
+
+    /**
+     * @return the comment
+     */
+    public String getComment()
+    {
+        return comment;
+    }
+
+    /**
+     * @param comment the comment to set
+     */
+    public void setComment( String comment )
+    {
+        this.comment = comment;
+    }
+
+    /**
+     * @return the date
+     */
+    public Date getDate()
+    {
+        return date;
+    }
+
+    /**
+     * @param date the date to set
+     */
+    public void setDate( Date date )
+    {
+        this.date = date;
+    }
+
+    /**
+     * @return the files
+     */
+    public List<ChangeFile> getFiles()
+    {
+        return files;
+    }
+
+    /**
+     * @param files the files to set
+     */
+    public void setFiles( List<ChangeFile> files )
+    {
+        this.files = files;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/scm/ScmResult.java b/src/main/java/org/apache/continuum/model/scm/ScmResult.java
new file mode 100644
index 0000000..ce44bca
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/scm/ScmResult.java
@@ -0,0 +1,159 @@
+package org.apache.continuum.model.scm;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import java.util.List;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@Entity
+@Table(name = "SCM_RESULT")
+public class ScmResult
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field success
+     */
+    @Basic
+    @Column(name = "FLG_SUCCESS")
+    private boolean success = false;
+
+    /**
+     * Field commandLine
+     */
+    @Basic
+    @Column(name = "COMMAND_LINE")
+    private String commandLine;
+
+    /**
+     * Field providerMessage
+     */
+    @Basic
+    @Column(name = "PROVIDER_MESSAGE")
+    private String providerMessage;
+
+    /**
+     * Field commandOutput
+     */
+    @Basic
+    @Column(name = "COMMAND_OUTPUT")
+    private String commandOutput;
+
+    /**
+     * Field exception XXX: Renamed from 'EXCEPTION' to 'EXCEPTION_MSG'
+     */
+    @Basic
+    @Column(name = "EXCEPTION_MSG")
+    private String exception;
+
+    /**
+     * Field changes
+     */
+    @OneToMany
+    private List<ChangeSet> changes;
+
+    /**
+     * @return the success
+     */
+    public boolean isSuccess()
+    {
+        return success;
+    }
+
+    /**
+     * @param success the success to set
+     */
+    public void setSuccess( boolean success )
+    {
+        this.success = success;
+    }
+
+    /**
+     * @return the commandLine
+     */
+    public String getCommandLine()
+    {
+        return commandLine;
+    }
+
+    /**
+     * @param commandLine the commandLine to set
+     */
+    public void setCommandLine( String commandLine )
+    {
+        this.commandLine = commandLine;
+    }
+
+    /**
+     * @return the providerMessage
+     */
+    public String getProviderMessage()
+    {
+        return providerMessage;
+    }
+
+    /**
+     * @param providerMessage the providerMessage to set
+     */
+    public void setProviderMessage( String providerMessage )
+    {
+        this.providerMessage = providerMessage;
+    }
+
+    /**
+     * @return the commandOutput
+     */
+    public String getCommandOutput()
+    {
+        return commandOutput;
+    }
+
+    /**
+     * @param commandOutput the commandOutput to set
+     */
+    public void setCommandOutput( String commandOutput )
+    {
+        this.commandOutput = commandOutput;
+    }
+
+    /**
+     * @return the exception
+     */
+    public String getException()
+    {
+        return exception;
+    }
+
+    /**
+     * @param exception the exception to set
+     */
+    public void setException( String exception )
+    {
+        this.exception = exception;
+    }
+
+    /**
+     * @return the changes
+     */
+    public List<ChangeSet> getChanges()
+    {
+        return changes;
+    }
+
+    /**
+     * @param changes the changes to set
+     */
+    public void setChanges( List<ChangeSet> changes )
+    {
+        this.changes = changes;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/scm/SuiteResult.java b/src/main/java/org/apache/continuum/model/scm/SuiteResult.java
new file mode 100644
index 0000000..2baf4b1
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/scm/SuiteResult.java
@@ -0,0 +1,136 @@
+package org.apache.continuum.model.scm;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import java.util.List;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@Entity
+@Table(name = "SUITE_RESULT")
+public class SuiteResult
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field name
+     */
+    @Basic
+    @Column(name = "NAME")
+    private String name;
+
+    /**
+     * Field testCount
+     */
+    @Basic
+    @Column(name = "TEST_COUNT")
+    private int testCount = 0;
+
+    /**
+     * Field failureCount
+     */
+    @Basic
+    @Column(name = "FAILURE_COUNT")
+    private int failureCount = 0;
+
+    /**
+     * Field totalTime
+     */
+    @Basic
+    @Column(name = "TOTAL_TIME")
+    private long totalTime = 0;
+
+    /**
+     * Field failures
+     */
+    @OneToMany
+    private List<TestCaseFailure> failures;
+
+    /**
+     * @return the name
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * @param name the name to set
+     */
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+    /**
+     * @return the testCount
+     */
+    public int getTestCount()
+    {
+        return testCount;
+    }
+
+    /**
+     * @param testCount the testCount to set
+     */
+    public void setTestCount( int testCount )
+    {
+        this.testCount = testCount;
+    }
+
+    /**
+     * @return the failureCount
+     */
+    public int getFailureCount()
+    {
+        return failureCount;
+    }
+
+    /**
+     * @param failureCount the failureCount to set
+     */
+    public void setFailureCount( int failureCount )
+    {
+        this.failureCount = failureCount;
+    }
+
+    /**
+     * @return the totalTime
+     */
+    public long getTotalTime()
+    {
+        return totalTime;
+    }
+
+    /**
+     * @param totalTime the totalTime to set
+     */
+    public void setTotalTime( long totalTime )
+    {
+        this.totalTime = totalTime;
+    }
+
+    /**
+     * @return the failures
+     */
+    public List<TestCaseFailure> getFailures()
+    {
+        return failures;
+    }
+
+    /**
+     * @param failures the failures to set
+     */
+    public void setFailures( List<TestCaseFailure> failures )
+    {
+        this.failures = failures;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/scm/TestCaseFailure.java b/src/main/java/org/apache/continuum/model/scm/TestCaseFailure.java
new file mode 100644
index 0000000..50e418f
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/scm/TestCaseFailure.java
@@ -0,0 +1,66 @@
+package org.apache.continuum.model.scm;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@Entity
+@Table(name = "TEST_CASE_FAILURE")
+public class TestCaseFailure
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field name
+     */
+    @Basic
+    @Column(name = "NAME")
+    private String name;
+
+    /**
+     * Field exception
+     */
+    @Basic
+    @Column(name = "EXCEPTION")
+    private String exception;
+
+    /**
+     * @return the name
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * @param name the name to set
+     */
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+    /**
+     * @return the exception
+     */
+    public String getException()
+    {
+        return exception;
+    }
+
+    /**
+     * @param exception the exception to set
+     */
+    public void setException( String exception )
+    {
+        this.exception = exception;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/scm/TestResult.java b/src/main/java/org/apache/continuum/model/scm/TestResult.java
new file mode 100644
index 0000000..7af4c2a
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/scm/TestResult.java
@@ -0,0 +1,113 @@
+package org.apache.continuum.model.scm;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import java.util.List;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@Entity
+@Table(name = "TEST_RESULT")
+public class TestResult
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field testCount
+     */
+    @Basic
+    @Column(name = "TEST_COUNT", nullable = false)
+    private int testCount = 0;
+
+    /**
+     * Field failureCount
+     */
+    @Basic
+    @Column(name = "FAILURE_COUNT", nullable = false)
+    private int failureCount = 0;
+
+    /**
+     * Field totalTime
+     */
+    @Basic
+    @Column(name = "TOTAL_TIME", nullable = false)
+    private long totalTime = 0;
+
+    /**
+     * Field suiteResults
+     */
+    @OneToMany
+    private List<SuiteResult> suiteResults;
+
+    /**
+     * @return the testCount
+     */
+    public int getTestCount()
+    {
+        return testCount;
+    }
+
+    /**
+     * @param testCount the testCount to set
+     */
+    public void setTestCount( int testCount )
+    {
+        this.testCount = testCount;
+    }
+
+    /**
+     * @return the failureCount
+     */
+    public int getFailureCount()
+    {
+        return failureCount;
+    }
+
+    /**
+     * @param failureCount the failureCount to set
+     */
+    public void setFailureCount( int failureCount )
+    {
+        this.failureCount = failureCount;
+    }
+
+    /**
+     * @return the totalTime
+     */
+    public long getTotalTime()
+    {
+        return totalTime;
+    }
+
+    /**
+     * @param totalTime the totalTime to set
+     */
+    public void setTotalTime( long totalTime )
+    {
+        this.totalTime = totalTime;
+    }
+
+    /**
+     * @return the suiteResults
+     */
+    public List<SuiteResult> getSuiteResults()
+    {
+        return suiteResults;
+    }
+
+    /**
+     * @param suiteResults the suiteResults to set
+     */
+    public void setSuiteResults( List<SuiteResult> suiteResults )
+    {
+        this.suiteResults = suiteResults;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/system/ContinuumDatabase.java b/src/main/java/org/apache/continuum/model/system/ContinuumDatabase.java
new file mode 100644
index 0000000..9f224b8
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/system/ContinuumDatabase.java
@@ -0,0 +1,290 @@
+package org.apache.continuum.model.system;
+
+import java.io.Serializable;
+
+import org.apache.continuum.model.project.ProjectGroup;
+import org.apache.continuum.model.project.Schedule;
+
+/**
+ * This is not an entity.
+ * 
+ * @version $Revision$ $Date$
+ */
+public class ContinuumDatabase implements Serializable
+{
+
+    /**
+     * Field projectGroups
+     */
+    private java.util.List projectGroups;
+
+    /**
+     * Field systemConfiguration
+     */
+    private SystemConfiguration systemConfiguration;
+
+    /**
+     * Field installations
+     */
+    private java.util.List installations;
+
+    /**
+     * Field schedules
+     */
+    private java.util.List schedules;
+
+    /**
+     * Field profiles
+     */
+    private java.util.List profiles;
+
+    // -----------/
+    // - Methods -/
+    // -----------/
+
+    /**
+     * Method addInstallation
+     * 
+     * @param installation
+     */
+    public void addInstallation( Installation installation )
+    {
+        if ( !( installation instanceof Installation ) )
+        {
+            throw new ClassCastException(
+                                          "ContinuumDatabase.addInstallations(installation) parameter must be instanceof "
+                                                          + Installation.class.getName() );
+        }
+        getInstallations().add( installation );
+    } // -- void addInstallation(Installation)
+
+    /**
+     * Method addProfile
+     * 
+     * @param profile
+     */
+    public void addProfile( Profile profile )
+    {
+        if ( !( profile instanceof Profile ) )
+        {
+            throw new ClassCastException( "ContinuumDatabase.addProfiles(profile) parameter must be instanceof "
+                            + Profile.class.getName() );
+        }
+        getProfiles().add( profile );
+    } // -- void addProfile(Profile)
+
+    /**
+     * Method addProjectGroup
+     * 
+     * @param projectGroup
+     */
+    public void addProjectGroup( ProjectGroup projectGroup )
+    {
+        if ( !( projectGroup instanceof ProjectGroup ) )
+        {
+            throw new ClassCastException(
+                                          "ContinuumDatabase.addProjectGroups(projectGroup) parameter must be instanceof "
+                                                          + ProjectGroup.class.getName() );
+        }
+        getProjectGroups().add( projectGroup );
+    } // -- void addProjectGroup(ProjectGroup)
+
+    /**
+     * Method addSchedule
+     * 
+     * @param schedule
+     */
+    public void addSchedule( Schedule schedule )
+    {
+        if ( !( schedule instanceof Schedule ) )
+        {
+            throw new ClassCastException( "ContinuumDatabase.addSchedules(schedule) parameter must be instanceof "
+                            + Schedule.class.getName() );
+        }
+        getSchedules().add( schedule );
+    } // -- void addSchedule(Schedule)
+
+    /**
+     * Method getInstallations
+     */
+    public java.util.List getInstallations()
+    {
+        if ( this.installations == null )
+        {
+            this.installations = new java.util.ArrayList();
+        }
+
+        return this.installations;
+    } // -- java.util.List getInstallations()
+
+    /**
+     * Method getProfiles
+     */
+    public java.util.List getProfiles()
+    {
+        if ( this.profiles == null )
+        {
+            this.profiles = new java.util.ArrayList();
+        }
+
+        return this.profiles;
+    } // -- java.util.List getProfiles()
+
+    /**
+     * Method getProjectGroups
+     */
+    public java.util.List getProjectGroups()
+    {
+        if ( this.projectGroups == null )
+        {
+            this.projectGroups = new java.util.ArrayList();
+        }
+
+        return this.projectGroups;
+    } // -- java.util.List getProjectGroups()
+
+    /**
+     * Method getSchedules
+     */
+    public java.util.List getSchedules()
+    {
+        if ( this.schedules == null )
+        {
+            this.schedules = new java.util.ArrayList();
+        }
+
+        return this.schedules;
+    } // -- java.util.List getSchedules()
+
+    /**
+     * Get null
+     */
+    public SystemConfiguration getSystemConfiguration()
+    {
+        return this.systemConfiguration;
+    } // -- SystemConfiguration getSystemConfiguration()
+
+    /**
+     * Method removeInstallation
+     * 
+     * @param installation
+     */
+    public void removeInstallation( Installation installation )
+    {
+        if ( !( installation instanceof Installation ) )
+        {
+            throw new ClassCastException(
+                                          "ContinuumDatabase.removeInstallations(installation) parameter must be instanceof "
+                                                          + Installation.class.getName() );
+        }
+        getInstallations().remove( installation );
+    } // -- void removeInstallation(Installation)
+
+    /**
+     * Method removeProfile
+     * 
+     * @param profile
+     */
+    public void removeProfile( Profile profile )
+    {
+        if ( !( profile instanceof Profile ) )
+        {
+            throw new ClassCastException( "ContinuumDatabase.removeProfiles(profile) parameter must be instanceof "
+                            + Profile.class.getName() );
+        }
+        getProfiles().remove( profile );
+    } // -- void removeProfile(Profile)
+
+    /**
+     * Method removeProjectGroup
+     * 
+     * @param projectGroup
+     */
+    public void removeProjectGroup( ProjectGroup projectGroup )
+    {
+        if ( !( projectGroup instanceof ProjectGroup ) )
+        {
+            throw new ClassCastException(
+                                          "ContinuumDatabase.removeProjectGroups(projectGroup) parameter must be instanceof "
+                                                          + ProjectGroup.class.getName() );
+        }
+        getProjectGroups().remove( projectGroup );
+    } // -- void removeProjectGroup(ProjectGroup)
+
+    /**
+     * Method removeSchedule
+     * 
+     * @param schedule
+     */
+    public void removeSchedule( Schedule schedule )
+    {
+        if ( !( schedule instanceof Schedule ) )
+        {
+            throw new ClassCastException( "ContinuumDatabase.removeSchedules(schedule) parameter must be instanceof "
+                            + Schedule.class.getName() );
+        }
+        getSchedules().remove( schedule );
+    } // -- void removeSchedule(Schedule)
+
+    /**
+     * Set null
+     * 
+     * @param installations
+     */
+    public void setInstallations( java.util.List installations )
+    {
+        this.installations = installations;
+    } // -- void setInstallations(java.util.List)
+
+    /**
+     * Set null
+     * 
+     * @param profiles
+     */
+    public void setProfiles( java.util.List profiles )
+    {
+        this.profiles = profiles;
+    } // -- void setProfiles(java.util.List)
+
+    /**
+     * Set null
+     * 
+     * @param projectGroups
+     */
+    public void setProjectGroups( java.util.List projectGroups )
+    {
+        this.projectGroups = projectGroups;
+    } // -- void setProjectGroups(java.util.List)
+
+    /**
+     * Set null
+     * 
+     * @param schedules
+     */
+    public void setSchedules( java.util.List schedules )
+    {
+        this.schedules = schedules;
+    } // -- void setSchedules(java.util.List)
+
+    /**
+     * Set null
+     * 
+     * @param systemConfiguration
+     */
+    public void setSystemConfiguration( SystemConfiguration systemConfiguration )
+    {
+        this.systemConfiguration = systemConfiguration;
+    } // -- void setSystemConfiguration(SystemConfiguration)
+
+    private String modelEncoding = "UTF-8";
+
+    public void setModelEncoding( String modelEncoding )
+    {
+        this.modelEncoding = modelEncoding;
+    }
+
+    public String getModelEncoding()
+    {
+        return modelEncoding;
+    }
+}
diff --git a/src/main/java/org/apache/continuum/model/system/Installation.java b/src/main/java/org/apache/continuum/model/system/Installation.java
new file mode 100644
index 0000000..14a995b
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/system/Installation.java
@@ -0,0 +1,114 @@
+package org.apache.continuum.model.system;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@Entity
+@Table(name = "INSTALLATION")
+public class Installation
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field type
+     * <p/>
+     * TODO: Enum?
+     */
+    @Basic
+    @Column(name = "INSTALLATION_TYPE")
+    private String type;
+
+    /**
+     * Field varValue
+     */
+    @Basic
+    @Column(name = "VAR_VALUE")
+    private String varValue;
+
+    /**
+     * Field varName
+     */
+    @Basic
+    @Column(name = "VAR_NAME")
+    private String varName;
+
+    /**
+     * Field name
+     */
+    @Basic
+    @Column(name = "NAME")
+    private String name;
+
+    /**
+     * @return the type
+     */
+    public String getType()
+    {
+        return type;
+    }
+
+    /**
+     * @param type the type to set
+     */
+    public void setType( String type )
+    {
+        this.type = type;
+    }
+
+    /**
+     * @return the varValue
+     */
+    public String getVarValue()
+    {
+        return varValue;
+    }
+
+    /**
+     * @param varValue the varValue to set
+     */
+    public void setVarValue( String varValue )
+    {
+        this.varValue = varValue;
+    }
+
+    /**
+     * @return the varName
+     */
+    public String getVarName()
+    {
+        return varName;
+    }
+
+    /**
+     * @param varName the varName to set
+     */
+    public void setVarName( String varName )
+    {
+        this.varName = varName;
+    }
+
+    /**
+     * @return the name
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * @param name the name to set
+     */
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/system/NotificationAddress.java b/src/main/java/org/apache/continuum/model/system/NotificationAddress.java
new file mode 100644
index 0000000..84067b6
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/system/NotificationAddress.java
@@ -0,0 +1,94 @@
+package org.apache.continuum.model.system;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+
+/**
+ * Configures one method for notifying users/developers when a build breaks.
+ *
+ * @version $Id$
+ */
+@Entity
+@Table(name = "NOTIFICATION_ADDRESS")
+public class NotificationAddress
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field type
+     * <p/>
+     * TODO: Enum?
+     */
+    @Basic
+    @Column(name = "ADDRESS_TYPE")
+    private String type = "mail";
+
+    /**
+     * Field address
+     */
+    @Basic
+    @Column(name = "ADDRESS")
+    private String address;
+
+    /**
+     * Field configuration
+     * <p/>
+     * TODO: Map!
+     */
+    @Transient
+    private java.util.Map configuration;
+
+    /**
+     * @return the type
+     */
+    public String getType()
+    {
+        return type;
+    }
+
+    /**
+     * @param type the type to set
+     */
+    public void setType( String type )
+    {
+        this.type = type;
+    }
+
+    /**
+     * @return the address
+     */
+    public String getAddress()
+    {
+        return address;
+    }
+
+    /**
+     * @param address the address to set
+     */
+    public void setAddress( String address )
+    {
+        this.address = address;
+    }
+
+    /**
+     * @return the configuration
+     */
+    public java.util.Map getConfiguration()
+    {
+        return configuration;
+    }
+
+    /**
+     * @param configuration the configuration to set
+     */
+    public void setConfiguration( java.util.Map configuration )
+    {
+        this.configuration = configuration;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/system/Profile.java b/src/main/java/org/apache/continuum/model/system/Profile.java
new file mode 100644
index 0000000..b54c75b
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/system/Profile.java
@@ -0,0 +1,206 @@
+package org.apache.continuum.model.system;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import java.util.List;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@Entity
+@Table(name = "PROFILE")
+public class Profile
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field active
+     */
+    @Basic
+    @Column(name = "FLG_ACTIVE", nullable = false)
+    private boolean active = false;
+
+    /**
+     * Field name
+     */
+    @Basic
+    @Column(name = "NAME", nullable = false)
+    private String name;
+
+    /**
+     * Field description
+     */
+    @Basic
+    @Column(name = "DESCRIPTION")
+    private String description;
+
+    /**
+     * Field scmMode
+     * <p/>
+     * TODO: Enum?
+     */
+    @Basic
+    @Column(name = "SCM_MODE", nullable = false)
+    private int scmMode = 0;
+
+    /**
+     * Field buildWithoutChanges
+     */
+    @Basic
+    @Column(name = "FLG_BUILD_WITHOUT_CHANGES", nullable = false)
+    private boolean buildWithoutChanges = false;
+
+    /**
+     * Field jdk
+     */
+    @OneToOne
+    private Installation jdk;
+
+    /**
+     * Field builder
+     */
+    @OneToOne
+    private Installation builder;
+
+    /**
+     * Field environmentVariables
+     */
+    @OneToMany
+    private List<Installation> environmentVariables;
+
+    /**
+     * @return the active
+     */
+    public boolean isActive()
+    {
+        return active;
+    }
+
+    /**
+     * @param active the active to set
+     */
+    public void setActive( boolean active )
+    {
+        this.active = active;
+    }
+
+    /**
+     * @return the name
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * @param name the name to set
+     */
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+    /**
+     * @return the description
+     */
+    public String getDescription()
+    {
+        return description;
+    }
+
+    /**
+     * @param description the description to set
+     */
+    public void setDescription( String description )
+    {
+        this.description = description;
+    }
+
+    /**
+     * @return the scmMode
+     */
+    public int getScmMode()
+    {
+        return scmMode;
+    }
+
+    /**
+     * @param scmMode the scmMode to set
+     */
+    public void setScmMode( int scmMode )
+    {
+        this.scmMode = scmMode;
+    }
+
+    /**
+     * @return the buildWithoutChanges
+     */
+    public boolean isBuildWithoutChanges()
+    {
+        return buildWithoutChanges;
+    }
+
+    /**
+     * @param buildWithoutChanges the buildWithoutChanges to set
+     */
+    public void setBuildWithoutChanges( boolean buildWithoutChanges )
+    {
+        this.buildWithoutChanges = buildWithoutChanges;
+    }
+
+    /**
+     * @return the jdk
+     */
+    public Installation getJdk()
+    {
+        return jdk;
+    }
+
+    /**
+     * @param jdk the jdk to set
+     */
+    public void setJdk( Installation jdk )
+    {
+        this.jdk = jdk;
+    }
+
+    /**
+     * @return the builder
+     */
+    public Installation getBuilder()
+    {
+        return builder;
+    }
+
+    /**
+     * @param builder the builder to set
+     */
+    public void setBuilder( Installation builder )
+    {
+        this.builder = builder;
+    }
+
+    /**
+     * @return the environmentVariables
+     */
+    public List<Installation> getEnvironmentVariables()
+    {
+        return environmentVariables;
+    }
+
+    /**
+     * @param environmentVariables the environmentVariables to set
+     */
+    public void setEnvironmentVariables( List<Installation> environmentVariables )
+    {
+        this.environmentVariables = environmentVariables;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/model/system/SystemConfiguration.java b/src/main/java/org/apache/continuum/model/system/SystemConfiguration.java
new file mode 100644
index 0000000..d1015ef
--- /dev/null
+++ b/src/main/java/org/apache/continuum/model/system/SystemConfiguration.java
@@ -0,0 +1,204 @@
+package org.apache.continuum.model.system;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@Entity
+@Table(name = "SYSTEM_CONFIGURATION")
+public class SystemConfiguration
+    extends CommonUpdatableEntity
+{
+
+    /**
+     * Field guestAccountEnabled
+     */
+    @Basic
+    @Column(name = "FLG_GUEST_ACCOUNT_ENABLED", nullable = false)
+    private boolean guestAccountEnabled = true;
+
+    /**
+     * Field defaultScheduleDescription
+     */
+    @Basic
+    @Column(name = "DEFAULT_SCHEDULE_DESC", nullable = false)
+    private String defaultScheduleDescription = "Run hourly";
+
+    /**
+     * Field defaultScheduleCronExpression
+     */
+    @Basic
+    @Column(name = "DEFAULT_SCHEDULE_CRON_EXP", nullable = false)
+    private String defaultScheduleCronExpression = "0 0 * * * ?";
+
+    /**
+     * Field workingDirectory
+     */
+    @Basic
+    @Column(name = "WORKING_DIRECTORY", nullable = false)
+    private String workingDirectory = "working-directory";
+
+    /**
+     * Field buildOutputDirectory
+     */
+    @Basic
+    @Column(name = "BUILD_OUTPUT_DIRECTORY", nullable = false)
+    private String buildOutputDirectory = "build-output-directory";
+
+    /**
+     * Field deploymentRepositoryDirectory
+     */
+    @Basic
+    @Column(name = "DEPLOYMENT_REPOSITORY_DIRECTORY")
+    private String deploymentRepositoryDirectory;
+
+    /**
+     * Field baseUrl
+     */
+    @Basic
+    @Column(name = "BASE_URL")
+    private String baseUrl;
+
+    /**
+     * Field initialized
+     */
+    @Basic
+    @Column(name = "FLG_INITIALIZED", nullable = false)
+    private boolean initialized = false;
+
+    /**
+     * @return the guestAccountEnabled
+     */
+    public boolean isGuestAccountEnabled()
+    {
+        return guestAccountEnabled;
+    }
+
+    /**
+     * @param guestAccountEnabled the guestAccountEnabled to set
+     */
+    public void setGuestAccountEnabled( boolean guestAccountEnabled )
+    {
+        this.guestAccountEnabled = guestAccountEnabled;
+    }
+
+    /**
+     * @return the defaultScheduleDescription
+     */
+    public String getDefaultScheduleDescription()
+    {
+        return defaultScheduleDescription;
+    }
+
+    /**
+     * @param defaultScheduleDescription the defaultScheduleDescription to set
+     */
+    public void setDefaultScheduleDescription( String defaultScheduleDescription )
+    {
+        this.defaultScheduleDescription = defaultScheduleDescription;
+    }
+
+    /**
+     * @return the defaultScheduleCronExpression
+     */
+    public String getDefaultScheduleCronExpression()
+    {
+        return defaultScheduleCronExpression;
+    }
+
+    /**
+     * @param defaultScheduleCronExpression the defaultScheduleCronExpression to set
+     */
+    public void setDefaultScheduleCronExpression( String defaultScheduleCronExpression )
+    {
+        this.defaultScheduleCronExpression = defaultScheduleCronExpression;
+    }
+
+    /**
+     * @return the workingDirectory
+     */
+    public String getWorkingDirectory()
+    {
+        return workingDirectory;
+    }
+
+    /**
+     * @param workingDirectory the workingDirectory to set
+     */
+    public void setWorkingDirectory( String workingDirectory )
+    {
+        this.workingDirectory = workingDirectory;
+    }
+
+    /**
+     * @return the buildOutputDirectory
+     */
+    public String getBuildOutputDirectory()
+    {
+        return buildOutputDirectory;
+    }
+
+    /**
+     * @param buildOutputDirectory the buildOutputDirectory to set
+     */
+    public void setBuildOutputDirectory( String buildOutputDirectory )
+    {
+        this.buildOutputDirectory = buildOutputDirectory;
+    }
+
+    /**
+     * @return the deploymentRepositoryDirectory
+     */
+    public String getDeploymentRepositoryDirectory()
+    {
+        return deploymentRepositoryDirectory;
+    }
+
+    /**
+     * @param deploymentRepositoryDirectory the deploymentRepositoryDirectory to set
+     */
+    public void setDeploymentRepositoryDirectory( String deploymentRepositoryDirectory )
+    {
+        this.deploymentRepositoryDirectory = deploymentRepositoryDirectory;
+    }
+
+    /**
+     * @return the baseUrl
+     */
+    public String getBaseUrl()
+    {
+        return baseUrl;
+    }
+
+    /**
+     * @param baseUrl the baseUrl to set
+     */
+    public void setBaseUrl( String baseUrl )
+    {
+        this.baseUrl = baseUrl;
+    }
+
+    /**
+     * @return the initialized
+     */
+    public boolean isInitialized()
+    {
+        return initialized;
+    }
+
+    /**
+     * @param initialized the initialized to set
+     */
+    public void setInitialized( boolean initialized )
+    {
+        this.initialized = initialized;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/service/api/ProjectGroupService.java b/src/main/java/org/apache/continuum/service/api/ProjectGroupService.java
new file mode 100644
index 0000000..9231aa9
--- /dev/null
+++ b/src/main/java/org/apache/continuum/service/api/ProjectGroupService.java
@@ -0,0 +1,35 @@
+package org.apache.continuum.service.api;
+
+import org.apache.continuum.model.project.Project;
+import org.apache.continuum.model.project.ProjectGroup;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
+ * @version $Id$
+ */
+public interface ProjectGroupService
+{
+    ProjectGroup saveOrUpdate( ProjectGroup projectGroup );
+
+    ProjectGroup getProjectGroup( long pgId );
+
+    ProjectGroup getProjectGroup( String groupId );
+
+    ProjectGroup addProjectGroup( ProjectGroup pg );
+
+    void removeProjectGroup( ProjectGroup pg );
+
+    void addProject( ProjectGroup pg, Project p );
+
+    void removeProject( ProjectGroup pg, Project p );
+
+    List<Project> getProjects( ProjectGroup pg );
+
+    //BuildResult buildProjectGroup( ProjectGroup pg );
+
+    //BuildResult buildProjectGroup( ProjectGroup, BuildDefinition bd );
+
+    //BuildResult buildProjectGroup( ProjectGroup, BuildDefinition bd, boolean force );
+}
diff --git a/src/main/java/org/apache/continuum/service/api/ProjectService.java b/src/main/java/org/apache/continuum/service/api/ProjectService.java
new file mode 100644
index 0000000..9c179fa
--- /dev/null
+++ b/src/main/java/org/apache/continuum/service/api/ProjectService.java
@@ -0,0 +1,33 @@
+package org.apache.continuum.service.api;
+
+import org.apache.continuum.model.project.Project;
+import org.apache.continuum.model.project.ProjectNotifier;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
+ * @version $Id$
+ */
+public interface ProjectService
+{
+    Project saveOrUpdate( Project project );
+
+    Project getProject( long projecId );
+
+    Project getProject( String groupId, String artifactId, String version );
+
+    List<ProjectNotifier> getNotifiers( Project p );
+
+    void addNotifier( Project p, ProjectNotifier notifier );
+
+    void removeNotifier( Project p, ProjectNotifier notifier );
+
+    //BuildDefinition getDefaultBuildDefinition( Project p );
+
+    //BuildResult buildProject( Project p );
+
+    //BuildResult buildProject( Project p, BuildDefinition bd );
+
+    //BuildResult buildProject( Project p, BuildDefinition bd, boolean force );
+}
diff --git a/src/main/java/org/apache/continuum/service/impl/ProjectGroupServiceImpl.java b/src/main/java/org/apache/continuum/service/impl/ProjectGroupServiceImpl.java
new file mode 100644
index 0000000..d419336
--- /dev/null
+++ b/src/main/java/org/apache/continuum/service/impl/ProjectGroupServiceImpl.java
@@ -0,0 +1,105 @@
+package org.apache.continuum.service.impl;
+
+import org.apache.continuum.dao.api.GenericDao;
+import org.apache.continuum.model.project.Project;
+import org.apache.continuum.model.project.ProjectGroup;
+import org.apache.continuum.service.api.ProjectGroupService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
+ * @version $Id$
+ */
+@Service
+public class ProjectGroupServiceImpl
+    implements ProjectGroupService
+{
+    GenericDao<ProjectGroup> projectGroupDao;
+
+    GenericDao<Project> projectDao;
+
+    public ProjectGroup saveOrUpdate( ProjectGroup projectGroup )
+    {
+        return projectGroupDao.saveOrUpdate( projectGroup );
+    }
+
+    public ProjectGroup getProjectGroup( long pgId )
+    {
+        return projectGroupDao.findById( pgId );
+    }
+
+    public ProjectGroup getProjectGroup( String groupId )
+    {
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put( "groupId", groupId );
+        return projectGroupDao.findUniqByNamedQueryAndNamedParams( ProjectGroup.class, "ProjectGroup.findProjectGroup",
+                                                                   params );
+    }
+
+    public ProjectGroup addProjectGroup( ProjectGroup pg )
+    {
+        return projectGroupDao.saveOrUpdate( pg );
+    }
+
+    @Transactional
+    public void removeProjectGroup( ProjectGroup pg )
+    {
+        if ( pg == null )
+        {
+            return;
+        }
+
+        for ( Project p : pg.getProjects() )
+        {
+            projectDao.delete( p );
+        }
+
+        projectGroupDao.delete( pg );
+    }
+
+    public void addProject( ProjectGroup pg, Project p )
+    {
+        pg.addProject( p );
+        projectGroupDao.saveOrUpdate( pg );
+    }
+
+    @Transactional
+    public void removeProject( ProjectGroup pg, Project p )
+    {
+        pg.removeProject( p );
+        projectDao.delete( p );
+        projectGroupDao.saveOrUpdate( pg );
+    }
+
+    public List<Project> getProjects( ProjectGroup pg )
+    {
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put( "projectGroup", pg );
+        return projectDao.findByNamedQueryAndNamedParams( Project.class, "ProjectGroup.findProjects", params );
+    }
+
+    public GenericDao<ProjectGroup> getProjectGroupDao()
+    {
+        return projectGroupDao;
+    }
+
+    public void setProjectGroupDao( GenericDao<ProjectGroup> projectGroupDao )
+    {
+        this.projectGroupDao = projectGroupDao;
+    }
+
+    public GenericDao<Project> getProjectDao()
+    {
+        return projectDao;
+    }
+
+    public void setProjectDao( GenericDao<Project> projectDao )
+    {
+        this.projectDao = projectDao;
+    }
+}
diff --git a/src/main/java/org/apache/continuum/service/impl/ProjectServiceImpl.java b/src/main/java/org/apache/continuum/service/impl/ProjectServiceImpl.java
new file mode 100644
index 0000000..a0b0c07
--- /dev/null
+++ b/src/main/java/org/apache/continuum/service/impl/ProjectServiceImpl.java
@@ -0,0 +1,85 @@
+package org.apache.continuum.service.impl;
+
+import org.apache.continuum.dao.api.GenericDao;
+import org.apache.continuum.model.project.Project;
+import org.apache.continuum.model.project.ProjectNotifier;
+import org.apache.continuum.service.api.ProjectService;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
+ * @version $Id$
+ */
+public class ProjectServiceImpl
+    implements ProjectService
+{
+    GenericDao<Project> projectDao;
+
+    GenericDao<ProjectNotifier> notifierDao;
+
+    public Project saveOrUpdate( Project project )
+    {
+        return projectDao.saveOrUpdate( project );
+    }
+
+    public Project getProject( long projecId )
+    {
+        return projectDao.findById( projecId );
+    }
+
+    public Project getProject( String groupId, String artifactId, String version )
+    {
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put( "groupId", groupId );
+        params.put( "artifactId", artifactId );
+        params.put( "version", version );
+        return projectDao.findUniqByNamedQueryAndNamedParams( Project.class, "Project.find", params );
+    }
+
+    public List<ProjectNotifier> getNotifiers( Project p )
+    {
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put( "projectId", p.getId() );
+        return notifierDao.findByNamedQueryAndNamedParams( ProjectNotifier.class, "Notifier.findAllFromProject",
+                                                           params );
+    }
+
+    @Transactional
+    public void addNotifier( Project p, ProjectNotifier notifier )
+    {
+        p.addNotifier( notifier );
+        projectDao.saveOrUpdate( p );
+    }
+
+    @Transactional
+    public void removeNotifier( Project p, ProjectNotifier notifier )
+    {
+        p.removeNotifier( notifier );
+        notifierDao.delete( notifier );
+        projectDao.saveOrUpdate( p );
+    }
+
+    public GenericDao<Project> getProjectDao()
+    {
+        return projectDao;
+    }
+
+    public void setProjectDao( GenericDao<Project> projectDao )
+    {
+        this.projectDao = projectDao;
+    }
+
+    public GenericDao<ProjectNotifier> getNotifierDao()
+    {
+        return notifierDao;
+    }
+
+    public void setNotifierDao( GenericDao<ProjectNotifier> notifierDao )
+    {
+        this.notifierDao = notifierDao;
+    }
+}
diff --git a/src/main/java/org/apache/continuum/store/api/DeprecatedSystemStore.java b/src/main/java/org/apache/continuum/store/api/DeprecatedSystemStore.java
new file mode 100644
index 0000000..d1df57e
--- /dev/null
+++ b/src/main/java/org/apache/continuum/store/api/DeprecatedSystemStore.java
@@ -0,0 +1,207 @@
+package org.apache.continuum.store.api;
+
+import java.util.List;
+
+import org.apache.continuum.model.project.Schedule;
+import org.apache.continuum.model.system.Installation;
+import org.apache.continuum.model.system.Profile;
+import org.apache.continuum.model.system.SystemConfiguration;
+
+/**
+ * Defines the contract consisting of operations that can be performed on following entities:
+ * <ul>
+ * <li>{@link Schedule},</li>
+ * <li>{@link Profile},</li>
+ * <li>{@link Installation},</li>
+ * <li>{@link SystemConfiguration}</li>
+ * </ul>
+ * 
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ * @since 1.1
+ */
+public interface DeprecatedSystemStore
+{
+    public static final String ROLE = DeprecatedSystemStore.class.getName();
+
+    /**
+     * Removes the passed {@link Installation} instance from the underlying store.
+     * 
+     * @param project
+     *            {@link Installation} instance to remove.
+     * @throws StoreException
+     *             if there was an error removing the entity.
+     */
+    public void deleteInstallation( Installation installation ) throws StoreException;
+
+    /**
+     * Removes the passed {@link Profile} instance from the underlying store.
+     * 
+     * @param project
+     *            {@link Profile} instance to remove.
+     * @throws StoreException
+     *             if there was an error removing the entity.
+     */
+    public void deleteProfile( Profile profile ) throws StoreException;
+
+    /**
+     * Removes the passed {@link Schedule} instance from the underlying store.
+     * 
+     * @param project
+     *            {@link Schedule} instance to remove.
+     * @throws StoreException
+     *             if there was an error removing the entity.
+     */
+    public void deleteSchedule( Schedule schedule ) throws StoreException;
+
+    /**
+     * Removes the passed {@link SystemConfiguration} instance from the underlying store.
+     * 
+     * @param project
+     *            {@link SystemConfiguration} instance to remove.
+     * @throws StoreException
+     *             if there was an error removing the entity.
+     */
+    public void deleteSystemConfiguration( SystemConfiguration systemConfiguration ) throws StoreException;
+
+    /**
+     * Looks up the underlying store and returns a {@link Installation} instance that matches the specified id.
+     * 
+     * @param id
+     *            {@link Installation} id to match.
+     * @return matching {@link Installation} instance.
+     * @throws EntityNotFoundException
+     *             if the instance could not be looked up.
+     * @throws StoreException
+     */
+    public Installation lookupInstallation( long id ) throws EntityNotFoundException, StoreException;
+
+    /**
+     * Looks up the underlying store and returns a {@link Profile} instance that matches the specified id.
+     * 
+     * @param id
+     *            {@link Profile} id to match.
+     * @return matching {@link Profile} instance.
+     * @throws EntityNotFoundException
+     *             if the instance could not be looked up.
+     * @throws StoreException
+     */
+    public Profile lookupProfile( long id ) throws EntityNotFoundException, StoreException;
+
+    /**
+     * Looks up the underlying store and returns a {@link Schedule} instance that matches the specified id.
+     * 
+     * @param id
+     *            {@link Schedule} id to match.
+     * @return matching {@link Schedule} instance.
+     * @throws EntityNotFoundException
+     *             if the instance could not be looked up.
+     * @throws StoreException
+     */
+    public Schedule lookupSchedule( long id ) throws EntityNotFoundException, StoreException;
+
+    /**
+     * Looks up the underlying store and returns a {@link SystemConfiguration} instance that matches the specified id.
+     * 
+     * @param id
+     *            {@link SystemConfiguration} id to match.
+     * @return matching {@link SystemConfiguration} instance.
+     * @throws EntityNotFoundException
+     *             if the instance could not be looked up.
+     * @throws StoreException
+     */
+    public SystemConfiguration lookupSystemConfiguration( long id ) throws EntityNotFoundException, StoreException;
+
+    /**
+     * Persists the passed in {@link Installation} instance to the underlying store.
+     * <p>
+     * If the entity instance already exists in the database it is updated, else a new instance is created and an
+     * store-generated identifier assigned to it.
+     * 
+     * @param project
+     *            {@link Installation} instance to be created/saved.
+     * @return updated {@link Installation} instance.
+     * @throws StoreException
+     *             if there was an error saving the entity.
+     */
+    public Installation saveInstallation( Installation installation ) throws StoreException;
+
+    /**
+     * Persists the passed in {@link Profile} instance to the underlying store.
+     * <p>
+     * If the entity instance already exists in the database it is updated, else a new instance is created and an
+     * store-generated identifier assigned to it.
+     * 
+     * @param project
+     *            {@link Profile} instance to be created/saved.
+     * @return updated {@link Profile} instance.
+     * @throws StoreException
+     *             if there was an error saving the entity.
+     */
+    public Profile saveProfile( Profile profile ) throws StoreException;
+
+    /**
+     * Persists the passed in {@link Schedule} instance to the underlying store.
+     * <p>
+     * If the entity instance already exists in the database it is updated, else a new instance is created and an
+     * store-generated identifier assigned to it.
+     * 
+     * @param project
+     *            {@link Schedule} instance to be created/saved.
+     * @return updated {@link Schedule} instance.
+     * @throws StoreException
+     *             if there was an error saving the entity.
+     */
+    public Schedule saveSchedule( Schedule schedule ) throws StoreException;
+
+    /**
+     * Persists the passed in {@link SystemConfiguration} instance to the underlying store.
+     * <p>
+     * If the entity instance already exists in the database it is updated, else a new instance is created and an
+     * store-generated identifier assigned to it.
+     * 
+     * @param project
+     *            {@link SystemConfiguration} instance to be created/saved.
+     * @return updated {@link SystemConfiguration} instance.
+     * @throws StoreException
+     *             if there was an error saving the entity.
+     */
+    public SystemConfiguration saveSystemConfiguration( SystemConfiguration systemConfiguration ) throws StoreException;
+
+    /**
+     * Obtains and returns a {@link List} of <b>all</b> {@link Schedule} instances for the system, stored in the
+     * underlying store.
+     * 
+     * @return list of all {@link Schedule} instances stored.
+     * @throws StoreException
+     */
+    public List getAllSchedules() throws StoreException;
+
+    /**
+     * Obtains and returns a {@link List} of <b>all</b> {@link Profile} instances for the system, stored in the
+     * underlying store.
+     * 
+     * @return list of all {@link Profile} instances stored.
+     * @throws StoreException
+     */
+    public List getAllProfiles() throws StoreException;
+
+    /**
+     * Obtains and returns a {@link List} of <b>all</b> {@link Installation} instances for the system, stored in the
+     * underlying store.
+     * 
+     * @return list of all {@link Installation} instances stored.
+     * @throws StoreException
+     */
+    public List getAllInstallations() throws StoreException;
+
+    /**
+     * Obtains and returns a {@link List} of <b>all</b> {@link SystemConfiguration} instances for the system, stored in
+     * the underlying store.
+     * 
+     * @return list of all {@link SystemConfiguration} instances stored.
+     * @throws StoreException
+     */
+    public List getAllSystemConfigurations() throws StoreException;
+
+}
diff --git a/src/main/java/org/apache/continuum/store/api/EntityNotFoundException.java b/src/main/java/org/apache/continuum/store/api/EntityNotFoundException.java
new file mode 100644
index 0000000..7eff923
--- /dev/null
+++ b/src/main/java/org/apache/continuum/store/api/EntityNotFoundException.java
@@ -0,0 +1,42 @@
+package org.apache.continuum.store.api;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
+ * @version $Id$
+ */
+public class EntityNotFoundException extends StoreException
+{
+    public EntityNotFoundException()
+    {
+        super();
+    }
+
+    public EntityNotFoundException( String message )
+    {
+        super( message );
+    }
+
+    public EntityNotFoundException( String type, String id )
+    {
+        this( "Could not find object. Type '" + type + "'. Id: '" + id + "'." );
+    }
+}
diff --git a/src/main/java/org/apache/continuum/store/api/Query.java b/src/main/java/org/apache/continuum/store/api/Query.java
new file mode 100644
index 0000000..d4e1eb5
--- /dev/null
+++ b/src/main/java/org/apache/continuum/store/api/Query.java
@@ -0,0 +1,33 @@
+/**
+ * 
+ */
+package org.apache.continuum.store.api;
+
+import java.util.Map;
+
+/**
+ * Wraps up Type Query criteria to be used by store extensions to filter (and obtain) matching type instances.
+ * <p>
+ * Implementations/extensions are expected to override {@link Object#toString()} method and return a <b>JPQL</b>
+ * formatted string. The JPQL string is consumed by the {@link Store} implementation in {@link Store#query(Query)}
+ * operations.
+ * 
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ * @since 1.2
+ */
+public interface Query<Q>
+{
+
+    /**
+     * Returns this instance of {@link Query} as a JPQL String.
+     * 
+     * @param whereClause
+     *            {@link Map} containing the named parameters to be substituted in the JPQL query. This is populated by
+     *            the {@link Query} implementation and subsequently used by the {@link Store} implementation to
+     *            interpolate the parameters before the JPQL query is executed.
+     * 
+     * @return {@link Query} as a JPQL String
+     */
+    public String toString( Map<String, Object> whereClause );
+}
diff --git a/src/main/java/org/apache/continuum/store/api/QueryFactory.java b/src/main/java/org/apache/continuum/store/api/QueryFactory.java
new file mode 100644
index 0000000..bcd8c3c
--- /dev/null
+++ b/src/main/java/org/apache/continuum/store/api/QueryFactory.java
@@ -0,0 +1,32 @@
+/**
+ * 
+ */
+package org.apache.continuum.store.api;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ * @since 1.2
+ */
+public class QueryFactory
+{
+    public static <T, Q extends Query<T>> Q createQuery( Class<Q> klass )
+    {
+        Q qry = null;
+        try
+        {
+            qry = klass.newInstance();
+        }
+        catch ( InstantiationException e )
+        {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        catch ( IllegalAccessException e )
+        {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        return qry;
+    }
+}
diff --git a/src/main/java/org/apache/continuum/store/api/Store.java b/src/main/java/org/apache/continuum/store/api/Store.java
new file mode 100644
index 0000000..c74453d
--- /dev/null
+++ b/src/main/java/org/apache/continuum/store/api/Store.java
@@ -0,0 +1,79 @@
+/**
+ * 
+ */
+package org.apache.continuum.store.api;
+
+import java.util.List;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+
+/**
+ * Interface that Continuum store extensions/implementations are expected to implement to allow operations on the
+ * underlying store.
+ * <ul>
+ * <li>Entity look ups</li>
+ * <li>Entity insert/updates</li>
+ * <li>Entity removal</li>
+ * <li>Querying one or more entity/entities based on specified criteria</li>
+ * </ul>
+ * 
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ * @since 1.2
+ */
+public interface Store<T extends CommonUpdatableEntity, Q extends Query<T>>
+{
+
+    /**
+     * Looks up the underlying store and returns a {@link T} instance that matches the specified id.
+     * 
+     * @param klass
+     *            {@link Class} for type entity to lookup and return an instance of.
+     * @param id
+     *            Entity Type {@link T}'s id to match.
+     * 
+     * @return matching entity type {@link T} instance.
+     * @throws StoreException
+     * @throws EntityNotFoundException
+     *             if the entity specified by the identifier could be located in the system.
+     * @throws EntityNotFoundException
+     *             if the instance could not be looked up.
+     */
+    public T lookup( Class<T> klass, Long id ) throws StoreException, EntityNotFoundException;
+
+    /**
+     * Persists the passed in entity type {@link T} instance to the underlying store.
+     * <p>
+     * If the entity instance already exists in the database it is updated, else a new instance is created and an
+     * store-generated identifier assigned to it.
+     * 
+     * @param entity
+     *            Type {@link T} instance to be created/saved.
+     * @return updated entity type {@link T} instance.
+     * @throws StoreException
+     *             if there was an error saving the entity.
+     */
+    public T save( T entity ) throws StoreException;
+
+    /**
+     * Removes the passed entity type {@link T} instance from the underlying store.
+     * 
+     * @param entity
+     *            Type {@link T} instance to remove.
+     * @throws StoreException
+     *             if there was an error removing the entity.
+     */
+    public void delete( T entity ) throws StoreException;
+
+    /**
+     * Obtains a {@link List} of instances of entity type {@link T} which match the criteria specified by the passed in
+     * query instance.
+     * 
+     * @param query
+     *            instance that wraps up the criteria for querying matching instances in the system.
+     * @return {@link List} of instances of entity type {@link T} which match the specified query.
+     * @throws StoreException
+     */
+    public List<T> query( Q query ) throws StoreException;
+
+}
diff --git a/src/main/java/org/apache/continuum/store/api/StoreException.java b/src/main/java/org/apache/continuum/store/api/StoreException.java
new file mode 100644
index 0000000..e67ac96
--- /dev/null
+++ b/src/main/java/org/apache/continuum/store/api/StoreException.java
@@ -0,0 +1,43 @@
+package org.apache.continuum.store.api;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
+ * @version $Id$
+ */
+public class StoreException extends Exception
+{
+
+    public StoreException()
+    {
+        super();
+    }
+
+    public StoreException( String msg )
+    {
+        super( msg );
+    }
+
+    public StoreException( String msg, Exception ex )
+    {
+        super( msg, ex );
+    }
+}
diff --git a/src/main/java/org/apache/continuum/store/jpa/JpaStore.java b/src/main/java/org/apache/continuum/store/jpa/JpaStore.java
new file mode 100644
index 0000000..318e9b4
--- /dev/null
+++ b/src/main/java/org/apache/continuum/store/jpa/JpaStore.java
@@ -0,0 +1,93 @@
+/**
+ * 
+ */
+package org.apache.continuum.store.jpa;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.continuum.model.CommonUpdatableEntity;
+import org.apache.continuum.store.api.EntityNotFoundException;
+import org.apache.continuum.store.api.Query;
+import org.apache.continuum.store.api.Store;
+import org.apache.continuum.store.api.StoreException;
+import org.springframework.orm.jpa.JpaObjectRetrievalFailureException;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ * @since 1.2
+ */
+public class JpaStore<T extends CommonUpdatableEntity, Q extends Query<T>> extends StoreSupport implements Store<T, Q>
+{
+
+    /**
+     * @{inheritDoc}
+     * 
+     * @see org.apache.continuum.store.api.Store#delete(java.lang.Object)
+     */
+    @Transactional( readOnly = false )
+    public void delete( T entity ) throws StoreException
+    {
+        getJpaTemplate().remove( entity );
+    }
+
+    /**
+     * @{inheritDoc}
+     * 
+     * @see org.apache.continuum.store.api.Store#lookup(Class, java.lang.Long)
+     */
+    public T lookup( Class<T> klass, Long id ) throws StoreException, EntityNotFoundException
+    {
+        if ( id == null )
+            throw new EntityNotFoundException();
+        T entity = null;
+        try
+        {
+            entity = getJpaTemplate().find( klass, id );
+        }
+        catch ( JpaObjectRetrievalFailureException e )
+        {
+            throw new EntityNotFoundException();
+        }
+        if ( entity == null )
+            throw new EntityNotFoundException();
+        return entity;
+    }
+
+    /**
+     * @{inheritDoc}
+     * 
+     * @see org.apache.continuum.store.api.Store#query(org.apache.continuum.store.api.Query)
+     */
+    public List<T> query( Q query ) throws StoreException
+    {
+        Map<String, Object> where = new HashMap<String, Object>();
+        String q = query.toString( where );
+
+        List<T> results = find( q, where, 0, 0 );
+
+        return results;
+    }
+
+    /**
+     * @{inheritDoc}
+     * 
+     * @see org.apache.continuum.store.api.Store#save(java.lang.Object)
+     */
+    @Transactional( readOnly = true )
+    public T save( T entity ) throws StoreException
+    {
+        if ( null != entity )
+        {
+            if ( null == entity.getId() )
+                getJpaTemplate().persist( entity );
+            else
+                entity = getJpaTemplate().merge( entity );
+        }
+        return entity;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/store/jpa/JpaStoreFactory.java b/src/main/java/org/apache/continuum/store/jpa/JpaStoreFactory.java
new file mode 100644
index 0000000..4824527
--- /dev/null
+++ b/src/main/java/org/apache/continuum/store/jpa/JpaStoreFactory.java
@@ -0,0 +1,70 @@
+/**
+ * 
+ */
+package org.apache.continuum.store.jpa;
+
+import org.apache.continuum.model.project.Project;
+import org.apache.continuum.model.project.ProjectGroup;
+import org.apache.continuum.model.project.ProjectNotifier;
+import org.apache.continuum.store.api.Store;
+
+/**
+ * Bean factory that is used by Spring container to create and return instances of {@link Store} implementations.
+ * 
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ * @since 1.2
+ */
+public class JpaStoreFactory
+{
+
+    /**
+     * Store instance that services executes requests on underlying store for {@link Project} entities.
+     */
+    private final JpaStore<Project, ProjectQuery<Project>> JPA_PROJECT_STORE =
+        new JpaStore<Project, ProjectQuery<Project>>();
+
+    /**
+     * Store instance that services executes requests on underlying store for {@link ProjectGroup} entities.
+     */
+    private final JpaStore<ProjectGroup, ProjectGroupQuery<ProjectGroup>> JPA_PROJECT_GROUP_STORE =
+        new JpaStore<ProjectGroup, ProjectGroupQuery<ProjectGroup>>();
+
+    /**
+     * Store instance that services executes requests on underlying store for {@link ProjectNotifier} entities.
+     */
+    private final JpaStore<ProjectNotifier, ProjectNotifierQuery<ProjectNotifier>> JPA_PROJECT_NOTIFIER_STORE =
+        new JpaStore<ProjectNotifier, ProjectNotifierQuery<ProjectNotifier>>();
+
+    /**
+     * Returns a {@link Store} instance to service {@link Project} Entity.
+     * 
+     * @return a {@link Store} instance to service {@link Project} Entity
+     */
+    public Store<Project, ProjectQuery<Project>> createProjectGroupStoreInstance()
+    {
+        return JPA_PROJECT_STORE;
+    }
+
+    /**
+     * Returns a {@link Store} instance to service {@link ProjectGroup} Entity.
+     * 
+     * @return a {@link Store} instance to service {@link ProjectGroup} Entity.
+     */
+    public Store<ProjectGroup, ProjectGroupQuery<ProjectGroup>> createProjectStoreInstance()
+    {
+        return JPA_PROJECT_GROUP_STORE;
+    }
+
+    /**
+     * Returns a {@link Store} instance to service {@link ProjectNotifier} Entity.
+     * 
+     * @return a {@link Store} instance to service {@link ProjectNotifier} Entity.
+     */
+    public Store<ProjectNotifier, ProjectNotifierQuery<ProjectNotifier>> createProjectNotifierStoreInstance()
+    {
+
+        return JPA_PROJECT_NOTIFIER_STORE;
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/store/jpa/ProjectGroupQuery.java b/src/main/java/org/apache/continuum/store/jpa/ProjectGroupQuery.java
new file mode 100644
index 0000000..7d4999c
--- /dev/null
+++ b/src/main/java/org/apache/continuum/store/jpa/ProjectGroupQuery.java
@@ -0,0 +1,308 @@
+/**
+ * 
+ */
+package org.apache.continuum.store.jpa;
+
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.continuum.store.api.Query;
+
+/**
+ * Wraps up retrieval criteria for {@link ProjectGroup}s.
+ * 
+ * @author <a href='mailto:rinku@apache.org'>Rahul Thakur</a>
+ * @version $Id$
+ * @since 1.2
+ */
+public class ProjectGroupQuery<ProjectGroup> implements Query<ProjectGroup>
+{
+
+    /**
+     * ProjectGroup creation date criteria.
+     */
+    private Date dateCreated;
+
+    /**
+     * ProjectGroup update date criteria.
+     */
+    private Date dateUpdated;
+
+    /**
+     * ProjectGroup description criteria.
+     */
+    private String description;
+
+    /**
+     * ProjectGroup groupId criteria.
+     */
+    private String groupId;
+
+    /**
+     * ProjectGroup Id criteria.
+     */
+    private Long id;
+
+    /**
+     * ProjectGroup model encoding criteria.
+     */
+    private String modelEncoding;
+
+    /**
+     * ProjectGroup name criteria.
+     */
+    private String name;
+
+    /**
+     * @return
+     * 
+     */
+    public Date getDateCreated()
+    {
+        return this.dateCreated;
+    }
+
+    /**
+     * @return
+     * @see org.apache.continuum.model.CommonUpdatableEntity#getDateUpdated()
+     */
+    public Date getDateUpdated()
+    {
+        return this.dateUpdated;
+    }
+
+    /**
+     * @return
+     * 
+     */
+    public String getDescription()
+    {
+        return this.description;
+    }
+
+    /**
+     * @return
+     * 
+     */
+    public String getGroupId()
+    {
+        return this.groupId;
+    }
+
+    /**
+     * Determine if a date of creation was specified in the query.
+     * 
+     * @return <code>true</code> if a date of creation was specified, else <code>false</code>.
+     */
+    public boolean hasDateCreated()
+    {
+        return ( null != this.dateCreated );
+    }
+
+    /**
+     * Determine if an update date was specified in the query.
+     * 
+     * @return <code>true</code> if a date of update was specified, else <code>false</code>.
+     */
+    public boolean hasDateUpdated()
+    {
+        return ( null != this.dateUpdated );
+    }
+
+    /**
+     * Determine if there was a Project Group 'description' specified in the query.
+     * 
+     * @return <code>true</code> if there was a Project Group 'description' specified , else <code>false</code>.
+     */
+    public boolean hasDescription()
+    {
+        return ( null != this.description && this.description.length() > 0 );
+    }
+
+    /**
+     * Determine if there was a Group Id for the {@link ProjectGroup} specified in the query.
+     * 
+     * @return <code>true</code> if there was a Group Id for the {@link ProjectGroup} specified, else
+     *         <code>false</code>.
+     */
+    public boolean hasGroupId()
+    {
+        return ( null != this.groupId && this.groupId.length() > 0 );
+    }
+
+    /**
+     * 
+     * @return
+     */
+    public boolean hasId()
+    {
+        return ( null != this.id && this.id.longValue() > 0L );
+    }
+
+    /**
+     * Determine if there was a model encoding specified in the query.
+     * 
+     * @return <code>true</code> if there was a model encoding specified, else <code>false</code>.
+     */
+    public boolean hasModelEncoding()
+    {
+        return ( null != this.modelEncoding && this.modelEncoding.length() > 0 );
+    }
+
+    /**
+     * Determine if there is a {@link ProjectGroup} name specified in the query.
+     * 
+     * @return <code>true</code> if there is a {@link ProjectGroup} name specified, else <code>false</code>.
+     */
+    public boolean hasName()
+    {
+        return ( null != this.name && this.name.length() > 0 );
+    }
+
+    /**
+     * @return
+     */
+    public Long getId()
+    {
+        return this.id;
+    }
+
+    /**
+     * @return
+     */
+    public String getModelEncoding()
+    {
+        return this.modelEncoding;
+    }
+
+    /**
+     * @return
+     */
+    public String getName()
+    {
+        return this.name;
+    }
+
+    /**
+     * @param dateCreated
+     */
+    public void setDateCreated( Date dateCreated )
+    {
+        this.dateCreated = dateCreated;
+    }
+
+    /**
+     * @param dateUpdated
+     */
+    public void setDateUpdated( Date dateUpdated )
+    {
+        this.dateUpdated = dateUpdated;
+    }
+
+    /**
+     * @param description
+     */
+    public void setDescription( String description )
+    {
+        this.description = description;
+    }
+
+    /**
+     * @param groupId
+     */
+    public void setGroupId( String groupId )
+    {
+        this.groupId = groupId;
+    }
+
+    /**
+     * @param id
+     */
+    public void setId( Long id )
+    {
+        this.id = id;
+    }
+
+    /**
+     * @param modelEncoding
+     */
+    public void setModelEncoding( String modelEncoding )
+    {
+        this.modelEncoding = modelEncoding;
+    }
+
+    /**
+     * @param name
+     */
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+    /**
+     * @{inheritDoc}
+     * 
+     * @see org.apache.continuum.store.api.Query#toString(java.util.Map)
+     */
+    public String toString( Map<String, Object> whereClause )
+    {
+        StringBuffer sb = new StringBuffer();
+
+        if ( this.hasId() )
+        {
+            whereClause.put( "id", this.getId() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " projectGroup.id =:id " );
+        }
+        if ( this.hasDateCreated() )
+        {
+            whereClause.put( "dateCreated", this.getDateCreated() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " projectGroup.dateCreated =:dateCreated " );
+        }
+        if ( this.hasDateUpdated() )
+        {
+            whereClause.put( "dateUpdated", this.getDateUpdated() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " projectGroup.dateUpdated =:dateUpdated " );
+        }
+        if ( this.hasDescription() )
+        {
+            whereClause.put( "description", this.getDescription() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " projectGroup.description =:description " );
+        }
+        if ( this.hasGroupId() )
+        {
+            whereClause.put( "groupId", this.getGroupId() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " projectGroup.groupId =:groupId " );
+        }
+        if ( this.hasModelEncoding() )
+        {
+            whereClause.put( "modelEncoding", this.getModelEncoding() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " projectGroup.modelEncoding =:modelEncoding " );
+        }
+        if ( this.hasName() )
+        {
+            whereClause.put( "name", this.getName() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " projectGroup.name =:name " );
+        }
+
+        if ( sb.length() > 0 )
+            sb.insert( 0, " where " );
+        sb.insert( 0, "select projectGroup from ProjectGroup as projectGroup " );
+
+        return sb.toString();
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/store/jpa/ProjectNotifierQuery.java b/src/main/java/org/apache/continuum/store/jpa/ProjectNotifierQuery.java
new file mode 100644
index 0000000..08312b1
--- /dev/null
+++ b/src/main/java/org/apache/continuum/store/jpa/ProjectNotifierQuery.java
@@ -0,0 +1,247 @@
+/**
+ * 
+ */
+package org.apache.continuum.store.jpa;
+
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.continuum.model.project.Project;
+import org.apache.continuum.model.project.ProjectGroup;
+import org.apache.continuum.store.api.Query;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ * @since 1.2
+ */
+public class ProjectNotifierQuery<ProjectNotifier> implements Query<ProjectNotifier>
+{
+
+    /**
+     * ProjectNotifier creation date criteria.
+     */
+    private Date dateCreated;
+
+    /**
+     * ProjectNotifier update date criteria.
+     */
+    private Date dateUpdated;
+
+    /**
+     * ProjectNotifier Id criteria.
+     */
+    private Long id;
+
+    /**
+     * Determines if a ProjectNotifier is set up on a {@link Project} or a {@link ProjectGroup}.
+     */
+    private boolean isDefinedOnProject = false;
+
+    /**
+     * Determines if a {@link ProjectNotifier} is defined by a user.
+     */
+    private boolean isUserDefined = false;
+
+    /**
+     * ProjectNotifier model encoding criteria.
+     */
+    private String modelEncoding;
+
+    /**
+     * @return
+     * 
+     */
+    public Date getDateCreated()
+    {
+        return this.dateCreated;
+    }
+
+    /**
+     * @return
+     * @see org.apache.continuum.model.CommonUpdatableEntity#getDateUpdated()
+     */
+    public Date getDateUpdated()
+    {
+        return this.dateUpdated;
+    }
+
+    /**
+     * @return
+     */
+    public Long getId()
+    {
+        return this.id;
+    }
+
+    /**
+     * @return
+     */
+    public String getModelEncoding()
+    {
+        return this.modelEncoding;
+    }
+
+    /**
+     * Determine if a date of creation was specified in the query.
+     * 
+     * @return <code>true</code> if a date of creation was specified, else <code>false</code>.
+     */
+    public boolean hasDateCreated()
+    {
+        return ( null != this.dateCreated );
+    }
+
+    /**
+     * Determine if an update date was specified in the query.
+     * 
+     * @return <code>true</code> if a date of update was specified, else <code>false</code>.
+     */
+    public boolean hasDateUpdated()
+    {
+        return ( null != this.dateUpdated );
+    }
+
+    /**
+     * 
+     * @return
+     */
+    public boolean hasId()
+    {
+        return ( null != this.id && this.id.longValue() > 0L );
+    }
+
+    /**
+     * Determine if there was a model encoding specified in the query.
+     * 
+     * @return <code>true</code> if there was a model encoding specified, else <code>false</code>.
+     */
+    public boolean hasModelEncoding()
+    {
+        return ( null != this.modelEncoding && this.modelEncoding.length() > 0 );
+    }
+
+    /**
+     * @return the isDefinedOnProject
+     */
+    public boolean isDefinedOnProject()
+    {
+        return isDefinedOnProject;
+    }
+
+    /**
+     * @return the isUserDefined
+     */
+    public boolean isUserDefined()
+    {
+        return isUserDefined;
+    }
+
+    /**
+     * @param dateCreated
+     */
+    public void setDateCreated( Date dateCreated )
+    {
+        this.dateCreated = dateCreated;
+    }
+
+    /**
+     * @param dateUpdated
+     */
+    public void setDateUpdated( Date dateUpdated )
+    {
+        this.dateUpdated = dateUpdated;
+    }
+
+    /**
+     * @param isDefinedOnProject
+     *            the isDefinedOnProject to set
+     */
+    public void setDefinedOnProject( boolean isDefinedOnProject )
+    {
+        this.isDefinedOnProject = isDefinedOnProject;
+    }
+
+    /**
+     * @param id
+     */
+    public void setId( Long id )
+    {
+        this.id = id;
+    }
+
+    /**
+     * @param modelEncoding
+     */
+    public void setModelEncoding( String modelEncoding )
+    {
+        this.modelEncoding = modelEncoding;
+    }
+
+    /**
+     * @param isUserDefined
+     *            the isUserDefined to set
+     */
+    public void setUserDefined( boolean isUserDefined )
+    {
+        this.isUserDefined = isUserDefined;
+    }
+
+    /**
+     * @{inheritDoc}
+     * 
+     * @see org.apache.continuum.store.api.Query#toString(java.util.Map)
+     */
+    public String toString( Map<String, Object> whereClause )
+    {
+        StringBuffer sb = new StringBuffer();
+
+        if ( this.hasId() )
+        {
+            whereClause.put( "id", this.getId() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " notifier.id =:id " );
+        }
+        if ( this.hasDateCreated() )
+        {
+            whereClause.put( "dateCreated", this.getDateCreated() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " notifier.dateCreated =:dateCreated " );
+        }
+        if ( this.hasDateUpdated() )
+        {
+            whereClause.put( "dateUpdated", this.getDateUpdated() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " notifier.dateUpdated =:dateUpdated " );
+        }
+        if ( this.hasModelEncoding() )
+        {
+            whereClause.put( "modelEncoding", this.getModelEncoding() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " notifier.modelEncoding =:modelEncoding " );
+        }
+        if ( this.isDefinedOnProject() )
+        {
+            // TODO: Implement!
+            // Need to check what property is setup on the Notifier.
+            // May need to add a property ORM mapping to persist.
+        }
+        if ( this.isUserDefined() )
+        {
+            // TODO: Implement!
+            // Need to check what property is setup on the Notifier.
+            // May need to add a property ORM mapping to persist.
+        }
+
+        if ( sb.length() > 0 )
+            sb.insert( 0, " where " );
+        sb.insert( 0, "select project from Project as project " );
+
+        return sb.toString();
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/store/jpa/ProjectQuery.java b/src/main/java/org/apache/continuum/store/jpa/ProjectQuery.java
new file mode 100644
index 0000000..cc744ba
--- /dev/null
+++ b/src/main/java/org/apache/continuum/store/jpa/ProjectQuery.java
@@ -0,0 +1,403 @@
+/**
+ * 
+ */
+package org.apache.continuum.store.jpa;
+
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.continuum.store.api.Query;
+
+/**
+ * Wraps up retrieval criteria for {@link Project}s.
+ * 
+ * @author <a href='mailto:rinku@apache.org'>Rahul Thakur</a>
+ * @version $Id$
+ * @since 1.2
+ */
+public class ProjectQuery<Project> implements Query<Project>
+{
+
+    /**
+     * Project artifactId criteria.
+     */
+    private String artifactId;
+
+    /**
+     * Project groupId criteria.
+     */
+    private String groupId;
+
+    /**
+     * Project id criteria.
+     */
+    private Long id;
+
+    /**
+     * Project name criteria.
+     */
+    private String name;
+
+    /**
+     * Project Artifact version criteria.
+     */
+    private String version;
+
+    private int buildNumber;
+
+    private Date dateCreated;
+
+    private Date dateUpdated;
+
+    private String description;
+
+    private String modelEncoding;
+
+    /**
+     * @return the artifactId
+     */
+    public String getArtifactId()
+    {
+        return this.artifactId;
+    }
+
+    /**
+     * @return build number as query critera
+     */
+    public int getBuildNumber()
+    {
+        return this.buildNumber;
+    }
+
+    /**
+     * Determines if a build number criteria was specified in the Project query.
+     * 
+     * @return
+     */
+    public boolean hasBuildNumber()
+    {
+        return ( this.buildNumber > 0 );
+    }
+
+    /**
+     * @return creation date as query criteria
+     */
+    public Date getDateCreated()
+    {
+        return this.dateCreated;
+    }
+
+    /**
+     * Determines if there was a creation date criteria specified in the ProjectQuery.
+     * 
+     * @return <code>true</code> if a creation date was specified, else <code>false</code>.
+     */
+    public boolean hasDateCreated()
+    {
+        return ( null != this.dateCreated );
+    }
+
+    /**
+     * @return last update date from query criteria.
+     */
+    public Date getDateUpdated()
+    {
+        return this.dateUpdated;
+    }
+
+    /**
+     * Determine if there was a last update date criteria specified in the ProjectQuery.
+     * 
+     * @return <code>true</code> if there was a last update date criteria specified, else <code>false</code>.
+     */
+    public boolean hasDateUpdated()
+    {
+        return ( null != this.dateUpdated );
+    }
+
+    /**
+     * @return description criteria from the query.
+     */
+    public String getDescription()
+    {
+        return this.description;
+    }
+
+    /**
+     * Determines if there was description criteria specified in the ProjectQuery.
+     * 
+     * @return <code>true</code> if there was description criteria specified, else <code>false</code>.
+     */
+    public boolean hasDescription()
+    {
+        return ( null != this.description && this.description.length() > 0 );
+    }
+
+    /**
+     * @return the groupId
+     */
+    public String getGroupId()
+    {
+        return this.groupId;
+    }
+
+    /**
+     * @return the id
+     */
+    public Long getId()
+    {
+        return this.id;
+    }
+
+    /**
+     * @return model encoding criteria from the query.
+     */
+    public String getModelEncoding()
+    {
+        return this.modelEncoding;
+    }
+
+    /**
+     * Determine if there is a model encoding specified in the ProjectQuery.
+     * 
+     * @return <code>true</code> if there is a model encoding specified, else <code>false</code>.
+     */
+    public boolean hasModelEncoding()
+    {
+        return ( null != this.modelEncoding && this.modelEncoding.length() > 0 );
+    }
+
+    /**
+     * @return the name
+     */
+    public String getName()
+    {
+        return this.name;
+    }
+
+    /**
+     * @return the version
+     */
+    public String getVersion()
+    {
+        return this.version;
+    }
+
+    /**
+     * Determines if an artifact Id criteria was specified.
+     * 
+     * @return
+     */
+    public boolean hasArtifactId()
+    {
+        return ( null != this.artifactId );
+    }
+
+    /**
+     * Determines if a ProjectGroup Id criteria was specified.
+     * 
+     * @return
+     */
+    public boolean hasGroupId()
+    {
+        return ( null != this.groupId );
+    }
+
+    /**
+     * Determines if a Project id criteria was specified in the query.
+     * 
+     * @return
+     */
+    public boolean hasId()
+    {
+        return ( null != this.id && this.id >= 0 );
+    }
+
+    /**
+     * Determines if a project name criteria was specified.
+     * 
+     * @return
+     */
+    public boolean hasName()
+    {
+        return ( null != this.name );
+    }
+
+    /**
+     * Determines if a Version criteria was specified.
+     * 
+     * @return
+     */
+    public boolean hasVersion()
+    {
+        return ( null != this.version );
+    }
+
+    /**
+     * @param artifactId
+     *            the artifactId to set
+     */
+    public void setArtifactId( String artifactId )
+    {
+        this.artifactId = artifactId;
+    }
+
+    /**
+     * @param buildNumber
+     *            criteria to set in the ProjectQuery
+     */
+    public void setBuildNumber( int buildNumber )
+    {
+        this.buildNumber = buildNumber;
+    }
+
+    /**
+     * @param created
+     *            criteria to set in the Project Query.
+     */
+    public void setDateCreated( Date dateCreated )
+    {
+        this.dateCreated = dateCreated;
+    }
+
+    /**
+     * @param updated
+     *            date criteria to set in the Project Query.
+     */
+    public void setDateUpdated( Date dateUpdated )
+    {
+        this.dateUpdated = dateUpdated;
+    }
+
+    /**
+     * @param description
+     */
+    public void setDescription( String description )
+    {
+        this.description = description;
+    }
+
+    /**
+     * @param groupId
+     *            the groupId to set
+     */
+    public void setGroupId( String groupId )
+    {
+        this.groupId = groupId;
+    }
+
+    /**
+     * @param id
+     *            the id to set
+     */
+    public void setId( Long id )
+    {
+        this.id = id;
+    }
+
+    /**
+     * @param name
+     *            the name to set
+     */
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+    /**
+     * @param version
+     *            the version to set
+     */
+    public void setVersion( String version )
+    {
+        this.version = version;
+    }
+
+    /**
+     * @{inheritDoc}
+     * 
+     * @see org.apache.continuum.store.api.Query#toString(java.util.Map)
+     */
+    public String toString( Map<String, Object> whereClause )
+    {
+        StringBuffer sb = new StringBuffer();
+
+        if ( this.hasId() )
+        {
+            whereClause.put( "id", this.getId() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " project.id =:id " );
+        }
+        if ( this.hasDateCreated() )
+        {
+            whereClause.put( "dateCreated", this.getDateCreated() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " project.dateCreated =:dateCreated " );
+        }
+        if ( this.hasDateUpdated() )
+        {
+            whereClause.put( "dateUpdated", this.getDateUpdated() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " project.dateUpdated =:dateUpdated " );
+        }
+        if ( this.hasDescription() )
+        {
+            whereClause.put( "description", this.getDescription() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " project.description =:description " );
+        }
+        if ( this.hasGroupId() )
+        {
+            whereClause.put( "groupId", this.getGroupId() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " project.groupId =:groupId " );
+        }
+        if ( this.hasModelEncoding() )
+        {
+            whereClause.put( "modelEncoding", this.getModelEncoding() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " project.modelEncoding =:modelEncoding " );
+        }
+        if ( this.hasName() )
+        {
+            whereClause.put( "name", this.getName() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " project.name =:name " );
+        }
+        if ( this.hasArtifactId() )
+        {
+            whereClause.put( "artifactId", this.getArtifactId() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " project.artifactId =:artifactId" );
+        }
+        if ( this.hasBuildNumber() )
+        {
+            whereClause.put( "buildNumber", this.getBuildNumber() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " project.buildNumber =:buildNumber" );
+        }
+        if ( this.hasVersion() )
+        {
+            whereClause.put( "version", this.getVersion() );
+            if ( sb.length() > 0 )
+                sb.append( "and" );
+            sb.append( " project.version =:version" );
+        }
+
+        if ( sb.length() > 0 )
+            sb.insert( 0, " where " );
+        sb.insert( 0, "select project from Project as project " );
+
+        return sb.toString();
+    }
+
+}
diff --git a/src/main/java/org/apache/continuum/store/jpa/StoreSupport.java b/src/main/java/org/apache/continuum/store/jpa/StoreSupport.java
new file mode 100644
index 0000000..15e5e1e
--- /dev/null
+++ b/src/main/java/org/apache/continuum/store/jpa/StoreSupport.java
@@ -0,0 +1,68 @@
+/**
+ * 
+ */
+package org.apache.continuum.store.jpa;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceException;
+import javax.persistence.Query;
+
+import org.apache.continuum.store.api.Store;
+import org.springframework.dao.DataAccessException;
+import org.springframework.orm.jpa.JpaCallback;
+import org.springframework.orm.jpa.support.JpaDaoSupport;
+
+/**
+ * Base class for concrete {@link Store} implementations that provides service methods for Entity retrievals.
+ * 
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ * @since 1.2
+ */
+public abstract class StoreSupport extends JpaDaoSupport
+{
+
+    /**
+     * Prepares and executes a query using the 'where' criteria, a start index and a given range of results to return.
+     * 
+     * @param queryString
+     * @param whereParams
+     * @param startIndex
+     * @param range
+     * @return
+     * @throws DataAccessException
+     */
+    protected List find( final String queryString, final Map<String, Object> whereParams, final int startIndex,
+                         final int range ) throws DataAccessException
+    {
+        return getJpaTemplate().executeFind( new JpaCallback()
+        {
+            public Object doInJpa( EntityManager em ) throws PersistenceException
+            {
+                Query query = em.createQuery( queryString );
+                if ( whereParams != null )
+                {
+                    for ( Iterator it = whereParams.entrySet().iterator(); it.hasNext(); )
+                    {
+                        Map.Entry<String, Object> entry = (Map.Entry<String, Object>) it.next();
+                        query.setParameter( entry.getKey(), entry.getValue() );
+                    }
+                }
+                if ( startIndex > 0 )
+                {
+                    query.setFirstResult( startIndex );
+                }
+                if ( range > 0 )
+                {
+                    query.setMaxResults( range );
+                }
+                return query.getResultList();
+            }
+        } );
+    }
+
+}
diff --git a/src/test/java/org/apache/continuum/store/ApplicationContextAwareStoreTestCase.java b/src/test/java/org/apache/continuum/store/ApplicationContextAwareStoreTestCase.java
new file mode 100644
index 0000000..f7bc886
--- /dev/null
+++ b/src/test/java/org/apache/continuum/store/ApplicationContextAwareStoreTestCase.java
@@ -0,0 +1,161 @@
+package org.apache.continuum.store;
+
+import org.apache.continuum.dao.api.GenericDao;
+import org.apache.continuum.model.CommonUpdatableEntity;
+import org.apache.openjpa.persistence.test.SingleEMTestCase;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test Case support class that allows extensions to load test data from specified SQL files.
+ * <p/>
+ * This also implements Spring's {@link ApplicationContextAware} interface that allows the {@link ApplicationContext} to
+ * be made available to this test case's extensions.
+ *
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ * @since 1.2
+ */
+public abstract class ApplicationContextAwareStoreTestCase
+    extends SingleEMTestCase
+    implements ApplicationContextAware
+{
+    /**
+     * Continuum Store persistent unit defined in <code>persistence.xml</code> used by tests.
+     */
+    private static final String PERSISTENT_UNIT_CONTINUUM_STORE = "continuum-store";
+
+    /**
+     * Spring application context.
+     */
+    private ApplicationContext applicationContext;
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Spring IoC container injects the {@link ApplicationContext} through here.
+     *
+     * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
+     */
+    public void setApplicationContext( ApplicationContext applicationContext )
+        throws BeansException
+    {
+        this.applicationContext = applicationContext;
+    }
+
+    /**
+     * Imports sql from the specified file.
+     *
+     * @param sqlResource Resource containing sql
+     */
+    public void setSqlSource( File sqlResource )
+    {
+        try
+        {
+            // TODO: Use Logger!
+            // System.out.println( "Loading sql: " + sqlResource.getAbsolutePath() );
+            List<String> statements = new ArrayList<String>( 20 );
+            BufferedReader br = new BufferedReader( new InputStreamReader( new FileInputStream( sqlResource ) ) );
+            String line;
+            StringBuffer currentStatement = new StringBuffer();
+            while ( ( line = br.readLine() ) != null )
+            {
+                if ( line.trim().length() == 0 )
+                {
+                    continue;
+                }
+                if ( line.trim().startsWith( "#" ) )
+                {
+                    continue;
+                }
+
+                currentStatement.append( line );
+                if ( line.endsWith( ";" ) )
+                {
+                    statements.add( currentStatement.toString() );
+                    currentStatement = new StringBuffer();
+                }
+            }
+            // append a line if missing a ';'
+            if ( currentStatement.length() > 0 )
+            {
+                statements.add( currentStatement.toString() );
+            }
+            runSQLStatements( statements );
+        }
+        catch ( Throwable e )
+        {
+            // TODO: User logger!
+            System.err.println( "Problem executing SQL!" );
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Run a bunch of SQL statements.
+     *
+     * @param statements Statements to run.
+     * @throws SQLException
+     */
+    public void runSQLStatements( final List<String> statements )
+        throws SQLException
+    {
+        for ( String qry : statements )
+        {
+            Connection con = (Connection) this.em.getConnection();
+            try
+            {
+                Statement stmt = con.createStatement();
+                System.out.println( qry );
+                stmt.execute( qry );
+                con.commit();
+            }
+            catch ( SQLException e )
+            {
+                try
+                {
+                    con.rollback();
+                }
+                catch ( SQLException e1 )
+                {
+                    // TODO: Use logger!
+                    System.err.println( "Unable to rollback transaction." );
+                    throw e1;
+                }
+                throw e;
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T extends CommonUpdatableEntity> GenericDao<T> getDao( String daoBeanReference )
+    {
+        return (GenericDao<T>) getObject( daoBeanReference );
+    }
+
+    protected Object getObject( String beanReference )
+    {
+        return applicationContext.getBean( beanReference );
+    }
+
+    /**
+     * Returns the name of the persistent-unit setup in <code>persistence.xml</code>.
+     */
+    @Override
+    protected String getPersistenceUnitName()
+    {
+        return PERSISTENT_UNIT_CONTINUUM_STORE;
+    }
+
+}
diff --git a/src/test/java/org/apache/continuum/store/jpa/JpaProjectGroupStoreTest.java b/src/test/java/org/apache/continuum/store/jpa/JpaProjectGroupStoreTest.java
new file mode 100644
index 0000000..d2758cb
--- /dev/null
+++ b/src/test/java/org/apache/continuum/store/jpa/JpaProjectGroupStoreTest.java
@@ -0,0 +1,92 @@
+/**
+ *
+ */
+package org.apache.continuum.store.jpa;
+
+import org.apache.continuum.model.project.ProjectGroup;
+import org.apache.continuum.service.api.ProjectGroupService;
+import org.apache.continuum.store.ApplicationContextAwareStoreTestCase;
+import org.apache.continuum.store.api.StoreException;
+import org.apache.openjpa.persistence.OpenJPAQuery;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import java.io.File;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ * @see <a
+ *      href="http://mail-archives.apache.org/mod_mbox/openjpa-users/200706.mbox/%3CBF2B99E3-7EF3-4E99-91E1-8AEB940524C7@apache.org%3E">
+ *      http://mail-archives.apache.org/mod_mbox/openjpa-users/200706.mbox/%3CBF2B99E3-7EF3-4E99-91E1-8AEB940524C7@apache.org%3E
+ *      </a>
+ * @since 1.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = "/META-INF/spring-config.xml")
+public class JpaProjectGroupStoreTest
+    extends ApplicationContextAwareStoreTestCase
+{
+    private static final String BEAN_REF__PROJECT_GROUP_STORE = "projectGroupStore";
+
+    @Override
+    @Before
+    public void setUp()
+    {
+        File testData = new File( "src/test/resources/sql/project-group-table-data.sql" );
+        Assert.assertTrue( "Unable to find test data resource: " + testData.getAbsolutePath(), testData.exists() );
+        Properties propMap = new Properties();
+        setUp( propMap );
+
+        // load test data from SQL file.
+        setSqlSource( testData );
+    }
+
+    @Override
+    @After
+    public void tearDown()
+        throws Exception
+    {
+        super.tearDown();
+    }
+
+    @Test
+    public void testOpenJPASetup()
+    {
+        OpenJPAQuery q = em.createQuery( "select pg from ProjectGroup pg" );
+        String[] sql = q.getDataStoreActions( null );
+        Assert.assertEquals( 1, sql.length );
+        Assert.assertTrue( sql[0].startsWith( "SELECT" ) );
+        List results = q.getResultList();
+        Assert.assertNotNull( results );
+        Assert.assertEquals( 1, results.size() );
+    }
+
+    @Test
+    public void testCreateProjectGroup()
+        throws StoreException
+    {
+        ProjectGroup group = new ProjectGroup();
+        group.setGroupId( "org.sample.group" );
+        group.setName( "Sample Project Group" );
+        group.setDescription( "A sample project group" );
+
+        Assert.assertTrue( null == group.getId() );
+        group = getService().saveOrUpdate( group );
+        Assert.assertTrue( null != group.getId() );
+        Assert.assertTrue( group.getId() > 0L );
+        Assert.assertEquals( 0, group.getProjects().size() );
+    }
+
+    private ProjectGroupService getService()
+    {
+        return (ProjectGroupService) getObject( "projectGroupService" );
+    }
+}
diff --git a/src/test/java/org/apache/continuum/store/jpa/JpaProjectStoreTest.java b/src/test/java/org/apache/continuum/store/jpa/JpaProjectStoreTest.java
new file mode 100644
index 0000000..f6fe8e9
--- /dev/null
+++ b/src/test/java/org/apache/continuum/store/jpa/JpaProjectStoreTest.java
@@ -0,0 +1,120 @@
+package org.apache.continuum.store.jpa;
+
+import org.apache.continuum.dao.api.GenericDao;
+import org.apache.continuum.model.project.Project;
+import org.apache.continuum.service.api.ProjectService;
+import org.apache.continuum.store.ApplicationContextAwareStoreTestCase;
+import org.apache.continuum.store.api.StoreException;
+import org.apache.openjpa.persistence.OpenJPAQuery;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import javax.persistence.Query;
+import java.io.File;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @author <a href='mailto:evenisse@apache.org'>Emmanuel Venisse</a>
+ * @version $Id$
+ * @see <a
+ *      href="http://mail-archives.apache.org/mod_mbox/openjpa-users/200706.mbox/%3CBF2B99E3-7EF3-4E99-91E1-8AEB940524C7@apache.org%3E">
+ *      http://mail-archives.apache.org/mod_mbox/openjpa-users/200706.mbox/%3CBF2B99E3-7EF3-4E99-91E1-8AEB940524C7@apache.org%3E
+ *      </a>
+ * @since 1.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = "/META-INF/spring-config.xml")
+public class JpaProjectStoreTest
+    extends ApplicationContextAwareStoreTestCase
+{
+    @Override
+    @Before
+    public void setUp()
+    {
+        File testData = new File( "src/test/resources/sql/project-table-data.sql" );
+        Properties propMap = new Properties();
+        setUp( propMap );
+        // load test data from SQL file.
+        setSqlSource( testData );
+    }
+
+    @Override
+    @After
+    public void tearDown()
+        throws Exception
+    {
+        super.tearDown();
+    }
+
+    @Test
+    public void testOpenJPASetup()
+    {
+        OpenJPAQuery q = em.createQuery( "select p from Project p" );
+        String[] sql = q.getDataStoreActions( null );
+        Assert.assertEquals( 1, sql.length );
+        Assert.assertTrue( sql[0].startsWith( "SELECT" ) );
+        List results = q.getResultList();
+        Assert.assertNotNull( results );
+        Assert.assertEquals( 2, results.size() );
+    }
+
+    @Test
+    public void testNamedQuery()
+    {
+        Query q = em.createNamedQuery( "Project.findAll" );
+        List<Project> projects = q.getResultList();
+        Assert.assertEquals( 2, projects.size() );
+        for ( Project p : projects )
+        {
+            System.out.println( p.getClass().getName() );
+            System.out.println( p );
+        }
+    }
+
+    @Test
+    public void testDao()
+    {
+        GenericDao<Project> dao = getDao( "projectDao" );
+        List<Project> projects = dao.findAll();
+        Assert.assertTrue( 2==projects.size() );
+    }
+
+    @Test
+    public void testCreateProject()
+        throws StoreException
+    {
+        Project project = new Project();
+        project.setArtifactId( "sample-project" );
+        project.setGroupId( "org.sample.group" );
+        project.setName( "Sample Project" );
+        project.setDescription( "A sample project" );
+        project.setScmUseCache( false );
+        project.setScmUrl( "https://localhost/svn/sample-project" );
+
+        Assert.assertTrue( null == project.getId() );
+
+        //store project
+        project = getService().saveOrUpdate( project );
+        Assert.assertTrue( "Identifier of the persisted new Entity should not be null.", null != project.getId() );
+        Assert.assertTrue( "Identifier of the persisted new Entity should be a valid positive value.",
+                           project.getId() > 0L );
+        long id = project.getId();
+
+        //search the project
+        Project p = getService().getProject( project.getGroupId(), project.getArtifactId(), project.getVersion() );
+        Assert.assertNotNull( p );
+        Assert.assertTrue( id == p.getId() );
+    }
+
+    private ProjectService getService()
+    {
+        return (ProjectService) getObject( "projectService" );
+    }
+}
diff --git a/src/test/java/org/apache/openjpa/persistence/test/PersistenceTestCase.java b/src/test/java/org/apache/openjpa/persistence/test/PersistenceTestCase.java
new file mode 100644
index 0000000..6131ed5
--- /dev/null
+++ b/src/test/java/org/apache/openjpa/persistence/test/PersistenceTestCase.java
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.test;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+
+import org.apache.openjpa.kernel.AbstractBrokerFactory;
+import org.apache.openjpa.kernel.Broker;
+import org.apache.openjpa.meta.ClassMetaData;
+import org.apache.openjpa.persistence.JPAFacadeHelper;
+import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
+import org.junit.Assert;
+
+/**
+ * Base test class providing persistence utilities.
+ */
+public abstract class PersistenceTestCase
+{
+
+    /**
+     * Marker object you an pass to {@link #setUp} to indicate that the database tables should be cleared.
+     */
+    protected static final Object CLEAR_TABLES = new Object();
+
+    /**
+     * Create an entity manager factory. Put {@link #CLEAR_TABLES} in this list to tell the test framework to delete all
+     * table contents before running the tests.
+     * 
+     * @param props
+     *            list of persistent types used in testing and/or configuration values in the form
+     *            key,value,key,value...
+     */
+    protected OpenJPAEntityManagerFactorySPI createEMF( Object... props )
+    {
+        return createNamedEMF( getPersistenceUnitName(), props );
+    }
+
+    /**
+     * The name of the persistence unit that this test class should use by default. This defaults to "test".
+     */
+    protected String getPersistenceUnitName()
+    {
+        return "test";
+    }
+
+    /**
+     * Create an entity manager factory for persistence unit <code>pu</code>. Put {@link #CLEAR_TABLES} in this list
+     * to tell the test framework to delete all table contents before running the tests.
+     * 
+     * @param props
+     *            list of persistent types used in testing and/or configuration values in the form
+     *            key,value,key,value...
+     */
+    protected OpenJPAEntityManagerFactorySPI createNamedEMF( String pu, Object... props )
+    {
+        Map map = new HashMap( System.getProperties() );
+        List<Class> types = new ArrayList<Class>();
+        boolean prop = false;
+        for ( int i = 0; i < props.length; i++ )
+        {
+            if ( prop )
+            {
+                map.put( props[i - 1], props[i] );
+                prop = false;
+            }
+            else if ( props[i] == CLEAR_TABLES )
+            {
+                map.put( "openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true,"
+                                + "SchemaAction='add,deleteTableContents')" );
+            }
+            else if ( props[i] instanceof Class )
+                types.add( (Class) props[i] );
+            else if ( props[i] != null )
+                prop = true;
+        }
+
+        if ( !types.isEmpty() )
+        {
+            StringBuffer buf = new StringBuffer();
+            for ( Class c : types )
+            {
+                if ( buf.length() > 0 )
+                    buf.append( ";" );
+                buf.append( c.getName() );
+            }
+            map.put( "openjpa.MetaDataFactory", "jpa(Types=" + buf.toString() + ")" );
+        }
+
+        return (OpenJPAEntityManagerFactorySPI) Persistence.createEntityManagerFactory( pu, map );
+    }
+
+    public void tearDown() throws Exception
+    {
+        // super.tearDown();
+    }
+
+    /**
+     * Safely close the given factory.
+     */
+    protected boolean closeEMF( EntityManagerFactory emf )
+    {
+        if ( emf == null )
+            return false;
+        if ( !emf.isOpen() )
+            return false;
+
+        for ( Iterator iter =
+            ( (AbstractBrokerFactory) JPAFacadeHelper.toBrokerFactory( emf ) ).getOpenBrokers().iterator(); iter.hasNext(); )
+        {
+            Broker b = (Broker) iter.next();
+            if ( b != null && !b.isClosed() )
+            {
+                EntityManager em = JPAFacadeHelper.toEntityManager( b );
+                if ( em.getTransaction().isActive() )
+                    em.getTransaction().rollback();
+                em.close();
+            }
+        }
+
+        emf.close();
+        return !emf.isOpen();
+    }
+
+    /**
+     * Delete all instances of the given types using bulk delete queries.
+     */
+    protected void clear( EntityManagerFactory emf, Class... types )
+    {
+        if ( emf == null || types.length == 0 )
+            return;
+
+        List<ClassMetaData> metas = new ArrayList<ClassMetaData>( types.length );
+        for ( Class c : types )
+        {
+            ClassMetaData meta = JPAFacadeHelper.getMetaData( emf, c );
+            if ( meta != null )
+                metas.add( meta );
+        }
+        clear( emf, metas.toArray( new ClassMetaData[metas.size()] ) );
+    }
+
+    /**
+     * Delete all instances of the persistent types registered with the given factory using bulk delete queries.
+     */
+    protected void clear( EntityManagerFactory emf )
+    {
+        if ( emf == null )
+            return;
+        clear(
+               emf,
+               ( (OpenJPAEntityManagerFactorySPI) emf ).getConfiguration().getMetaDataRepositoryInstance().getMetaDatas() );
+    }
+
+    /**
+     * Delete all instances of the given types using bulk delete queries.
+     */
+    private void clear( EntityManagerFactory emf, ClassMetaData... types )
+    {
+        if ( emf == null || types.length == 0 )
+            return;
+
+        EntityManager em = emf.createEntityManager();
+        em.getTransaction().begin();
+        for ( ClassMetaData meta : types )
+        {
+            if ( !meta.isMapped() || meta.isEmbeddedOnly()
+                            || Modifier.isAbstract( meta.getDescribedType().getModifiers() ) )
+                continue;
+            em.createQuery( "DELETE FROM " + meta.getTypeAlias() + " o" ).executeUpdate();
+        }
+        em.getTransaction().commit();
+        em.close();
+    }
+
+    /**
+     * Return the entity name for the given type.
+     */
+    protected String entityName( EntityManagerFactory emf, Class c )
+    {
+        ClassMetaData meta = JPAFacadeHelper.getMetaData( emf, c );
+        return ( meta == null ) ? null : meta.getTypeAlias();
+    }
+
+    public static void assertNotEquals( Object o1, Object o2 )
+    {
+        if ( o1 == o2 )
+            Assert.fail( "expected args to be different; were the same instance." );
+        else if ( o1 == null || o2 == null )
+            return;
+        else if ( o1.equals( o2 ) )
+            Assert.fail( "expected args to be different; compared equal." );
+    }
+}
diff --git a/src/test/java/org/apache/openjpa/persistence/test/SQLListenerTestCase.java b/src/test/java/org/apache/openjpa/persistence/test/SQLListenerTestCase.java
new file mode 100644
index 0000000..2710723
--- /dev/null
+++ b/src/test/java/org/apache/openjpa/persistence/test/SQLListenerTestCase.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.openjpa.lib.jdbc.AbstractJDBCListener;
+import org.apache.openjpa.lib.jdbc.JDBCEvent;
+import org.apache.openjpa.lib.jdbc.JDBCListener;
+import org.junit.Assert;
+
+/**
+ * Base class for tests that need to check generated SQL.
+ * 
+ * @author Patrick Linskey
+ */
+public abstract class SQLListenerTestCase extends SingleEMFTestCase
+{
+
+    protected List<String> sql = new ArrayList<String>();
+
+    protected int sqlCount;
+
+    @Override
+    public void setUp( Object... props )
+    {
+        Object[] copy = new Object[props.length + 2];
+        System.arraycopy( props, 0, copy, 0, props.length );
+        copy[copy.length - 2] = "openjpa.jdbc.JDBCListeners";
+        copy[copy.length - 1] = new JDBCListener[] { new Listener() };
+        super.setUp( copy );
+    }
+
+    /**
+     * Confirm that the specified SQL has been executed.
+     * 
+     * @param sqlExp
+     *            the SQL expression. E.g., "SELECT FOO .*"
+     */
+    public void assertSQL( String sqlExp )
+    {
+        for ( String statement : sql )
+        {
+            if ( statement.matches( sqlExp ) )
+                return;
+        }
+
+        Assert.fail( "Expected regular expression <" + sqlExp + "> to have" + " existed in SQL statements: " + sql );
+    }
+
+    /**
+     * Confirm that the specified SQL has not been executed.
+     * 
+     * @param sqlExp
+     *            the SQL expression. E.g., "SELECT BADCOLUMN .*"
+     */
+    public void assertNotSQL( String sqlExp )
+    {
+        boolean failed = false;
+
+        for ( String statement : sql )
+        {
+            if ( statement.matches( sqlExp ) )
+                failed = true;
+        }
+
+        if ( failed )
+            Assert.fail( "Regular expression <" + sqlExp + ">" + " should not have been executed in SQL statements: "
+                            + sql );
+    }
+
+    /**
+     * Confirm that the executed SQL String contains the specified sqlExp.
+     * 
+     * @param sqlExp
+     *            the SQL expression. E.g., "SELECT BADCOLUMN .*"
+     */
+    public void assertContainsSQL( String sqlExp )
+    {
+        for ( String statement : sql )
+        {
+            if ( statement.contains( sqlExp ) )
+                return;
+        }
+
+        Assert.fail( "Expected regular expression <" + sqlExp + "> to be" + " contained in SQL statements: " + sql );
+    }
+
+    /**
+     * Gets the number of SQL issued since last reset.
+     */
+    public int getSQLCount()
+    {
+        return sqlCount;
+    }
+
+    /**
+     * Resets SQL count.
+     * 
+     * @return number of SQL counted since last reset.
+     */
+    public int resetSQLCount()
+    {
+        int tmp = sqlCount;
+        sqlCount = 0;
+        return tmp;
+    }
+
+    public class Listener extends AbstractJDBCListener
+    {
+
+        @Override
+        public void beforeExecuteStatement( JDBCEvent event )
+        {
+            if ( event.getSQL() != null && sql != null )
+            {
+                sql.add( event.getSQL() );
+                sqlCount++;
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/openjpa/persistence/test/SingleEMFTestCase.java b/src/test/java/org/apache/openjpa/persistence/test/SingleEMFTestCase.java
new file mode 100644
index 0000000..a4f3e75
--- /dev/null
+++ b/src/test/java/org/apache/openjpa/persistence/test/SingleEMFTestCase.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.test;
+
+import org.apache.openjpa.jdbc.meta.ClassMapping;
+import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
+
+public abstract class SingleEMFTestCase extends PersistenceTestCase
+{
+
+    protected OpenJPAEntityManagerFactorySPI emf;
+
+    /**
+     * Call {@link #setUp(Object...)} with no arguments so that the emf set-up happens even if <code>setUp()</code> is
+     * not called from the subclass.
+     */
+    public void setUp() throws Exception
+    {
+        setUp( new Object[0] );
+    }
+
+    /**
+     * Initialize entity manager factory. Put {@link #CLEAR_TABLES} in this list to tell the test framework to delete
+     * all table contents before running the tests.
+     * 
+     * @param props
+     *            list of persistent types used in testing and/or configuration values in the form
+     *            key,value,key,value...
+     */
+    protected void setUp( Object... props )
+    {
+        emf = createEMF( props );
+    }
+
+    /**
+     * Closes the entity manager factory.
+     */
+    public void tearDown() throws Exception
+    {
+        super.tearDown();
+
+        if ( emf == null )
+            return;
+
+        try
+        {
+            clear( emf );
+        }
+        finally
+        {
+            closeEMF( emf );
+        }
+    }
+
+    protected ClassMapping getMapping( String name )
+    {
+        return (ClassMapping) emf.getConfiguration().getMetaDataRepositoryInstance().getMetaData(
+                                                                                                  name,
+                                                                                                  getClass().getClassLoader(),
+                                                                                                  true );
+    }
+}
diff --git a/src/test/java/org/apache/openjpa/persistence/test/SingleEMTestCase.java b/src/test/java/org/apache/openjpa/persistence/test/SingleEMTestCase.java
new file mode 100644
index 0000000..3144291
--- /dev/null
+++ b/src/test/java/org/apache/openjpa/persistence/test/SingleEMTestCase.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.test;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.persistence.EntityTransaction;
+
+import org.apache.openjpa.persistence.OpenJPAEntityManager;
+import org.apache.openjpa.persistence.OpenJPAQuery;
+
+/**
+ * A base test case that can be used to easily test scenarios where there is only a single EntityManager at any given
+ * time.
+ * 
+ * @author Marc Prud'hommeaux
+ */
+public abstract class SingleEMTestCase extends SingleEMFTestCase
+{
+
+    protected OpenJPAEntityManager em;
+
+    @Override
+    public void setUp()
+    {
+        setUp( new Object[0] );
+    }
+
+    @Override
+    public void setUp( Object... props )
+    {
+        super.setUp( props );
+        em = emf.createEntityManager();
+    }
+
+    /**
+     * Clear the current EntityManager and re-initialize it.
+     */
+    protected void reset()
+    {
+        close();
+        em = emf.createEntityManager();
+    }
+
+    @Override
+    public void tearDown() throws Exception
+    {
+        rollback();
+        close();
+        super.tearDown();
+    }
+
+    /**
+     * Start a new transaction if there isn't currently one active.
+     * 
+     * @return true if a transaction was started, false if one already existed
+     */
+    protected boolean begin()
+    {
+        EntityTransaction tx = em.getTransaction();
+        if ( tx.isActive() )
+            return false;
+
+        tx.begin();
+        return true;
+    }
+
+    /**
+     * Commit the current transaction, if it is active.
+     * 
+     * @return true if the transaction was committed
+     */
+    protected boolean commit()
+    {
+        EntityTransaction tx = em.getTransaction();
+        if ( !tx.isActive() )
+            return false;
+
+        tx.commit();
+        return true;
+    }
+
+    /**
+     * Rollback the current transaction, if it is active.
+     * 
+     * @return true if the transaction was rolled back
+     */
+    protected boolean rollback()
+    {
+        EntityTransaction tx = em.getTransaction();
+        if ( !tx.isActive() )
+            return false;
+
+        tx.rollback();
+        return true;
+    }
+
+    /**
+     * Closes the current EntityManager if it is open.
+     * 
+     * @return false if the EntityManager was already closed.
+     */
+    protected boolean close()
+    {
+        if ( em == null )
+            return false;
+
+        rollback();
+
+        if ( !em.isOpen() )
+            return false;
+
+        em.close();
+        return !em.isOpen();
+    }
+
+    /**
+     * Delete all of the instances.
+     * 
+     * If no transaction is running, then one will be started and committed. Otherwise, the operation will take place in
+     * the current transaction.
+     */
+    protected void remove( Object... obs )
+    {
+        boolean tx = begin();
+        for ( Object ob : obs )
+            em.remove( ob );
+        if ( tx )
+            commit();
+    }
+
+    /**
+     * Persist all of the instances.
+     * 
+     * If no transaction is running, then one will be started and committed. Otherwise, the operation will take place in
+     * the current transaction.
+     */
+    protected void persist( Object... obs )
+    {
+        boolean tx = begin();
+        for ( Object ob : obs )
+            em.persist( ob );
+        if ( tx )
+            commit();
+    }
+
+    /**
+     * Creates a query in the current EntityManager with the specified string.
+     */
+    protected OpenJPAQuery query( String str )
+    {
+        return em.createQuery( str );
+    }
+
+    /**
+     * Create a query against the specified class, which will be aliased as "x". For example, query(Person.class, "where
+     * x.age = 21") will create the query "select x from Person x where x.age = 21".
+     * 
+     * @param c
+     *            the class to query against
+     * @param str
+     *            the query suffix
+     * @param params
+     *            the parameters, if any
+     * @return the Query object
+     */
+    protected OpenJPAQuery query( Class c, String str, Object... params )
+    {
+        String query = "select x from " + entityName( emf, c ) + " x " + ( str == null ? "" : str );
+        OpenJPAQuery q = em.createQuery( query );
+        for ( int i = 0; params != null && i < params.length; i++ )
+            q.setParameter( i + 1, params[i] );
+        return q;
+    }
+
+    /**
+     * Returns a list of all instances of the specific class in the database.
+     * 
+     * @param c
+     *            the class to find
+     * @param q
+     *            the query string suffix to use
+     * @param params
+     *            the positional parameter list value
+     * 
+     * @see #query(java.lang.Class,java.lang.String)
+     */
+    protected <E> List<E> find( Class<E> c, String q, Object... params )
+    {
+        return Collections.checkedList( query( c, q, params ).getResultList(), c );
+    }
+
+    /**
+     * Returns a list of all instances of the specific class in the database.
+     */
+    protected <E> List<E> find( Class<E> c )
+    {
+        return find( c, null );
+    }
+}
diff --git a/src/test/resources/META-INF/org.apache.openjpa.lib.conf.ProductDerivation b/src/test/resources/META-INF/org.apache.openjpa.lib.conf.ProductDerivation
new file mode 100644
index 0000000..f958c6d
--- /dev/null
+++ b/src/test/resources/META-INF/org.apache.openjpa.lib.conf.ProductDerivation
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+org.apache.openjpa.persistence.jdbc.JDBCPersistenceProductDerivation
diff --git a/src/test/resources/META-INF/persistence.xml b/src/test/resources/META-INF/persistence.xml
new file mode 100644
index 0000000..803f3e4
--- /dev/null
+++ b/src/test/resources/META-INF/persistence.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
+  <persistence-unit name="continuum-store">    
+    <!-- provider>org.hibernate.ejb.HibernatePersistence</provider -->
+    <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
+    
+    <!-- Mapped superclasses -->
+    <class>org.apache.continuum.model.CommonPersistableEntity</class>
+    <class>org.apache.continuum.model.CommonCreatedEntity</class>
+    <class>org.apache.continuum.model.CommonUpdatableEntity</class>
+
+    <!-- Entities -->    
+    <class>org.apache.continuum.model.project.BuildDefinition</class>
+    <class>org.apache.continuum.model.project.BuildDefinitionTemplate</class>
+    <class>org.apache.continuum.model.project.BuildResult</class>
+    <class>org.apache.continuum.model.project.Project</class>
+    <class>org.apache.continuum.model.project.ProjectDependency</class>
+    <class>org.apache.continuum.model.project.ProjectDeveloper</class>
+    <class>org.apache.continuum.model.project.ProjectGroup</class>
+    <class>org.apache.continuum.model.project.ProjectNotifier</class>
+    <class>org.apache.continuum.model.project.Schedule</class>
+    
+    <class>org.apache.continuum.model.scm.ChangeFile</class>
+    <class>org.apache.continuum.model.scm.ChangeSet</class>
+    <class>org.apache.continuum.model.scm.ScmResult</class>
+    <class>org.apache.continuum.model.scm.SuiteResult</class>
+    <class>org.apache.continuum.model.scm.TestCaseFailure</class>
+    <class>org.apache.continuum.model.scm.TestResult</class>
+    
+    <class>org.apache.continuum.model.system.Installation</class>
+    <class>org.apache.continuum.model.system.NotificationAddress</class>
+    <class>org.apache.continuum.model.system.Profile</class>
+    <class>org.apache.continuum.model.system.SystemConfiguration</class>
+    
+    <properties>
+      <property name="openjpa.jdbc.DBDictionary" value="hsql" />
+      <property name="openjpa.ConnectionURL" value="jdbc:hsqldb:mem:continuum_jpa;create=true,autoCommit=true"/>
+      <property name="openjpa.ConnectionDriverName" value="org.hsqldb.jdbcDriver"/>
+      <property name="openjpa.ConnectionUserName" value="sa"/>
+      <property name="openjpa.ConnectionPassword" value=""/>
+      <!--  Configure OpenJPA to automatically run the mapping tool at runtime and create schema on Unit Test setup -->
+      <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)"/> 
+      
+      <!-- Enable SQL logging in OpenJPA 
+      <property name="openjpa.Log" value="DefaultLevel=INFO,SQL=TRACE" />
+      -->
+      
+      <!-- Change default log level across OpenJPA --> 
+      <property name="openjpa.Log" value="DefaultLevel=WARN"/>
+      
+    </properties>
+    
+  </persistence-unit>
+</persistence>
\ No newline at end of file
diff --git a/src/test/resources/META-INF/spring-config.xml b/src/test/resources/META-INF/spring-config.xml
new file mode 100644
index 0000000..8e52903
--- /dev/null
+++ b/src/test/resources/META-INF/spring-config.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xmlns:tx="http://www.springframework.org/schema/tx"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+				http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
+				http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
+
+
+  <bean id="entityManagerFactory"
+    class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
+    <property name="persistenceUnitName" value="continuum-store" />
+  </bean>
+
+  <!-- Setup the transaction manager -->
+  <bean id="transactionManager"
+    class="org.springframework.orm.jpa.JpaTransactionManager">
+    <property name="entityManagerFactory" ref="entityManagerFactory" />
+  </bean>
+  <bean id="transactionInterceptor"
+    class="org.springframework.transaction.interceptor.TransactionInterceptor">
+    <property name="transactionManager" ref="transactionManager" />
+    <property name="transactionAttributeSource">
+      <bean
+        class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource" />
+    </property>
+  </bean>
+  
+  <!-- enable the configuration of transactional behavior based on annotations -->
+  <tx:annotation-driven transaction-manager="transactionManager" />
+
+  <bean id="storeFactory"
+    class="org.apache.continuum.store.jpa.JpaStoreFactory">
+  </bean>
+
+  <bean id="projectStore"
+    class="org.apache.continuum.store.jpa.JpaStore"
+    factory-bean="storeFactory"
+    factory-method="createProjectStoreInstance">
+    <property name="entityManagerFactory" ref="entityManagerFactory" />
+  </bean>
+
+  <bean id="projectGroupStore"
+    class="org.apache.continuum.store.jpa.JpaStore"
+    factory-bean="storeFactory"
+    factory-method="createProjectGroupStoreInstance">
+    <property name="entityManagerFactory" ref="entityManagerFactory" />
+  </bean>
+
+  <bean id="projectNotifierStore"
+    class="org.apache.continuum.store.jpa.JpaStore"
+    factory-bean="storeFactory"
+    factory-method="createProjectNotifierStoreInstance">
+    <property name="entityManagerFactory" ref="entityManagerFactory" />
+  </bean>
+
+
+  <!-- DAO -->
+
+  <bean id="daoFactory"
+    class="org.apache.continuum.dao.impl.DaoFactoryImpl">
+  </bean>
+
+  <bean id="projectGroupDao"
+    class="org.apache.continuum.dao.impl.GenericDaoJpa"
+    factory-bean="daoFactory"
+    factory-method="createProjectGroupDao">
+    <property name="entityManagerFactory" ref="entityManagerFactory" />
+  </bean>
+
+  <bean id="projectDao"
+    class="org.apache.continuum.dao.impl.GenericDaoJpa"
+    factory-bean="daoFactory"
+    factory-method="createProjectDao">
+    <property name="entityManagerFactory" ref="entityManagerFactory" />
+  </bean>
+
+  <bean id="notifierDao"
+    class="org.apache.continuum.dao.impl.GenericDaoJpa"
+    factory-bean="daoFactory"
+    factory-method="createNotifierDao">
+    <property name="entityManagerFactory" ref="entityManagerFactory" />
+  </bean>
+
+  <!-- SERVICES -->
+
+  <bean id="projectGroupService"
+    class="org.apache.continuum.service.impl.ProjectGroupServiceImpl">
+    <property name="projectGroupDao" ref="projectGroupDao"/>
+    <property name="projectDao" ref="projectDao"/>
+  </bean>
+
+  <bean id="projectService"
+    class="org.apache.continuum.service.impl.ProjectServiceImpl">
+    <property name="projectDao" ref="projectGroupDao"/>
+    <property name="notifierDao" ref="notifierDao"/>
+  </bean>
+
+</beans>
\ No newline at end of file
diff --git a/src/test/resources/sql/project-group-table-data.sql b/src/test/resources/sql/project-group-table-data.sql
new file mode 100644
index 0000000..db4f53d
--- /dev/null
+++ b/src/test/resources/sql/project-group-table-data.sql
@@ -0,0 +1,5 @@
+# Test SQL to populate data here

+

+

+INSERT INTO PROJECT_GROUP ( ID, DATE_CREATED, DESCRIPTION, GROUP_ID, NAME)

+	VALUES ( 100, 2007-11-10, 'Group for Continuum sub-projects', 'org.apache.continuum', 'Continuum Projects')

diff --git a/src/test/resources/sql/project-notifier-table-data.sql b/src/test/resources/sql/project-notifier-table-data.sql
new file mode 100644
index 0000000..3d92286
--- /dev/null
+++ b/src/test/resources/sql/project-notifier-table-data.sql
@@ -0,0 +1,5 @@
+# Test data for Project Notifier tests

+

+INSERT INTO PROJECT_NOTIFIER 

+  (ID, DATE_CREATED, DATE_UPDATED, FLG_ENABLED, NOTIFIER_ORIGIN, RECIPIENT_TYPE, FLG_SEND_ON_ERROR,FLG_SEND_ON_FAILURE, FLG_SEND_ON_SUCCESS, FLG_SEND_ON_WARNING)

+  VALUES (100, 2007-11-01, 2007-11-10, 1, 1, 1 , 1, 0, 0, 0 )
\ No newline at end of file
diff --git a/src/test/resources/sql/project-table-data.sql b/src/test/resources/sql/project-table-data.sql
new file mode 100644
index 0000000..ec7a5ea
--- /dev/null
+++ b/src/test/resources/sql/project-table-data.sql
@@ -0,0 +1,7 @@
+# Test SQL to populate data here

+

+INSERT INTO PROJECT (ID, DATE_CREATED, DATE_UPDATED, ARTIFACT_ID, GROUP_ID, DESCRIPTION, NAME, FLG_SCM_USE_CACHE)

+  VALUES (100, 2007-11-01, 2007-11-10, 'continuum-jpa-model', 'org.apache.continuum', 'Model for Continuum using JPA', 'continuum-jpa-model', false);

+

+INSERT INTO PROJECT (ID, DATE_CREATED, DATE_UPDATED, ARTIFACT_ID, GROUP_ID, DESCRIPTION, NAME, FLG_SCM_USE_CACHE)

+  VALUES (101, 2007-11-02, 2007-11-11, 'continuum-jdo-model', 'org.apache.continuum', 'Legacy Model for Continuum using JDO', 'continuum-jdo-model', true);
\ No newline at end of file