Developing Services in Kuali Student

yazooalbumSecurity

Nov 3, 2013 (3 years and 5 months ago)

308 views

Developing Services in Kuali Student


1





Developing Services in Kuali Student



February 2011

2


Developing Services in Kuali Student




Developing Services in Kuali Student


3

Contents

1

Introduction

................................
................................
................................
................................
..........

5

1.1

Requirements

................................
................................
................................
................................

5

2

Understanding the service layer

................................
................................
................................
...........

6

2.1

Project Structure

................................
................................
................................
...........................

7

3

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

4

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

5

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

6

Service Impl
ementation

................................
................................
................................
......................

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

7

Setting up Service Database

................................
................................
................................
...............

45

4


Developing Services in Kuali Student

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


Developing Services in Kuali Student


5

1

Introduction

Kuali Student
is comprised

of several components including
user interface,
application and service
layers. This document
explain
s

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



S
pring



Maven

In addition, please follow the corresponding KS
D
eveloper
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

(J
ava 6 JDK, Eclipse and plugins, Oracle XE, Maven, Subversion)



KS projects already checked out and can run tests

(ks
-
common, ks
-
core,ks
-
lum)



6


Developing Services in Kuali Student

2

Understanding the service layer

The service layer is designed like a
ny

typical
3
-
tier
application. It
consists of multiple layers
:
a

client
-
facing 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 a
n 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 (P
OJOs) that represent the data
the service uses.

They are annotated
with JAXB annotations so they can be marshaled and unmarshal
ed into XML.

Service Interface and Service Implementation

These are

responsible for business logic, data validation, and tr
ansforming data to and from the
persistence layer.




Developing Services in Kuali Student


7

JPA Entit
i
es

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

Per
iod (ATP) service:

ks
-
core







Project

ks
-
core
-
api/src/main/java

org.kuali.student.core.atp.dto


DTOs (
atp
Info
POJO
s)

org.kuali.student.core.atp.service


Service API interface


ks
-
core
-
api/src/main/resources

META
-
INF

wsdl





Service WSDL


k
s
-
core
-
impl/src/main/java

org.kuali.student.core.atp.dao


DAO interface

org.kuali.student.core.atp.dao.impl


DAO implementation

org.kuali.student.core.atp.entity


JPA Entitiy POJOs

org.kuali.student.core.atp.service.impl

Service implementation


ks
-
co
re
-
impl/src/main/resources

ksb






Spring Context

META
-
INF





JPA persistence.xml configuration


ks
-
core
-
impl/src/test/java

org.kuali.student.core.atp.dao


DAO Implementation tests

org.kuali.student.core.atp.service.impl

Service Implementation tests


ks
-
core
-
impl/src/test/resources



Test data and configuration




8


Developing Services in Kuali Student

3

Generating
the
Service A
PI

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 w
ill 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

Developing Services in Kuali Student


9

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
-
>
O
ptions
-
>
Privacy


and
click
R
emove individual cookies
, then type

wiki.kuali.org

in th
e 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.

R
un

“mvn

generate
-
sources

. This process will generate your

Java interface, and all needed DTOs.

4.

Once you have your service Java files
,

d
elete the contract config in your pom, or disable it by s
etting
the phase to
“none”

10


Developing Services in Kuali Student

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 d
uration 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;


}


}

Developing Services in Kuali Student


11

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 wo
rk 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 contr
act.

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
-
java2ws
-
plugin
. 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 t
he 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:

k
s
-
core
\
ks
-
core
-
api>mvn
-
Dks.java2ws.phase=process
-
classes clean
process
-
classes

if you look in your project/ks
-
core
-
api/target/generated/ws
d
l, you will see your AtpService.wsdl

12


Developing Services in Kuali Student

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 wi
thout 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
.



Developing Services in Kuali Student


13

4

Create JPA enti
ties 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.



14


Developing Services in Kuali Student

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 loc
king.

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.

