Creating Services in Kuali Student - Final 01-31-11-1

advertisement
Developing Services in Kuali Student
February 2011
Developing Services in Kuali Student
1
2
Developing Services in Kuali Student
Contents
1
Introduction .......................................................................................................................................... 5
1.1
2
Understanding the service layer ........................................................................................................... 6
2.1
3
4
5
6
7
Requirements ................................................................................................................................ 5
Project Structure ........................................................................................................................... 7
Generating the Service API ................................................................................................................... 8
3.1
Checkout the ks-core-tutorial Project ........................................................................................... 8
3.2
Create the Service API ................................................................................................................... 9
3.3
Code Cleanup .............................................................................................................................. 10
Create JPA entities corresponding to API ........................................................................................... 13
4.1
Create initial “root” entity based on DTO ................................................................................... 14
4.2
Some JPA Quirks ......................................................................................................................... 20
4.3
JPA Entities Review ..................................................................................................................... 21
DAO Layer ........................................................................................................................................... 22
5.1
Create DAO interface .................................................................................................................. 22
5.2
Create DAO Tests ........................................................................................................................ 22
5.3
Named Queries ........................................................................................................................... 24
5.4
DAO Review ................................................................................................................................ 25
Service Implementation ...................................................................................................................... 26
6.1
Service Tests................................................................................................................................ 27
6.2
Service Implementation .............................................................................................................. 28
6.3
Exceptions ................................................................................................................................... 29
6.4
Extra Service Parameters ............................................................................................................ 29
6.5
Validation .................................................................................................................................... 30
6.6
Service Assemblers ..................................................................................................................... 30
6.7
Test Data ..................................................................................................................................... 32
6.8
Remaining Crud Operations ........................................................................................................ 34
6.9
Finishing Touches: Wiring in Dictionary, Validation and Search................................................. 37
6.10
Final steps ................................................................................................................................... 38
6.11
Service Implementation Review ................................................................................................. 43
Setting up Service Database ............................................................................................................... 45
Developing Services in Kuali Student
3
7.1
Schema Generation..................................................................................................................... 45
7.2
Export .......................................................................................................................................... 46
7.3
Adding Indexes to Foreign Keys .................................................................................................. 46
7.4
Generate SQL .............................................................................................................................. 47
7.5
Copy SQL Files to Your Project .................................................................................................... 47
7.6
Service Database Review ............................................................................................................ 47
8
Further reading / links to resources.................................................................................................... 48
4
Developing Services in Kuali Student
1 Introduction
Kuali Student is comprised of several components including user interface, application and service
layers. This document explains the service layer and how to create Java code for the API, service and
persistence layers.
1.1 Requirements
Before you start this tutorial, please make sure you have a solid understanding of the following concepts
and technologies:
 Java
 RDB/SQL
 Spring
 Maven
In addition, please follow the corresponding KS Developer Guide
(https://wiki.kuali.org/display/KULSTG/Kuali+Student+System+Developer+Guide) to ensure that your
environment is setup correctly.
 All software and tools installed (Java 6 JDK, Eclipse and plugins, Oracle XE, Maven, Subversion)
 KS projects already checked out and can run tests (ks-common, ks-core,ks-lum)
Developing Services in Kuali Student
5
2 Understanding the service layer
The service layer is designed like any typical 3-tier application. It consists of multiple layers: a clientfacing layer that uses SOAP, a service layer that handles the business logic and a persistence/DAO layer
that interfaces with a database.
SOAP
SOAP stands for Simple Object Access Protocol. It is an XML protocol specification for exchanging
structured information in the implementation of Web Services. We use the JAX-WS and JAXB APIs in
conjunction with the CXF web service framework to handle the SOAP layer.
Service DTOs
These are plain old java objects (POJOs) that represent the data the service uses. They are annotated
with JAXB annotations so they can be marshaled and unmarshaled into XML.
Service Interface and Service Implementation
These are responsible for business logic, data validation, and transforming data to and from the
persistence layer.
6
Developing Services in Kuali Student
JPA Entities
These are JPA annotated POJOs that represent the data stored in a database. We use JPA as an object
relational mapping technology to map from the POJOs to the database.
DAOs
These are responsible for CRUD operations (create, update, delete) as well as fetching and searching for
data in the database.
2.1 Project Structure
KS uses a standard directory layout for each service that is developed. The following example shows the
KS Academic Time Period (ATP) service:
ks-core
Project
ks-core-api/src/main/java
org.kuali.student.core.atp.dto
org.kuali.student.core.atp.service
ks-core-api/src/main/resources
META-INF
wsdl
ks-core-impl/src/main/java
org.kuali.student.core.atp.dao
org.kuali.student.core.atp.dao.impl
org.kuali.student.core.atp.entity
org.kuali.student.core.atp.service.impl
ks-core-impl/src/main/resources
ksb
META-INF
ks-core-impl/src/test/java
org.kuali.student.core.atp.dao
org.kuali.student.core.atp.service.impl
ks-core-impl/src/test/resources
Developing Services in Kuali Student
DTOs (atpInfo POJOs)
Service API interface
Service WSDL
DAO interface
DAO implementation
JPA Entitiy POJOs
Service implementation
Spring Context
JPA persistence.xml configuration
DAO Implementation tests
Service Implementation tests
Test data and configuration
7
3 Generating the Service API
The service API represents the java service interface, the DTO POJOs, and the SOAP Web Service
Definition Language(WSDL). In Kuali student, the service team will make a wiki page that defines the
service. We use a tool to read that definition and create the Java DTOs and service interfaces.
3.1 Checkout the ks-core-tutorial Project
This will be the template project we will use for our tutorial.
Check out from svn:
https://test.kuali.org/svn/student/examples/ks-core-tutorial/trunk
8
Developing Services in Kuali Student
3.2 Create the Service API
1. Download the ks-contract plugin:
Check out via subversion and install into your local repo:
https://test.kuali.org/svn/student/tools/maven-kscontract-plugin
2. Edit the pom of ks-core-api:
Go to the URL: https://wiki.kuali.org/display/KULSTU/Academic+Time+Period+Service in your
browser to obtain a valid jsession id if needed
(In firefox go to Tools->Options->Privacy and click Remove individual cookies, then type
wiki.kuali.org in the URL bar. Cut and paste the jsession Id into the test-kscontract pom)
Make sure these are set in the pom as well:
<packageName>org.kuali.student.core.atp</packageName>
<serviceName>AtpService</serviceName>
<namespace>http://student.kuali.org/wsdl/atp</namespace>
<contractURL>https://wiki.kuali.org/display/KULSTU/Academic+Time+Period
+Service</contractURL>
These settings are used to generate the java code and all 4 parameters should be changed to
match the service you are generating.
3. Run “mvn generate-sources”. This process will generate your Java interface, and all needed DTOs.
4. Once you have your service Java files, delete the contract config in your pom, or disable it by setting
the phase to “none”
Developing Services in Kuali Student
9
3.3 Code Cleanup
3.3.1 Extending TypeInfo
Since KS has so many type structures that are very similar, there is a generic TypeInfo class you can
extend to make life easier. Open AtpSeasonalType and change it to extend TypeInfo.
Since TypeInfo contains the exact same structure as AtpSeasonalType, we can delete the majority of the
code in AtpSeasonalType:
@XmlAccessorType(XmlAccessType.FIELD)
public class AtpSeasonalTypeInfo extends TypeInfo {
private static final long serialVersionUID = 1L;
}
Some DTO types have additional fields. For example, the only difference between AtpTypeInfo and the
generic TypeInfo is the two fields named seasonalType and durationType.
Change AtpTypeInfo to extend TypeInfo and remove all the code except for seasonalType and
durationType fields and accessor methods. Your code should look like this:
@XmlAccessorType(XmlAccessType.FIELD)
public class AtpTypeInfo extends TypeInfo implements Serializable, Idable,
HasAttributes {
private static final long serialVersionUID = 1L;
@XmlElement
private String durationType;
@XmlElement
private String seasonalType;
/**
* Unique identifier for an academic time period duration type.
*/
public String getDurationType() {
return durationType;
}
public void setDurationType(String durationType) {
this.durationType = durationType;
}
/**
* Unique identifier for an academic time period seasonal type.
*/
public String getSeasonalType() {
return seasonalType;
}
public void setSeasonalType(String seasonalType) {
this.seasonalType = seasonalType;
}
}
10
Developing Services in Kuali Student
Make similar changes to the remaining typeInfos (DateRangeTypeInfo and MilestoneTypeInfo).
3.3.2 Extending Search and Dictionary
Make sure your Service extends SearchService and DictionaryService. These might not be on the wiki,
but are needed. Ask the service team if you have any questions.
public interface AtpService extends SearchService, DictionaryService {
3.3.3 Final Steps
You may want to organize your imports (Ctrl-shift-O) to help clean up your code. Make sure that all your
code compiles and nothing is missing or looks out of the ordinary as the contract plugin may have
missed something.
It is good to keep in mind that this project uses many different technologies and none of them
are perfect. Many times you will follow directions and things will not work as expected.
Developers are encouraged to research solutions to any issues and update our
documentation/wiki/code so that others will benefit.
3.3.4 WSDL Generation
At this point, you can generate your WSDL. The WSDL is an XML file that defines the SOAP contract.
There is a CXF WSDL generator plugin for Maven that will generate our WSDL.
1. Open the pom.xml for the ks-core-api module. Look for the build plugin definition for cxf-java2wsplugin. Here you will add an additional execution for the service you are developing.
<execution>
<id>atp-wsdl</id>
<phase>${ks.java2ws.phase}</phase>
<configuration>
<className>org.kuali.student.core.atp.service.AtpService</className>
<serviceName>AtpService</serviceName>
<targetNameSpace>http://student.kuali.org/wsdl/atp</targetNameSpace>
</configuration>
<goals>
<goal>java2ws</goal>
</goals>
</execution>
2. Edit the serviceName, targetNameSpace, and className to match the similar values you used for
creating the service using the contract plugin. Also make sure you have a unique Id for the
execution.
3. Run the following on your ks-core-api pom:
ks-core\ks-core-api>mvn -Dks.java2ws.phase=process-classes clean
process-classes
if you look in your project/ks-core-api/target/generated/wsdl, you will see your AtpService.wsdl
Developing Services in Kuali Student
11
4. Copy this file to ks-core-api/src/main/resources/META-INF/wsdl
Be sure to regenerate the WSDL if you make any further changes to the API so it remains in sync.
3.3.5 Service API Review
We have just created everything we need for our service interface layer. This will allow developers to
start coding against the service without writing any implementation code.
 The Service Team created a wiki page that defined our service contract.
 We ran the ks-contract-plugin to generate our java code.
 We extended TypeInfo in our Type DTOs to reduce code size and reusability.
 We made sure that SearchService and DictionaryService were extended if needed
 We double checked that the code was sound and copied it into our project structure.
 We added configuration to our api pom and generated the wsdl with the cxf-java2ws-plugin.
12
Developing Services in Kuali Student
4 Create JPA entities corresponding to API
According to Wikipedia, “The Java Persistence API, sometimes referred to as JPA, is a Java programming
language framework managing relational data in applications using Java.”
We use JPA annotated POJOs to define a mapping between Java objects and the database.
Our JPA provider is Hibernate, although in theory we should be able to use any JPA provider such as
Eclipselink or OpenJPA.
Developing Services in Kuali Student
13
4.1 Create initial “root” entity based on DTO
Before creating entities, it is a good idea to become familiar with the entity diagram for the service and
how the DTOs relate to one another.
Let’s start by creating our first entity that corresponds to the AtpInfo object. We are now in
implementation territory, so all ATP entities will go in the ks-core-impl module.
1. Create a new Class called “Atp.java” with a package of “org.kuali.student.core.atp.entity” and a
superclass of “org.kuali.student.core.entity.MetaEntity”.
All of our KS entity classes should extend “org.kuali.student.core.entity.BaseEntity”. MetaEntity
extends BaseEntity and is used for any classes that have metaInfo(create and update time/date/user
information)
BaseEntity takes care of primary key ids and optimistic locking. As an added benefit we can easily
extend all our entity classes if they all share the same super class.
2. All Annotated JPA entities need to be marked with @Entity at the class level. In addition, we need
to define the table name that corresponds to this class by adding a @Table(name =
"KSAP_ATP")annotation with the table name.
IMPORTANT: Please use our naming conventions for all database column and table names.
3. Let’s also copy all of the fields from AtpInfo into our Atp entity class which we will edit later. Delete
any @Xml annotations from the fields.
At this point, your Atp class should look like this:
@Entity
@Table(name = "KSAP_ATP")
public class Atp extends MetaEntity{
private String name;
private RichTextInfo desc;
private Date startDate;
private Date endDate;
private Map<String, String> attributes;
private MetaInfo metaInfo;
private String type;
private String state;
private String id;
}
14
Developing Services in Kuali Student
4. Let’s remove some fields that our superclass is already taking care of. Delete the lines for “id” and
“metaInfo”
5. Now let’s make sure that none of the field names are JPQL/SQL reserved words which can cause
weird bugs later on. Change the field “desc” to “descr”
Now we can start annotating our simple types (anything like String, int, long, Boolean)
IMPORTANT: The KS convention is to add column names to every field so that we have control over
the column names in the database.
6. Annotate the name and state fields with
@Column(name = "NAME")
@Column(name = "STATE")
7. For the start and end dates, we need to add additional annotations to tell the DB how we want our
temporal fields to be stored (DateStamp,Timestamp,etc)
Add The following to your startDate and endDate fields(with the correct corresponding column
names):
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "START_DT")
4.1.1 Dynamic attributes
Many of the KS DTOs have dynamic attributes which are really a simple Map of string key to string value.
To take advantage of some utility code, let’s add an interface to our class:
implements AttributeOwner<AtpAttribute>
Let’s also replace the code for the attributes field with the following:
@OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
private List<AtpAttribute> attributes;
JPA does not support maps, so we need to convert our maps to a list of string-value pairs. Since many
DTOs in KS have attributes, there are helper classes and interfaces. The @OneToMany annotation tells
the JPA provider how to do joins between this class and the AtpAttribute class.
Great! We only have two more fields to work with for now, descr and type.
4.1.2 Types
Like attributes and meta, most KS entities have Types. In the database they exist as strings with some
attributes of their own, but are mainly used by their ids as constraints on data. Types must be
constrained to a set of values (you can just make up a new type, it has to be a value in the proper type
table). Since we are using ORM, you can’t just set the string key of the type to a string value in the
Developing Services in Kuali Student
15
referencing object—we need to set a specific “Type Object” value in the referencing Object. Let’s
change the type definition to this:
@ManyToOne
@JoinColumn(name = "TYPE")
private AtpType type;
This constrains the type to an AtpType.
4.1.3 RichTextInfo
RichTextInfo is another frequently used data type. We have a corresponding entity called RichText for
that which can be extended each time you want a different RichText table. Currently we are making
RichText tables per service, but you could do it per reference as well.
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "RT_DESCR_ID")
private AtpRichText descr;
This defines a relationship to an Atp-specific RichText table called AtpRichText.
4.1.4 Final Steps
The Code should now look like this:
@Entity
@Table(name = "KSAP_ATP")
public class Atp extends MetaEntity implements AttributeOwner<AtpAttribute> {
@Column(name = "NAME")
private String name;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "RT_DESCR_ID")
private AtpRichText descr;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "START_DT")
private Date startDate;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "END_DT")
private Date endDate;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
private List<AtpAttribute> attributes;
@ManyToOne
@JoinColumn(name = "TYPE")
private AtpType type;
@Column(name = "STATE")
private String state;
}
16
Developing Services in Kuali Student
We are done for now with our annotations and changes. Let’s generate our public getters and setters so
we have a real Pojo.
4.1.5 Creating Missing Classes
With our changes, we are now seeing some compilation errors due to use referencing classes that don’t
yet exist:
AtpAttribute
AtpRichText
AtpType
In Eclipse you can right-click to create missing classes. Do this for the three missing classes as described
in the sections below.
4.1.6
Attributes
1. Open up AtpAttribute. Let’s start with our JPA class-level annotations. Remember that each entity
class needs an @Entity annotation. Also for KS we are explicitly defining table names using our
standards.
@Entity
@Table(name = "KSAP_ATP_ATTR")
2. To make our new AtpAttribute class work with some of our utility classes, we will extend the
Attribute class and give it the generic type Atp :
public class AtpAttribute extends Attribute<Atp> {
3. You will now see that we are missing some method implementations. Let’s add some basic
boilerplate code to complete the class.
@ManyToOne
@JoinColumn(name = "OWNER")
private Atp owner;
@Override
public Atp getOwner() {
return owner;
}
@Override
public void setOwner(Atp owner) {
this.owner = owner;
}
This adds an Atp field called Owner which maps back to an Atp. This is an example of bi-directional
mapping. The @ManyToOne maps many AtpAttributes to one Atp.It also defines the column name
Developing Services in Kuali Student
17
in the KSAP_ATP_ATTR table that will hold the id of the owning Atp. If you look at the Atp mapping
for attributes, you will see the reverse of the mapping:
@OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
This maps a collection of AtpAttributes to a single Atp. It also uses the mappedBy attribute to define
the field representing the owning entity on the non-owning side of the bidirectional relationship. In
this case, the AtpAttribute “owns” the relationship since the AtpAttribute table contains the foreign
key to the Atp. (The field name ‘owner’ is a little misleading in this case, as the Attribute is really the
JPA owner)
By default, relationships in JPA are unidirectional. If we want each side of the relationship to be
aware of the other, we can make our relationship bi-directional. In bi-directional relationships we
must set both sides of the relationship in out objects, for example:
Atp atp = new Atp();
AtpAttribute attribute = new AtpAttribute();
atp.getAttributes().add(attribute);
attribute.setOwner(atp);
Ok! We’re finished mapping our first relationship. Let’s move on to the other missing classes.
4.1.7 RichText
This is an easy one. Add our standard annotations of @Entity and @Table with a name attribute. Then
all we have to do is extend the RichText class:
@Entity
@Table(name = "KSAP_RICH_TEXT_T")
public class AtpRichText extends RichText {
}
If you look at the RichText class, you’ll see some new JPA annotations:
@MappedSuperclass
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
This allows all subclasses to inherit the mappings from the superclass, and a new table will be created
for each subclass. All we need is a new class that extends RichText, and we have a new RichText table.
4.1.8 Standard Types
Think back to when we created the API DTOs for AtpTypes. We had a TypeInfo superclass that made life
a little easier for us. There is a Type entity on the JPA side that corresponds to the TypeInfo class.
1. Let’s start on the AtpSeasonalType class. Create a new class called AtpSeasonalType. Let’s add
@Entity and @Table annotations to it and extend Type<AtpSeasonalTypeAttribute>. This is similar
to how we implemented the AttributeOwner interface.
18
Developing Services in Kuali Student
At this point it might get a little overwhelming with all the classes we need. Our base entity Atp
needed attributes, so we had to make an AtpAttribute class. Then we needed an AtpSeasonalType
entity class which, in turn, needed another Attribute class. The service architects have determined
that all types have dynamic attributes, so we will need to make these classes.
2. Edit your AtpSeasonalType to look like this:
@Entity
@Table(name = "KSAP_ATP_SEASONAL_TYPE")
public class AtpSeasonalType extends Type<AtpSeasonalTypeAttribute>{
@OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
private List<AtpSeasonalTypeAttribute> attributes;
public List<AtpSeasonalTypeAttribute> getAttributes() {
return attributes;
}
public void setAttributes(List<AtpSeasonalTypeAttribute> attributes) {
this.attributes = attributes;
}
}
3. Now we need to make an AtpSeasonalTypeAttribute:
@Entity
@Table(name = "KSAP_ATP_SEASONAL_TYPE_ATTR")
public class AtpSeasonalTypeAttribute extends Attribute<AtpSeasonalType> {
@ManyToOne
@JoinColumn(name = "OWNER")
private AtpSeasonalType owner;
@Override
public AtpSeasonalType getOwner() {
return owner;
}
@Override
public void setOwner(AtpSeasonalType owner) {
this.owner = owner;
}
}
AtpSeasonalTypeAttribute and AtpAttribute are nearly identical. You can cut and paste the
boilerplate code—just make sure you set a proper table name and change the GenericType.
4. Create AtpDurationType and AtpDurationTypeAttribute in the same manner.
4.1.9 Non Standard Types
When we created AtpTypeInfo, we extended TypeInfo and added two additional fields for durationType
and seasonalType. We need to reflect that data in the persistence entities. We will accomplish this by
Developing Services in Kuali Student
19
extending Type (and creating a new AtpTypeAttribute class) and then adding two additional fields that
have unidirectional relationships to the AtpDurationType and AtpSeasonalType classes.
Let’s make AtpType just like we made AtpSeasonalType and AtpDurationType. Now let’s add the two
additional fields that are in our DTOs and map them:
@ManyToOne
@JoinColumn(name = "SEASONAL_TYPE")
private AtpSeasonalType seasonalType;
@ManyToOne
@JoinColumn(name = "DUR_TYPE")
private AtpDurationType durationType;
This is a ManyToOne unidirectional mapping. The @JoinColumn annotations define the column names
on the KSAP_ATP_TYPE table that holds the foreign keys to seasonal and duration types. Add your
getters and setters and we’re done.
4.1.10 Persistence.xml
JPA requires a configuration file that defines where your entities are.
5. Create a new file called atp-persistence.xml in ks-core-impl/src/main/resources/META-INF
<persistence version="1.0"
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="Atp" transaction-type="JTA">
<class>org.kuali.student.core.entity.Type</class>
<class>org.kuali.student.core.entity.Attribute</class>
<class>org.kuali.student.core.entity.Meta</class>
<class>org.kuali.student.core.entity.MetaEntity</class>
<class>org.kuali.student.core.entity.RichText</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
</persistence-unit>
</persistence>
6. Set the Persistence Unit Name to “Atp” which corresponds to our service.
This groups the entities into a logical unit. The transaction-type sets whether or not this is a JTA
supported persistence unit. The classes listed are the helper entities that we extended from.
JPA providers can scan the classpath for JPA annotated classes, but this is time consuming and can
cause problems if you have multiple persistence units. We use the exclude-unlisted-classes
to explicitly list which classes are needed in out persistence unit. We will need to add the fully
qualified class names of every class in our org.kuali.student.core.atp.entity package. (You can select
the classes in the project explorer and cut and paste to make sure you don’t miss anything)
4.2 Some JPA Quirks
20
Developing Services in Kuali Student
The following are some quirks of JPA and suggestions for how to mitigate them.
4.2.1 Mapping many-to-one primitives:
There is no easy way in standard JPA 1.0 to map Collection<String> in an entity. You will have to make a
new Entity that represents the String to map to.
4.2.2 Collection Initialization:
In our DTOs, you will see in the getters:
public List<AdminOrgInfo> getAdminOrgs() {
if (adminOrgs == null) {
adminOrgs = new ArrayList<AdminOrgInfo>();
}
return adminOrgs;
}
This is fine and is needed for our xml binding to work in our DTO Info classes, but DO NOT use in Entities
as it creates a variety of problems.
4.2.3 Naming issues and standards (sql conflicts)
Please make sure you change the names of any fields that are reserved SQL/JPQL words. Use our Kuali
Student naming standards for all table and column names.
4.3 JPA Entities Review
We have just created our persistence Entities. This will allow developers to start work on the DAO layer,
and start creating any test data that is needed.




We created JPA annotated Entites corresponding to our DTOs
We created attributes and types (and type attributes)
We added relationship mapping between our entities
We created a persistence.xml file that defines our persistence unit
Developing Services in Kuali Student
21
5 DAO Layer
The Data Access Object layer represents a layer between the services and the database. In Kuali Student,
that makes it responsible for CRUD operations on JPA Entities, named query lookups, and searches.
We will create a DAO interface, a JUnit test class, and the DAO implementation which will include JPQL
named queries.
5.1 Create DAO interface
The Dao Interface is pretty easy to make. Create an AtpDao interface and have it extend CrudDao and
SearchableDao:
public interface AtpDao extends CrudDao, SearchableDao {
We will add more method signatures to this interface later as we need them.
5.2 Create DAO Tests
KS tries to maintain test-driven development. This means that we try to write our tests before we write
our implementation code.
22
Developing Services in Kuali Student
KS has a test framework for our DAOs we will use to validate our entities and named queries.
1. Create a new Java class called org.kuali.student.core.atp.TestAtpDaoImpl:
@PersistenceFileLocation("classpath:META-INF/atp-persistence.xml")
public class TestAtpDaoImpl extends AbstractTransactionalDaoTest {
@Dao(value = "org.kuali.student.core.atp.dao.impl.AtpDaoImpl")
public AtpDao dao;
@Test
public void testCreateType() {
}
}
@PersistenceFileLocation is an annotation that defines where our persistence.xml file is
located.
AbstractTransactionalDaoTest is our JUnit test framework class that takes care of all the Hibernate
wiring, temporary data sources and transactions.
The @Dao annotation injects an instance of the Dao Implementation into the dao field of our test
class. It also is used to configure test data file locations which we will use later.
2. Let’s make our initial DAO implementation so we can run our test.
In ks-core-impl/src/main/java, create a new class called
org.kuali.student.core.atp.dao.impl.AtpDaoImpl and have it extend AbstractSearchableCrudDaoImpl
and implement AtpDao. Add the following code to tie in the persistence unit to the entitymanager:
public class AtpDaoImpl extends AbstractSearchableCrudDaoImpl
implements AtpDao {
@PersistenceContext(unitName = "Atp")
@Override
public void setEm(EntityManager em) {
super.setEm(em);
}
}
AbstractSearchableCrudDaoImpl is a DAO implementation that takes care of some basic CRUD
operations and also our search framework.
3. Now that we have our Dao implementation we can run our test. Run the TestAtpDaoImpl as a JUnit
test. If you are getting class not found errors, you might need to re-sync your environment with
“mvn test-compile” first.
Each DAO test is run in its own transaction that is rolled back at the end of the test, so any
persistence changes in one test will not affect another test.
Developing Services in Kuali Student
23
4. Our test is a little empty, so let’s add a quick check to see that we can persist and fetch a new
AtpTpe:
@Test
public void testCreateType() throws DoesNotExistException {
AtpType atpType= new AtpType();
atpType.setId("atp.type.1");
atpType.setName("Test Atp Type 1");
atpType = dao.create(atpType);
AtpType foundAtpType = dao.fetch(AtpType.class, "atp.type.1");
assertEquals(atpType.getName(),foundAtpType.getName());
}
This is typical of the kinds of tests we could run. The majority of our DAO tests are duplicated in the
Service Tests, so it is up to the developer to decide how robust the DAO tests should be.
You can see that the AtpDao already has CRUD operations (like create and fetch) in it that are
inherited from the AbstractSearchableCrudDaoImpl.
5.3 Named Queries
In the AtpService interface, there are many calls named get…(). The getAtp(String id) method can be
implemented with a simple dao.fetch(AtpType.class, id);
If we wanted to implement the getAtpsByAtpType(String atpTypeKey), we would need to create a
database query that selects all Atps with a specific type. We will do this by writing a custom named
query in JPQL, which is like SQL but works against the objects and not the tables. For the most part, the
only other methods that needs to go into DAO implementations are calls to named queries.
We define named queries as annotations on entity classes (usually the entity with the bulk of the
returned columns). These usually correspond to a specific service “get” operations that return
something other than standard dao fetch calls.
1. Add the following annotation code in Atp:
@NamedQueries( {
@NamedQuery(name = "Atp.findAtpsByAtpType", query = "SELECT atp FROM
Atp atp WHERE atp.type.id = :atpTypeId")
})
public class Atp extends MetaEntity implements AttributeOwner<AtpAttribute> {
A few things to notice here:



Please namespace your named queries so they are easy to identify and keep track of.
Always qualify every field reference with an alias (atp.type.id instead of just type.id)
Try to use our naming conventions, initial lowercase for aliases and binding variables, all caps for
JPQL reserved words.
2. Let’s add a method signature to the AtpDao interface :
24
Developing Services in Kuali Student
List<Atp> findAtpsByAtpType(String atpTypeKey);
3. Finally we need to implement the new method in AtpDaoImpl:
@Override
public List<Atp> findAtpsByAtpType(String atpTypeId) {
Query q = em.createNamedQuery("Atp.findAtpsByAtpType");
q.setParameter("atpTypeId", atpTypeId);
@SuppressWarnings("unchecked")
List<Atp> results = q.getResultList();
return results;
}
The gist of this is we dereference the named query by the name, set the query parameters with the
method parameters, and return the query results.
4. Now we should test our new method. Write a new test in TestAtpDaoImpl that creates two Atp
types and three Atps, two of one type and one of the other. Then call findAtpsByAtpType with
each of the types to make sure the JPQL is working.
5. Implement the remainder of your named queries to complete the DAO layer. Be sure to thoroughly
test each query.
5.4 DAO Review




We created our DAO interface and extended SearchableCrudDao to reuse code
We created named queries for each comlex query in our service API
We created a DAO implementation that extended AbstractSearchableCrudDaoImpl to make use
of existing CRUD and search implementations.
We created JUnit tests to test our DAO implementation.
Developing Services in Kuali Student
25
6 Service Implementation
The Service implementation is the business logic connecting the calls to the service with the persistence
layer. The major code written in this layer has to do with converting DTOs into Entity beans. Other parts
include validation and exception handling, and versioning.
26
Developing Services in Kuali Student
6.1 Service Tests
Since we are doing test-driven development, we should start with a test. I like my first test to be a call to
create. This usually involves the bulk of code you need to write.
Create a new Junit test in ks-core-impl/src/test/java called
org.kuali.student.core.atp.TestAtpServiceImpl. Have it extend AbstractService test and additional
annotations so your code looks like this:
@Daos( { @Dao(value = "org.kuali.student.core.atp.dao.impl.AtpDaoImpl")})
@PersistenceFileLocation("classpath:META-INF/atp-persistence.xml")
public class TestAtpServiceImpl extends AbstractServiceTest {
final Logger LOG = Logger.getLogger(TestAtpService.class);
@Client(value =
"org.kuali.student.core.atp.service.impl.AtpServiceImpl")
public AtpService client;
@Test
public void testCreateAtp(){
}
}
AbstractServiceTest is part of our test framework that will start up an embedded datasource, inject your
DAO implementation into your service implementation, start up a jetty server, and publish your service
as a SOAP service. This lets you test end to end your service call to make sure there are no problems,
such as marshalling errors, or strange persistence problems that would not get caught until later.
Of course, you can feel free to create unit tests that use mock objects and avoid the overhead of
integration testing.
The @Daos and @PersistenceFileLocation annotations help the test framework know how to configure
your DAO implementation.
The @Client defines the service implementation class to use.
Our test will create an Atp. Implement the testCreateAtp method using the client Info DTOs instead of
the JPA Entities. Make sure you fill out every field and a dynamic attribute or two to make sure your
test is working.
Sample test:
AtpInfo atpInfo = new AtpInfo();
atpInfo.setName("Atp one");
atpInfo.setDesc(new RichTextInfo());
atpInfo.getDesc().setPlain("Atp one Descr");
AtpInfo created = client.createAtp("atp.test.type.1", "atp.1",atpInfo);
assertEquals(atpInfo.getName(),created.getName());
Developing Services in Kuali Student
27
assertEquals(atpInfo.getDesc().getFormatted(),created.getDesc().getForm
atted());
This is the basic structure of the tests. We create data, perform a client operation, and then assert that
the data matches our expected values.
Make sure that if you are comparing values in collections, you don’t assert using index numbers like
assertEquals(obj1.getList().get(0),obj2.getList().get(0));. This can cause intermittent bugs if you are not
guaranteed the sort order of our two collections.
6.2 Service Implementation
Once the test is finished, we need to start work on our Atp Service Implementation. In ks-coreimpl/src/main/java create a new class org.kuali.student.core.atp.service.impl.AtpServiceImpl which
implements the AtpService interface. Give it a private AtpDao member(and set access method) so we
have access to persistence:
@WebService(endpointInterface =
"org.kuali.student.core.atp.service.AtpService", serviceName =
"AtpService", portName = "AtpService", targetNamespace =
"http://student.kuali.org/wsdl/atp")
@Transactional(readOnly=true,noRollbackFor={DoesNotExistException.class
},rollbackFor={Throwable.class})
public class AtpServiceImpl implements AtpService {
private AtpDao atpDao;
In addition, there are two annotations here.
@WebService is a JAX-WS annotation that helps define this class as implementing a web service.
@Transaction is used with our spring configuration to mark this as a transactional class. The
readOnly=true marks that all methods will be readOnly unless we specify otherwise,
which we will need to do for methods that create, delete or update data. In our
implementation of the createAtp method, we will add the
@Transactional(readOnly=false) to mark this method as transactional.
28
Developing Services in Kuali Student
6.3 Exceptions
The first things to notice are the exceptions that the service throws. Make sure you are handling these
exceptions so that the service behaves as expected.
Most service operations have missing parameter exceptions, so let’s add a check that see if the
parameters passed in are null or empty and throw a MissingParameterException if any of them are.
checkMissingParameters(new String[]{"atpTypeKey","atpKey","atpInfo"},
new Object[]{ atpTypeKey, atpKey, atpInfo});
checkMissingParameters is boilerplate code that should be added to a serviceUtils class in the
future, but for now cut and paste the method from another service implementation
private void checkMissingParameters(String[] paramNames, Object[]
params) throws MissingParameterException {
String errors = null;
int i = 0;
for(Object param:params){
if(param==null){
errors = errors==null?paramNames[i]:errors+", " +
paramNames[i];
}
i++;
}
if(errors!=null){
throw new MissingParameterException("Missing Parameters: "
+ errors);
}
}
Some other exceptions you might see are VersionMismatchException which we will cover later, and
CyclicDependencyException where you will have to check for cycles in a recursive object structure(like
avoiding adding a group to itself).
Try to implement the checks for each exception that might be thrown early so that they are not
forgotten. The end users are expecting certain behavior based on the exceptions thrown.
6.4 Extra Service Parameters
Another confusing thing about services is that some of the parameters are redundant. For example, the
atpKey and atpTypeKey are both parameters, but they are both contained within the atpInfo object
being passed in. In some cases the parameters might be used for lookup and the Info is used for
replacement values. For now, let’s just assume that the parameters “win”, and use the parameters to
set the corresponding values of the DTO:
atpInfo.setType(atpTypeKey);
atpInfo.setId(atpKey);
Developing Services in Kuali Student
29
6.5 Validation
The next thing we need to do is validate our object. This should be done for all operations that throw a
DataValidationErrorException (mainly creates and updates).
// Validate
List<ValidationResultInfo> validationResults;
try {
validationResults = validateAtp("OBJECT", atpInfo);
} catch (DoesNotExistException e) {
throw new OperationFailedException("Validation call failed." +
e.getMessage());
}
if (null != validationResults && validationResults.size() > 0) {
throw new DataValidationErrorException("Validation error.",
validationResults);
}
6.6 Service Assemblers
Now that we know our data is valid, we need to use our DAO to persist it. The only problem is that we
have a DTO object and our DAO takes in JPA entities. We’ll use an assembler class that does this
transformation, and also does the reverse transformation.
The rest of the createAtp code will now look like this:
Atp atp = AtpAssembler.toAtp(new Atp(), atpInfo, atpDao);
atpDao.create(atp);
return AtpAssembler.toAtpInfo(atp);
We’ll transform the DTO to an Entity, use our DAO to persist, and transform the result back to a DTO
which is returned.
1. Make the AtpServiceAssembler class which extends BaseAssembler and the toAtp and toAtpInfo
static methods. We will just copy the corresponding fields from the AtpInfo to the Atp and use the
dao for looking up objects if needed.
2. Let’s start with the toAtp method. This is going to be used for both create and update so keep that
in mind when implementing. Create will take in a blank Atp, and update will take in the currently
persisted Atp.
There are some helper utilities to assist in copying common data structures such as metaInfo,
attributes, standard Types and richText. Let’s add code to copy attributes and richtext:
// Copy Attributes
atp.setAttributes(toGenericAttributes(AtpAttribute.class,
atpInfo.getAttributes(), atp, atpDao));
//Copy RichText
atp.setDescr(toRichText(AtpRichText.class, atpInfo.getDesc()));
30
Developing Services in Kuali Student
3. We need to do some additional logic when copying the type since we are only passed in the type
key, and in JPA, we need to set the relationship to the type object.
// Search for and copy the type
try {
AtpType atpType = atpDao.fetch(AtpType.class, atpInfo.getType());
atp.setType(atpType);
} catch (DoesNotExistException e) {
throw new InvalidParameterException("AtpType does not exist for
key: " + atpInfo.getType());
}
4. All that is left is to copy the remaining “primitive” types:
atp.setStartDate(atpInfo.getStartDate());
atp.setEndDate(atpInfo.getEndDate());
atp.setName(atpInfo.getName());
atp.setId(atpInfo.getId());
atp.setState(atpInfo.getState());
5. We can take a shortcut with the previous code by replacing it with Spring’s BeanUtils to copy
properties.
BeanUtils.copyProperties(atpInfo, atp, new String[] { "type",
"attributes", "metaInfo"});
Just remember to add the ignore properties for complex types, and other properties that you are
copying manually or want to ignore.
6. Our toAtp method is complete, let’s make the toAtpInfo method.
AtpInfo atpInfo = new AtpInfo();
BeanUtils.copyProperties(atp, atpInfo, new String[] { "type",
"attributes", "metaInfo", "desc" });
// copy attributes, metadata, Atp, and Type
atpInfo.setAttributes(toAttributeMap(atp.getAttributes()));
atpInfo.setMetaInfo(toMetaInfo(atp.getMeta(), atp.getVersionNumber()));
atpInfo.setType(atp.getType().getId());
atpInfo.setDesc(toRichTextInfo(atp.getDescr()));
return atpInfo;
You’ll see that this is very similar to the toAtpMethod. There are helper methods for attributes and
Richtext. MetaInfo is also copied with a helper method (this is only needed one-way from the DB,
metaInfo should never be updated manually)
IMPORTANT: Try to keep the logic separated between the Service implementation and the
Assemblers. Assemblers should only be responsible for transforming data and nothing more. Service
code should not be doing any transformation that is not a special business case.
Developing Services in Kuali Student
31
6.7 Test Data
Run the test again to see how we’ve done. You should get this exception:
org.kuali.student.core.exceptions.InvalidParameterException: AtpType does not
exist for key: atp.test.type.1
What happened? Another confusing thing about our services is that there is no maintenance for types
meaning the types must be created outside of the service. For testing this can be a pain. You can
configure your tests to load test data prior to execution. Test data can exist as sql files or spring bean
xml files.
Let’s create test data that loads our AtpType into the DB. Create a file in
ks-core-impl/src/text/resources called atp-test-beans.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="persistList"
class="org.springframework.beans.factory.config.ListFactoryBean">
<property name="sourceList">
<list>
<ref bean="atp.test.type.1" />
</list>
</property>
</bean>
<bean id="atp.test.type.1"
class="org.kuali.student.core.atp.entity.AtpType">
<property name="id" value="atp.test.type.1" />
<property name="name" value="Atp Test Type 1" />
<property name="descr" value="ATP Testing Type" />
<property name="effectiveDate" value="01/01/2008" />
<property name="expirationDate" value="01/01/2100" />
</bean>
<bean id="customEditorConfigurer"
class="org.springframework.beans.factory.config.CustomEditorConfigurer"
>
<property name="customEditors">
<map>
<entry key="java.util.Date">
<bean
class="org.springframework.beans.propertyeditors.CustomDateEditor">
<constructor-arg index="0">
<bean
class="java.text.SimpleDateFormat">
<constructor-arg
value="MM/dd/yyyy" />
</bean>
</constructor-arg>
<constructor-arg index="1" value="false"
/>
</bean>
</entry>
32
Developing Services in Kuali Student
</map>
</property>
</bean>
</beans>
This xml is a bit more complex than it needs to be, but the main parts of it are a bean with
id=”persistList” that contains a list of JPA entities you would like inserted into persistence before your
tests are run. The test framework looks for the bean named “persistList” and persists every bean in it (in
order) using your DAO.
The bean “atp.test.type.1” is just a spring representation of our Type object that will be persisted.
(the customEditorConfigurer bean is not really important, it is just an easy way to add dates in your
spring config)
The last thing we need to do to get our test data working is to update our test class annotations to point
to our test data file. In TestAtpService update the annotation so I looks like this:
@Daos( { @Dao(value = "org.kuali.student.core.atp.dao.impl.AtpDaoImpl",
testDataFile = "classpath:atp-test-beans.xml") })
Now run the test again and get no errors.
6.7.1 Implementing fetch operations:
The bulk of getFoo() operations will use your DAOs and named queries. Let’s implement the
getAtpsByAtpType method in the service that we already implemented in the DAO.
1. We’ll start with another test: Add another test for testing getAtpsByAtpType after the previous test:
@Test
public void testGetAtpsByAtpType() throws InvalidParameterException,
MissingParameterException, OperationFailedException{
List<AtpInfo> atps = client.getAtpsByAtpType("atp.test.type.1");
assertEquals(1,atps.size());
assertEquals("Atp one",atps.get(0).getName());
}
2. Now we need to implement the service call.
2.1. First we’ll add a check for missing params as this method throws a MissingParameterException.
2.2. Next we’ll use the DAO to find the Atp entities that have the same type:
atpDao.findAtpsByAtpType(atpTypeKey).
2.3. The last thing we need to do is transform the list of Atp entities into a list of AtpInfos. We can
do this using a new assembler method toAtpInfoList, that will internally call toAtpInfo for each
Atp passed in.
@Override
public List<AtpInfo> getAtpsByAtpType(String atpTypeKey)
Developing Services in Kuali Student
33
throws InvalidParameterException,
MissingParameterException, OperationFailedException {
checkMissingParameters(new String[]{"atpTypeKey"}, new
Object[]{atpTypeKey});
List<Atp> atps = atpDao.findAtpsByAtpType(atpTypeKey);
return AtpAssembler.toAtpInfoList(atps);
}
And in AtpAssembler:
public static List<AtpInfo> toAtpInfoList(List<Atp> atps) {
if(null==atps){
return Collections.<AtpInfo>emptyList();
}
List<AtpInfo> atpInfoList = new
ArrayList<AtpInfo>(atps.size());
for (Atp atp : atps) {
atpInfoList.add(toAtpInfo(atp));
}
return atpInfoList;
}
3. Now you can run your test again, and the service will call your custom DAO method which will
execute the named query and then transform the results back to a list of DTOs.
This works because you have already created an Atp in the previous test. The service tests differ
from the Dao tests in that the service tests don’t rollback changes so be sure that each test can be
run independently (i.e. our last test is bad because it will fail if run before the testCreateAtp test).
JUnit DOES NOT GUARANTEE the order in which tests are run, so your tests should never rely on
data manipulated in another test. One way to ensure this is to delete any data that was created in
the same test. A testCrud method shouldcreate an entity, update it and finally delete it. What we
really should be doing is adding test data either with SQL or Spring Beans that we can test our get
methods with.
6.8 Remaining Crud Operations
6.8.1
Updates
1. Rename the testCreateAtp test to testAtpCrud.
We are going to test create, delete and update methods in one test so the DB state is unchanged
after a successful test. Let’s add some code to test updates.
2. After creating the ATP, change every field slightly so we can make sure that the fields have been
changed correctly. Then call client.updateAtp() to perform the update. At this point we should also
check for version mismatchExceptions(optimistic locking):
34
Developing Services in Kuali Student
(modify Atp values here)
//Update Atp
AtpInfo updatedAtp = client.updateAtp(“someAtpKey…”, createdAtp);
//now try to update again with the same version
try {
client.updateAtp(atp_fall2008Semester, createdAtp);
fail("AtpService.updateAtp() should have thrown
VersionMismatchException");
} catch (VersionMismatchException vme) {
// what we expect
}
Make sure you have assertion tests for every value to make sure your Assembler is copying
correctly.
3. It’s time to implement the update method in the service implementation.
3.1. Check for missing params
3.2. Load the existing atp from persistence
3.3. Check for VersionMismatchException
3.4. Use the assembler to transform the AtpInfo values into the existing Atp
3.5. Transform the resulting Atp back to an AtpInfo and return
6.8.2 Optimistic Locking
Version mismatch Exceptions are caused by optimistic locking. Every time an entity is updated, the
@Version annotation tells our persistence provider to increment the optimistic lock field on our object.
If User1 loads an Atp for editing and then User2 loads the same Atp, if user1 makes changes and saves
and then User 2 makes other changes and saves, User 2 would overwrite the changes that User1 made.
To prevent this from happening, the version indicator of the DTO is compared to the Entity in
persistence. If they do not match, the VersionMismatchException is thrown, forcing User2 to reload the
Atp to make any changes.
Be careful not to confuse the optimistic lock ‘version’ with the term ‘version’ as it relates to historical
data. We hope to change this terminology in the future to make it less confusing.
Developing Services in Kuali Student
35
6.8.3 Completion of Update Code
The Final code should look like this:
@Override
public AtpInfo updateAtp(String atpKey, AtpInfo atpInfo)
throws DataValidationErrorException, DoesNotExistException,
InvalidParameterException, MissingParameterException,
OperationFailedException, PermissionDeniedException,
VersionMismatchException {
CheckMissingParameters(new String[]{"atpKey","atpInfo"},
new Object[]{ atpKey, atpInfo});
Atp atp = atpDao.fetch(Atp.class, atpKey);
if
(!String.valueOf(atp.getVersionNumber()).equals(atpInfo.getMetaInfo().getVers
ionInd())){
throw new VersionMismatchException("Atp to be updated is
not the current version");
}
atp = AtpAssembler.toAtp(atp, atpInfo, atpDao);
Atp updatedAtp = atpDao.update(atp);
return AtpAssembler.toAtpInfo(updatedAtp);
}
By separating out the copying and persistence to other classes, our service implementation becomes
very simple and readable.
6.8.4 Update List Pattern
Sometimes you will need to perform updates on a list of items. Instead of deleting the entire list and
recreating it, it is sometimes more optimal to compare the values in the existing list and the new values
to be persisted. This way you can add anything missing, update anything that is in both lists and delete
anything that is left over.
You can see this in use in the BaseAssembler.toGenericAttributes() method. The existing list is copied
into a map keyed by the unique id of the items in the existing list, then the existing list is cleared out.
Next we iterate over the new list and check if the key exists in our map. If the key exists we do an update
of the existing value and remove that entry from the map, if it does not we need to create a new object.
At this point our existing list will contain all pre-existing and new items from the input. The last thing we
will do is clean up the orphaned items that are left in the map by deleting them.
6.8.5 Updates that cause deletes
Some objects have dependent objects related to them that must be deleted if the relationship is deleted
during an update.
For example, Clu contains a list of CluIdentifiers. If the persisted Clu had 2 Identifiers, CluIdent1 and
CluIdent2, and we are updating it to contain only the new identifier CluIdent3, we will have to explicitly
delete CluIdent1 and CluIdent2, then add CluIdent3, so there are not any orphaned CluIdentifiers
36
Developing Services in Kuali Student
floating around the database. This logic could be placed in the Service implementation (not ideal), the
Assembler or, possibly, the assembler could return a list of all the orphaned entities and leave the
responsibility of deleting them to the service implementation.
6.8.6 Deletes
Deletes are relatively simple to do, just call dao.delete on your entity. Most delete operations return a
StatusInfo Object which we just set to true and return.
6.9 Finishing Touches: Wiring in Dictionary, Validation and Search
6.9.1 Dictionary
We will delegate all of our dictionary calls to an existing DictionaryService. Add a member called
dictionaryService and accessor methods:
private DictionaryService dictionaryService;
We only need to implement two methods for the DictionaryService interface and we can delegate those:
@Override
public ObjectStructureDefinition getObjectStructure(String
objectTypeKey) {
return
dictionaryServiceDelegate.getObjectStructure(objectTypeKey);
}
@Override
public List<String> getObjectTypes() {
return dictionaryServiceDelegate.getObjectTypes();
}
6.9.2 Search
We can delegate calls to search just like the dictionary using a search manager. Add a member and
accessor methods for searchManager:
private SearchManager searchManager;
Implement the remaining SearchService methods by delegating to the search manager.
6.9.3 Validation
We will also use delegation for validation, but there is some additional logic we will need. Add a
ValidatorFactory member:
private ValidatorFactory validatorFactory;
Implement each validation method by delegating to the validator factory and passing in the proper key.
Our implementation uses the class name as the object structure key used for validation:
@Override
public List<ValidationResultInfo> validateAtp(String validationType,
Developing Services in Kuali Student
37
AtpInfo atpInfo) throws DoesNotExistException,
InvalidParameterException, MissingParameterException,
OperationFailedException {
checkMissingParameters(new String[]{"validationType", "atpInfo"},
new Object[]{ validationType , atpInfo});
ObjectStructureDefinition objStructure =
this.getObjectStructure(AtpInfo.class.getName());
Validator defaultValidator = validatorFactory.getValidator();
List<ValidationResultInfo> validationResults =
defaultValidator.validateObject(atpInfo, objStructure);
return validationResults;
}
It is beyond the scope of this training to configure search and data dictionaries. Please review the
configuration guides. In the meantime, you can temporarily disable the validation until the data
dictionary is complete.
6.10 Final steps
6.10.1 Wiring in new service to application
KS uses Spring and the inversion of control pattern extensively. This makes it easy to build up an
application from many parts.
1.
2.
3.
4.
We’ll need a datasource which should already be configured.
Configure the EntitymanagerFactory and EntityManagers using the datasource.
Configure our DAO and plug in the EntityManager.
Configure our service and plug in our Dao. Other parts we need are wired in to our service as well
including the dictionary, search, and validation.
5. Finally, we expose our service to the KSB bus as a soap service.
38
Developing Services in Kuali Student
Developing Services in Kuali Student
39
Atp should be configured in ks-core-context.xml . These beans should already be configured, especially
the core datasource, default entityManagerFactory, validationFactory, DicttionaryService and
searchDispatcher:
<bean id="coreDataSource"
class="bitronix.tm.resource.jdbc.PoolingDataSource" init-method="init"
destroy-method="close">
<property name="className"
value="oracle.jdbc.xa.client.OracleXADataSource" />
<property name="uniqueName" value="coreDataSource" />
<property name="maxPoolSize" value="${ks.core.datasource.maxSize}"
/>
<property name="useTmJoin" value="true" />
<property name="testQuery"
value="${ks.core.datasource.validationQuery}" />
<property name="allowLocalTransactions" value="true" />
<property name="driverProperties">
<props>
<prop key="URL">${ks.core.datasource.url}</prop>
<prop key="user">${ks.core.datasource.username}</prop>
<prop key="password">${ks.core.datasource.password}</prop>
</props>
</property>
</bean>
<!-- Default JPA EntityManagerFactory -->
<bean id="coreDefaultEntityManagerFactory" abstract="true"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="jpaVendorAdapter">
<bean
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform"
value="${ks.core.jpa.DatabasePlatform}" />
<property name="showSql" value="${ks.core.jpa.showSql}" />
<property name="generateDdl"
value="${ks.core.jpa.generateDdl}" />
</bean>
</property>
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.transaction.manager_lookup_class"
value="${ks.core.jpa.JpaProperties.hibernate.transaction.manager_lookup_class
}"/>
<entry key="hibernate.hbm2ddl.auto"
value="${ks.core.jpa.JpaProperties.hibernate.hbm2ddl.auto}"/>
<entry key="hibernate.connection.release_mode"
value="${ks.core.jpa.JpaProperties.hibernate.connection.release_mode}"/>
<!--<entry key="hibernate.connection.autocommit"
value="${ks.core.jpa.JpaProperties.hibernate.connection.autocommit}"/>-->
</map>
</property>
</bean>
<bean id="coreDictionaryService"
40
Developing Services in Kuali Student
class="org.kuali.student.core.dictionary.service.impl.DictionaryServiceImpl">
<constructor-arg index="0"
value="${ks.core.dictionary.serviceContextLocations}" />
</bean>
<bean id="coreServiceValidator"
class="org.kuali.student.common.validator.DefaultValidatorImpl">
<property name="searchDispatcher" ref="coreSearchDispatcher"/>
</bean>
<bean id="coreValidatorFactory-parent"
abstract="true"
class="org.kuali.student.common.validator.ValidatorFactory">
<property name="defaultValidator" ref="coreServiceValidator"/>
<property name="validatorList">
<list/>
</property>
</bean>
<bean id="coreValidatorFactory" parent="coreValidatorFactory-parent"/>
<bean id="coreSearchDispatcher"
class="org.kuali.student.core.search.service.impl.SearchDispatcherImpl">
<property name="services">
<list>
<ref bean="atpServiceImpl"/>
<ref bean="emServiceImpl"/>
<ref bean="orgServiceImpl"/>
<ref bean="documentServiceImpl"/>
<ref bean="proposalServiceImpl"/>
<ref bean="commentServiceImpl"/>
</list>
</property>
</bean>
The SearchDispatcher is used when performing a search where the target service is unknown, only the
search key is known. You will need to add the atpServiceImpl to the search dispatcher for it to properly
dispatch searches to the Atp service.
Developing Services in Kuali Student
41
TherRest of the configuration will need to be added:
<!-- Atp Service Config -->
<bean id="atpEntityManagerFactory"
parent="coreDefaultEntityManagerFactory">
<property name="persistenceUnitName" value="Atp"/>
<property name="persistenceXmlLocation" value="classpath:METAINF/atp-persistence.xml" />
<property name="dataSource" ref="coreDataSource" />
</bean>
<bean id="atpEntityManager"
class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
<property name="entityManagerFactory" ref="atpEntityManagerFactory"
/>
</bean>
<bean id="atpDao"
class="org.kuali.student.core.atp.dao.impl.AtpDaoImpl">
<property name="em" ref="atpEntityManager" />
</bean>
<bean id="atpServiceImpl"
class="org.kuali.student.core.atp.service.impl.AtpServiceImpl">
<property name="atpDao" ref="atpDao" />
<property name="searchManager" ref="atpSearchManager"/>
<property name="dictionaryService" ref="coreDictionaryService"/>
<property name="validatorFactory" ref="coreValidatorFactory"/>
</bean>
<bean id="atpSearchManager"
class="org.kuali.student.core.search.service.impl.SearchManagerImpl">
<constructor-arg index="0" value="classpath:atp-search-config.xml" />
</bean>
<bean id="ks.exp.atpService"
class="org.kuali.rice.ksb.messaging.KSBExporter">
<property name="serviceDefinition">
<bean class="org.kuali.rice.ksb.messaging.SOAPServiceDefinition">
<property name="jaxWsService" value="true" />
<property name="service" ref="atpServiceImpl" />
<property name="serviceInterface"
value="org.kuali.student.core.atp.service.AtpService" />
<property name="localServiceName" value="AtpService" />
<property name="serviceNameSpaceURI"
value="http://student.kuali.org/wsdl/atp" />
<property name="busSecurity" value="${ks.core.bus.security}"
/>
</bean>
</property>
</bean>
The service specific config is fairly self explanatory. We use property placeholders where possible to
make configuration as easy as possible for end users. Each layer of our stack is instantiated and plugged
into the next layer.
42
Developing Services in Kuali Student
The SharedEntityManagerBean is a way to avoid using JNDI in obtaining a reference to the entity
manager.
KSBExporter allows us to publish services on the KSB (Kuali Service Bus). We use the
SOAPServiceDefinition with jaxWsService set to “true” to publish as a SOAP endpoint. Internally KSB
uses CXF to accomplish this.
6.10.2 Note on service testing and additional spring context:
Our test framework for services only injects daos, so if you need to inject other objects into the service
implementation during testing, you can configure them in an additional spring context file and add an
annotation attribute to your test. The beans in the additional context will be automatically wired by type
into your service implementation.
1. Create a new context file in ks-core-impl/src/test/resources called atp-additional-context.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="searchManager"
class="org.kuali.student.core.search.service.impl.SearchManagerImpl"
autowire="byType">
<constructor-arg index="0" value="classpath:atp-search-config.xml" />
</bean>
<bean id="dictionaryService"
class="org.kuali.student.core.dictionary.service.impl.DictionaryServiceImpl">
<constructor-arg index="0" value="classpath:ks-atp-dictionarycontext.xml" />
</bean>
<bean id="atpServiceValidator"
class="org.kuali.student.core.dictionary.service.MockDefaultValidatorImpl"/>
<bean id="validatorFactory"
class="org.kuali.student.common.validator.ValidatorFactory">
<property name="defaultValidator" ref="atpServiceValidator"/>
</bean>
</beans>
2. Now add the following @Client attribute to your TestAtpSerrviceImpl test class:
additionalContextFile="classpath:atp-additional-context.xml"
This will configure your test to inject any other beans your service needs. You can use mock objects
or real ones, it’s up to you.
6.11 Service Implementation Review
We have now completed the final layer to our service stack.
Developing Services in Kuali Student
43






44
We created a test class and test cases for our service
We created an Assembler to transform DTOs into JPA entities and back
We implemented the Crud operations in our service
We implemented out fetch operations using our DAO which called named queries
We added support for Validation, Dictionary and Search by delegation
We wired up our service using spring configuration.
Developing Services in Kuali Student
7 Setting up Service Database
The schema for our database must be put into the project so that it is available to users. Fortunately,
Hibernate can automatically generate the schema for us. All we have to do is run our application while
enabling the generation of the schema, run the IMPEX Export tool and copy the relevant SQL to our
project.
7.1 Schema Generation
Once your service is configured to run in the application, all we need to do is set the hibernate config
param: hibernate.hbm2ddl.auto to update. You can see this is being set in the ks-core-context.xml
bean definition for the coreDefaultEntityManagerFactory.
Edit your ${user}/kuali/main/dev/ks-embedded-config.xml and set these properties:
<param
name="ks.core.jpa.JpaProperties.hibernate.hbm2ddl.auto">update</param>
<param name="ks.core.jpa.generateDdl">true</param>
Now run your ks-embedded war as you normally would. The schema will be created in your database.
Developing Services in Kuali Student
45
7.2 Export
Run the Kuali IMPEX export task, making sure that you have configured your impex-build.properties file
to point to the correct database and project. For example:
export.torque.database.user=KSEMBEDDED
export.torque.database.schema=KSEMBEDDED
export.torque.database.password=KSEMBEDDED
…
torque.schema.dir={your project directory}/ks-cfg-dbs/ks-standalone-db/src/main/impex
7.3 Adding Indexes to Foreign Keys
As a rule of thumb, it is important to add indexes to all foreign key columns. If you don’t, Oracle
performance will suffer and you might get deadlocks during referential integrity checks.
1. In your ks-cfg-dbs (or wherever you exported your data to), look in the impex/schema.xml file. This
is where Impex stores the schema data. Look for the table definition for KSAP_ATP. You should see
these foreign key constraints:
<foreign-key foreignTable="KSAP_RICH_TEXT_T" name="FK123098231BB">
<reference foreign="ID" local="RT_DESCR_ID"/>
</foreign-key>
<foreign-key foreignTable="KSAP_ATP_TYPE" name="FK123098231BA">
<reference foreign="TYPE_KEY" local="TYPE"/>
</foreign-key>
2. First, the names of the foreign keys are not very useful and are autogenerated. Let’s rename them
so we know what table they belong to.
<foreign-key foreignTable="KSAP_RICH_TEXT_T" name="KSAP_ATP_FK2">
<reference foreign="ID" local="RT_DESCR_ID"/>
</foreign-key>
<foreign-key foreignTable="KSAP_ATP_TYPE" name="KSAP_ATP_FK1">
<reference foreign="TYPE_KEY" local="TYPE"/>
</foreign-key>
3. Let’s add indexes to the local columns that reference foreign tables right below these definitions:
<index name="KSAP_ATP_I1">
<index-column name="TYPE"/>
</index>
<index name="KSAP_ATP_I2">
<index-column name="RT_DESCR_ID"/>
</index>
4. Now our data should look and perform better.
46
Developing Services in Kuali Student
7.4 Generate SQL
After running export, you can run the import task on your impex project to create the SQL data files you
will need. You might want to clean out the sql and datasql folders first if they are causing conflicts.
7.5 Copy SQL Files to Your Project
In your ks-cfg-dbs (or wherever you exported your data), look in the impex/sql/schema.sql file. This is
where the schema was created. Search for your new module’s table definitions (KSAP_*) and cut and
paste them all into a new .sql file located in the sql module of your project
(ks-core-sql/src/main/resources). Depending on whether this is a new module, or a new service in an
existing module you will want to add the sql file to the initial-db or upgrades folders.
You will also want to copy the constraints for KSAP* tables for the Impex project in
schema-constraints.sql. If there is any data that your module requires, you should copy/create that as
well.
7.6 Service Database Review
Once you commit your sql files to the sql module, the data will be built into the latest KS impex project
for everyone to use.





We generated the schema using Hibernate’s ddl generation
We exported our database to an impex project
We cleaned up our schema names and added indexes to foreign keys
We used impex again to generate sql code
We copied our new module’s schema, constraints and data to our project.
Developing Services in Kuali Student
47
8 Further reading / links to resources
JPQL reference:
http://download.oracle.com/docs/cd/E11035_01/kodo41/full/html/ejb3_langref.html
JPA annotation reference:
http://www.oracle.com/technetwork/middleware/ias/toplink-jpa-annotations-096251.html
Spring reference:
http://static.springsource.org/spring/docs/2.5.x/reference/
KS Documentation (Specifically for Configuration Guide and Developer Guide):
https://wiki.kuali.org/display/KULSTG/KS+Curriculum+Management+1.1+Documentation
48
Developing Services in Kuali Student
Download