I
n addition, we need
to define the table name that corresponds to th
is 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
;

}

Developing Services in Kuali Student


15

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 provid
er 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
string
s

with some
a
ttributes 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 s
tring key of the type to a string value in the
16


Developing Services in Kuali Student

referencing object

we need to
s
e
t

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
RichTe
xt 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 d
efines 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
;

}

Developing Services in Kuali Student


17


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
E
clipse 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
@Many
ToOne

maps many AtpAttributes to one Atp.It also defines the column name
18


Developing Services in Kuali Student

in the
KSAP_ATP_ATTR

table that will hold the id of the owning A
tp. 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
-
ow
ning side of the bidirectional relationship.

In
this case
,

the AtpAttribute “owns” the relationship
since

the AtpAttribute table contains the fore
ign
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 o
ur 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 Ri
chText class:

@Entity

@Table
(name =
"KSAP_RICH_TEXT_T"
)

public

class

AtpRichText
extends

RichText {


}

If you look at the Rich
Text 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

S
tandard

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 corr
esponds to the TypeInfo class.

1.

Let’s start on
the
AtpSeasonalType
class. Create a n
ew 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.

Developing Services in Kuali Student


19

At this point it might get a little overwhelming with all the cl
asses we need. Our base entity Atp
needed attributes
,

so we had to make an AtpAttribute
c
lass
. Then we needed a
n

AtpSeasonalType
entity
class which
,

in turn
,

needed another Attribute class.

The service architects have determined
that all types have dy
namic 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 S
tandard

Types

When we created AtpTypeInfo, we extended TypeInfo and added t
wo additional fields for
d
urationType
and seasonalType. W
e

need to reflect that data in the persistence entities. We will accomplish this by
20


Developing Services in Kuali Student

extending Type (and creating a new AtpTypeAttribute class) and then adding two additional fields that
have unidire
ctional 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 uni
directional 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.xm
l 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
>

</
persi
stence
>

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 class
es, 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
or
g.kuali.student.core.atp.entity package. (You can select
the classes in the project explorer and cut and past
e to make sure you don’t miss anything)

4.2

Some JPA
Q
uirks

Developing Services in Kuali Student


21

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 conflic
ts)

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 deve
lopers 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



22


Developing Services in Kuali Student

5

DAO Layer

The Data A
c
cess
Object l
ayer 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
. C
reate an AtpDao interface and have it e
xtend 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.

Developing Services in Kuali Student


23

KS has a test framework for our DAOs we will use to validate our entities and named queries.

1.

Create a new J
ava 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 tr
ansactions.

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, y
ou 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.

24


Developing Services in Kuali Student

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 impl
ement 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” operation
s

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.

L
et’s add a method
signature
to
the AtpDao interface :

Developing Services in Kuali Student


2
5


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 t
est 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 na
med 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.



26


Developing Services in Kuali Student

6

Service
Implementation

The Service implementation is the busi
ness 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.


Developing Services in Kuali Student


27

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.a
tp.TestAtpService
Impl
. 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

TestAtpService
Impl

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 @PersistenceFileLocatio
n 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 a
n

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());

28


Developing Services in Kuali Student

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
-
core
-
impl/src/main/java create a new class org.kuali.student.core.atp.service.impl.AtpServiceImpl which
imple
ments 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
},rollba
ckFor={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 mar
k 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
implementat
ion of the createAtp method, we will add the
@Transactional(readOnly=false) to mark this method as transactional.

Developing Services in Kuali Student


29

6.3

Exceptions

The first thing
s

to notice
are

the exceptions that the service throws. Make sure
you are handling these
exceptions so that th
e 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.


c
heckMissingParameters(
new

String[]{
"atpTypeKey"
,
"atpKey"
,
"atpInfo"
},





new

Object[]{ atpTypeKey, atpKey,
atpInfo
});

c
heckMissingParameters is boilerplate code that should be added to a serviceUtils class in the
future, but for now cut and paste

the method

from a
nother 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 us
ers 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 I
nfo is used for
replacement values. F
or 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);

30


Developing Services in Kuali Student

6.5

Validation

The next thing we need to do is v
alidate 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
.

T
he 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 Atp
Service
Assembler 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()));


Developing Services in Kuali Student


31

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
, atpInf
o.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
p
roperties.


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 b
etween 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.

32


Developing Services in Kuali Student

6.7

Test Data

Run the test again to se
e how we’ve done. You should get this exception:

o
rg.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
>

Developing Services in Kuali Student


33




</
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 nam
ed “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 w
ay 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 =
"or
g.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 imp
lement
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> getAtpsByAt
pType(String
atpTypeKey
)

34


Developing Services in Kuali Student




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 b
ack to a list of DTOs.

This work
s

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 t
est 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 delet
e any data that was created in
the same test.
A

testCrud method
should
create

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

R
emaining
C
rud
O
perations

6.8.1

Updates

1.

Rename the
testCreateAtp

test to testAtpCrud.

We are going to test create, delete and update metho
ds 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):

Developing Services in Kuali Student


35


(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
VersionMismatchExcep
tion

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 lo
ads 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 te
rminology in the future to make it less confusing.

36


Developing Services in Kuali Student

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, ou
r

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 e
xisting list is copied
into a map keyed by the unique id of the items in the existing list
, t
hen 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 existin
g 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 depend
e
nt 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 C
l
uIdent2
, then add CluIdent3,

so ther
e are not any orphaned CluIdentifiers

Developing Services in Kuali Student


37

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
S
tatusInfo 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 access
or 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 mana
ger. 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 th
e object structure key used for validation:

@Override

public

List<ValidationResultInfo> validateAtp(String validationType,

38


Developing Services in Kuali Student



AtpInfo atpInfo)
throws

DoesNotExistException,



InvalidParameterException, MissingParameterException,



OperationFailedException {

c
heckMissingParameters
(
new

String[]{
"validationType"
,
"atpInfo"
},


new

Object[]{ validationType , atpInfo});


ObjectStructureDefinition objStructure =
this
.getObjectStructure(AtpInfo.
class
.getName());

Validator defaultValidator =
va
lidatorFactory
.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 revi
ew 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.

W
e’ll need
a datasource which should already be configured.

2.

C
onfigure the EntitymanagerFactory and EntityManagers using the datasource.

3.

C
onfigure our DAO and plug in the EntityManager.

4.

C
onfigure our
s
ervice 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.

Developing Services in Kuali Student


39


40


Developing Services in Kuali Student

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
-
meth
od
=
"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"

Developing Services in Kuali Student


41


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 dispa
tcher for it to properly
dispatch searches to the Atp service.

42


Developing Services in Kuali Student

The
r
Rest 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:META
-
INF/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"
>


<
proper
ty

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.


Developing Services in Kuali Student


43

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:

O
ur 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"


xmln
s: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
-
dictionary
-
context.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.

44


Developing Services in Kuali Student



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 oper
ations 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


45

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, r
un 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.

46


Developing Services in Kuali Student

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 directo
ry
}
/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 d
efinition 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"
/>


</
inde
x
>


<
index

name
=
"KSAP_ATP_I2"
>


<
index
-
column

name
=
"RT_DESCR_ID"
/>


</
index
>

4.

Now our data should look and perform better.

Developing Services in Kuali Student


47

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
Y
our
P
roject

In your ks
-
cfg
-
dbs (or wherever you exported your data
), look in the impex/sql/schema.sql file. This is
wher
e the
s
chema 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 ne
w 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 req
uires
,

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 e
xported 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.


48


Developing Services in Kuali Student

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://stati
c.springsource.org/spring/docs/2.5.x/reference/

KS

Documentation (Specifically for
C
onfiguration
G
uide

and Developer Guide
)
:

https://wiki.kuali.org/display/KULSTG/KS+Curriculum+Management+1.1+Documentation