74
School Complex Persistence Layer, JAX-RS RESTful, Mockito Óbudai Egyetem, Java Enterprise Edition Műszaki Informatika szak Labor 6 Bedők Dávid 2016.10.31. v1.1

School - users.nik.uni-obuda.hu

  • Upload
    others

  • View
    7

  • Download
    0

Embed Size (px)

Citation preview

Page 1: School - users.nik.uni-obuda.hu

SchoolComplex Persistence Layer, JAX-RS RESTful, Mockito

Óbudai Egyetem, Java Enterprise EditionMűszaki Informatika szakLabor 6

Bedők Dávid2016.10.31.v1.1

Page 2: School - users.nik.uni-obuda.hu

Feladat

Hozzunk létre egy Enterprise Java alkalmazást, mely hallgatók érdemjegyeit adott tantárgy vonatkozásában tárolja és kezeli.A diákokat neptun kódjuk egyedien azonosítja és tároljuk el nevüket és intézményüket (BANKI, KANDO, NEUMANN).A tantárgyaknak legyen egy egyedi nevük, oktatójuk (név és neptun kód) és leírásuk.Minden érdemjegyhez tároljunk egy megjegyzést és egy pontos időbélyeget is.

2

Page 3: School - users.nik.uni-obuda.hu

Feladat szolgáltatás oldala

A megvalósított tárolási réteg fölé az alábbi RESTful service réteget építsük fel:● GET http://localhost:8080/school/api/student/WI53085

○ Megadott neptun kóddal rendelkező hallgató adatait adja vissza.● GET http://localhost:8080/school/api/student/list

○ Az összes hallgató adatait adja vissza.● POST http://localhost:8080/school/api/mark/stat

○ Payload: “Sybase PowerBuilder”○ Egy adott tantárgy vonatkozásában visszaad egy intézményre és

évekre bontott átlag-eredmény statisztikát.● PUT http://localhost:8080/school/api/mark/add

○ Payload: {"subject": "Sybase PowerBuilder","neptun": "WI53085","grade": "WEAK","note": "Lorem ipsum"}

○ Adott érdemjegyet rögzít a rendszerben.● DELETE http://localhost:8080/school/api/student/TX78476

○ Megadott neptun kóddal rendelkező hallgatót töröl, amennyiben az létezik és nincs egyetlen korábban tárolt érdemjegye sem. 3

Page 4: School - users.nik.uni-obuda.hu

Technológia

A megvalósítás során PostgreSQL RDBMS adatmodellre épített JPA-n keresztül megszólított ORM réteg kerül építésre. Immáron a fizikai táblák közötti kapcsolat az entitások közötti kapcsolatokban is meg fog mutatkozni.Speciális lekérdezések mellett az új rekord rögzítésének és rekord törlésének mikéntjét is alaposabban megvizsgáljuk.A RESTful service megvalósítása kapcsán megismerkedünk a JAX-RS alapjaival, mind szerver mind kliens oldalon.A feladat végén egység teszteket fogunk készíteni az adaptor rétegben (TestNG, Mockito).Remote debug lehetőségeire is kitekintünk.

4

Page 5: School - users.nik.uni-obuda.hu

Adatbázis oldalSchema létrehozása (create-schema.sql):

Táblák:● institute● student (FK: student_institute_id)● teacher● subject (FK: subject_teacher_id)● mark (FK: mark_student_id, FK: mark_subject_id)Kapcsolatok:● 1-N: institute-student● 1-N: teacher-subject● N-M: student-subject 5

\jboss\school\sch-persistence\databaseGIT

Page 6: School - users.nik.uni-obuda.hu

Project struktúra

school (root project) [ear]● sch-persistence [jar]● sch-ejbservice [jar]● sch-weblayer [war]

○ Kizárólag a StudentPingServlet, annak érdekében hogy az sch-webservice elkészültéig meg lehessen szólítani a backend service funkcióit.

● sch-webservice [war]● sch-restclient (standalone)Nincs igény Remote EJB client-re, ezért az sch-ejbservice-t nem kell kétfelé szedni. Az sch-webservice is local EJB hívásokkal éri el az service réteget, ahogyan a sch-weblayer. 6

Page 7: School - users.nik.uni-obuda.hu

Root gradle project

7

version = '1.0'

ext {

...

jaxrsVersion = '2.0.1'

webserviceArchiveName = 'sch-webservice.war'

webserviceContextPath = 'school'

}

...dependencies {

deploy project('sch-ejbservice')

deploy project('sch-persistence')

deploy project(path: 'sch-weblayer', configuration: 'archives')

deploy project(path: 'sch-webservice', configuration: 'archives')

}

school/build.gradle

Az új third-party library a JAX-RS 2.0.1 API-ja lesz. JBoss 6.4 ennek RESTEasy 2.3.10 Final implementációját tartalmazza, de mi kizárólag API-n keresztül fogjuk vezérelni.

A kimeneti ear nevében a version információ bekerül. Tetszőleges szabad-szöveges érték lehet.

Page 8: School - users.nik.uni-obuda.hu

Persistence réteg

Entity class-ok:● Mark (table: mark)● Student (table: student)● Subject (table: subject)● Teacher (table: teacher)Enumeration:● Institute (table: institute)

EJB Service-ek:● MarkService● StudentService● SubjectService

8

Page 9: School - users.nik.uni-obuda.hu

Subject-Teacher relation1 tantárgynak 1 oktatója van

9

@Entity @Table(name = "subject")

public class Subject implements Serializable {

...

@ManyToOne(fetch = FetchType.EAGER, optional = false)

@JoinColumn(name = "subject_teacher_id", referencedColumnName = "teacher_id", nullable = false)

private Teacher teacher;

...

}

Subject.class

FetchType:● EAGER: az entitás lekérésekor automatikusan kapcsolja a ‘teacher’ táblát,

ezáltal elérhetőek lesznek a kapcsolt adatok (pl. tanár neptun kódja)● LAZY: nem csatolja automatikusan, csak ha erre kéri a lekérdezés, vagy az

attached entitás (hatékonyabb, de körültekintést igényel)

Az egyik legfontosabb (és legnehezebb) dolog megfelelően beállítani az EAGER és LAZY kapcsolatokat. Ha ellentétes igények merülnek fel, akkor sincs probléma: ugyanarra a DB táblára akármennyi entitást készíthetünk, egyikben a kapcsolat lehet EAGER, a másikban LAZY.

A @JoinColumn és @Column annotációk hasonlóan a @Table annotációhoz a fizikai adatbázissal való kapcsolatot szolgálják.

Page 10: School - users.nik.uni-obuda.hu

Student-Mark relation1 diáknak számos jegye van

10

@Entity

@Table(name = "student")

public class Student implements Serializable {

@OneToMany(fetch = FetchType.LAZY, targetEntity = Mark.class, mappedBy = "student")

private final Set<Mark> marks;

public Student() {

this.marks = new HashSet<>();

}

}

Student.class

Egy hallgatónak N darab érdemjegye van. Gondolva a teljesítményre, az ilyen halmazokat LAZY-vel érdemes felvenni. A Lista használata Hibernate értelmezésében hiba (mivel nincs a sorrendiségnek entrópiája jelen esetben).

A @OneToMany és a @ManyToOne annotáció az ORM modellre vonatkozik, a hivatkozott mezők field-ek nevei (pl. student és nem mark_student_id).

Page 11: School - users.nik.uni-obuda.hu

Subject-Mark relation1 tantárgyhoz is számos jegy tartozik

11

@Entity

@Table(name = "subject")

public class Subject implements Serializable {

@OneToMany(fetch = FetchType.LAZY, targetEntity = Mark.class, mappedBy = "subject")

private final Set<Mark> marks;

public Subject() {

this.marks = new HashSet<>();

}

}

Subject.class

Teljesen azonos a Student-Mark kapcsolat jelzésével, azonban ritkábban van szerepe ennek. Egy hallgató jegyeit kigyűjteni “gyakoribb” lehet mint egy tárgyhoz tartozó összes jegyet egyben kezelni (bár minden üzleti igény kérdése).

Page 12: School - users.nik.uni-obuda.hu

Mark entitásStudent-Subject N-M kapcsolótábla

12

@Entity@Table(name = "mark")public class Mark implements Serializable {

...@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL,

optional = false)@JoinColumn(name = "mark_student_id", referencedColumnName =

"student_id", nullable = false)private Student student;

@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, optional = false)

@JoinColumn(name = "mark_subject_id", referencedColumnName = "subject_id", nullable = false)

private Subject subject;...

}

Mark.class

A cascade értéke egy CascadeType enum value halmaz (ALL esetén nem kell felsorolni), mely meghatározza hogy milyen művelet során szükséges a kapcsolaton végigmenni. cascade={PERSIST, MERGE, REMOVE, REFRESH, DETACH}. Alapértelmezésben üres lista.

Page 13: School - users.nik.uni-obuda.hu

Java API for RESTful WebServicesJAX-RS

● JSR 311● Java EE 6 része

● "Párja" a JAX-WS (Java API for XML Web Services), mely SOAP webservice-ek készítéséhez készült (JSR 224, Java EE 6 része).

13

Page 14: School - users.nik.uni-obuda.hu

sch-webservice child project

14

apply plugin: 'war'

war { archiveName webserviceArchiveName }

dependencies {

providedCompile project(':sch-ejbservice')

providedCompile group: 'javax.ws.rs', name:'javax.ws.rs-api', version: jaxrsVersion

providedCompile group: 'javax.servlet', name:'javax.servlet-api', version: servletapiVersion

}

build.gradle

Page 15: School - users.nik.uni-obuda.hu

RESTful services

● StudentRestService○ StudentStub getStudent( String neptun )○ List<StudentStub> getAllStudent( )○ removeStudent( String neptun )

● MarkRestService○ List<MarkDetailStub> getMarkDetails( String subject )○ MarkStub addMark( MarkInputStub stub )

15

Page 16: School - users.nik.uni-obuda.hu

Szolgáltatás16

Hallgató adatainak lekérdezéseGET http://localhost:8080/school/api/student/{neptun}

Page 17: School - users.nik.uni-obuda.hu

Diák adatainaklekérdezése

17

{

"name": "Juanita A. Jenkins",

"neptun": "WI53085",

"institute": "BANKI",

"marks": [{

"subject": {

"name": "C/C++ Programming",

"teacher": {

"name": "Lorine B. Pine",

"neptun": "MD21921"

},

"description": "Maecenas..."

},

"grade": 2,

"note": "Vivamus",

"date": 1401956934000

}, ...]

}

REQ: GET http://localhost:8080/school/api/student/WI53085RESP: 200 OK

EPOCH timestamp

Page 18: School - users.nik.uni-obuda.hu

getStudent()Kezdjük a legegyszerűbb esettel :-)

1. webservice: StudentRestServicea. StudentStub getStudent( String neptun ) throws AdaptorException;b. StudentStub, MarkStub, SubjectStub, TeacherStub

2. webservice: StudentRestServiceBeana. call ejbservice layer

3. ejbservice: StudentFacadea. StudentStub getStudent( String neptun ) throws AdaptorException;

4. ejbservice: StudentFacadeImpla. call persistence layerb. call converter service

5. persistence: StudentServicea. Student read( String neptun ) throws PersistenceServiceException;

6. persistence: StudentServiceImpla. GET_BY_NEPTUN: SELECT st FROM Student st LEFT JOIN FETCH

st.marks m LEFT JOIN FETCH m.subject su LEFT JOIN FETCH su.teacher WHERE st.neptun=:studentNeptun

7. ejbservice: StudentConvertera. ejbservice: StudentConverterImplb. ejbservice: MarkConverterc. ejbservice: MarkConverterImpl

18

JOIN: a jegyek bekötése LAZY, vagyis ha nem jelezzük, csak akkor jönnek le, ha még attached entitáson bejárjuk (pl. student.getMarks().size() ). Azonban ha JOIN-al jelezzük a lekérést a Named Query-ben, akkor felülbíráljuk a LAZY-t.Mindez egyetlen natív query lefutását jelenti!

Page 19: School - users.nik.uni-obuda.hu

Generált natív lekérdezés

19

SELECTstudent0_.student_id AS student_1_2_0_,marks1_.mark_id AS mark_id1_0_1_,subject2_.subject_id AS subject_1_3_2_,teacher3_.teacher_id AS teacher_1_4_3_,student0_.student_institute_id AS student_2_2_0_,student0_.student_name AS student_3_2_0_,student0_.student_neptun AS student_4_2_0_,marks1_.mark_date AS mark_dat2_0_1_,marks1_.mark_grade AS mark_gra3_0_1_,marks1_.mark_note AS mark_not4_0_1_,marks1_.mark_student_id AS mark_stu5_0_1_,marks1_.mark_subject_id AS mark_sub6_0_1_,marks1_.mark_student_id AS mark_stu5_2_0__,marks1_.mark_id AS mark_id1_0_0__,subject2_.subject_description AS subject_2_3_2_,subject2_.subject_name AS subject_3_3_2_,subject2_.subject_teacher_id AS subject_4_3_2_,teacher3_.teacher_name AS teacher_2_4_3_,teacher3_.teacher_neptun AS teacher_3_4_3_

FROMstudent student0_

INNER JOIN mark marks1_ ON student0_.student_id=marks1_.mark_student_id INNER JOIN subject subject2_ ON marks1_.mark_subject_id=subject2_.subject_id INNER JOIN teacher teacher3_ ON subject2_.subject_teacher_id=teacher3_.teacher_id

WHEREstudent0_.student_neptun=?

Page 20: School - users.nik.uni-obuda.hu

Szolgáltatás20

Összes hallgató adatainak lekérdezéseGET http://localhost:8080/school/api/student/list

Page 21: School - users.nik.uni-obuda.hu

getAllStudents()

SELECT s FROM Student s ORDER BY s.name21

...

for (final Student student : result) {

student.getMarks().size();

}

...

StudentServiceImpl.java

REQ: GET http://localhost:8080/school/api/student/listRESP: 200 OK

Named Query: StudentQuery.GET_ALL

JOIN: a jegyek bekötése LAZY, ugyanaz az eset mint korábban, azonban itt a lekérdezés mellőzi a JOIN-okat. E végett csak abban az esetben kapjuk vissza a jegyeket, ha még attached állapotban (tranzakció határon belül) lekérjük a jegyeket. A jegyekben a Student és a Subject már EAGER, így azok is lejönnek, ahogyan a tárgy Teacher eleme is (szintén EAGER). Azonban mindez nagyon sok (11) natív query-be kerül!

Page 22: School - users.nik.uni-obuda.hu

Szolgáltatás22

Átlag eredmény statisztikaPOST http://localhost:8080/school/api/mark/stat

Page 23: School - users.nik.uni-obuda.hu

Postman

GET kérésekhez bármely böngésző megfelelő, azonban POST/PUT (stb.) kérések elküldéséhez és/vagy űrlap/payload megadásához már külön kis kliens alkalmazást kellene írni, vagy XHTML formokat szerkesztgetni. E körülményes megoldások helyett használhatjuk a Postman-t is (Google Chrome kiegészítő vagy Mac app).https://www.getpostman.com/

23

Page 24: School - users.nik.uni-obuda.hu

getMarkDetails()

Egy adott tantárgy (payload) vonatkozásában visszaad egy intézményre (group-by) és évekre (group-by) bontott átlag-eredmény statisztikát (average).

24

REQ: POST http://localhost:8080/school/api/mark/statRESP: 200 OK

Sybase PowerBuilder

Payload

[{

"institute": "BANKI",

"year": 2015,

"averageGrade": 4.0

},

{

"institute": "KANDO",

"year": 2012,

"averageGrade": 4.0

},

{

"institute": "KANDO",

"year": 2013,

"averageGrade": 4.0

},

{

"institute": "NEUMANN",

"year": 2014,

"averageGrade": 3.5

}]

Page 25: School - users.nik.uni-obuda.hu

Probléma ?!

Timestamp-ből az év kinyerése (PSQL function):● DATE_TRUNC('year', mark_date)

Túl összetett lekérdezés és/vagy speciális db függvény használata (akár saját db függvény használata). → Eltolja az ORM implementációt natív query irányba (erre van technikai lehetőség)

Megoldás: a felelősség egy részét DB oldalon megvalósítani, és ORM szinten tisztán JPA Named Query-vel dolgozni (ahogy eddig is).Megjegyzés: Más alternatív lehetőség is elképzelhető.

25

Page 26: School - users.nik.uni-obuda.hu

Natív lekérdezés

26

SELECTmarkdetail.student_institute_id,markdetail.mark_year,AVG(markdetail.mark_grade)

FROM(

SELECTmark_subject_id,student_institute_id,mark_grade,DATE_TRUNC('year', mark_date) AS mark_year

FROM markINNER JOIN student ON ( mark_student_id = student_id )

WHERE ( 1 = 1 ) ) AS markdetail

WHERE ( 1 = 1 )AND ( markdetail.mark_subject_id = 2 )

GROUP BYmarkdetail.student_institute_id,markdetail.mark_year

ORDER BYmarkdetail.student_institute_id,markdetail.mark_year

markdetail.sql

A natív lekérdezés elkészítése nélkül a JPA megoldást sem lehet (nem érdemes) elkészíteni.

Page 27: School - users.nik.uni-obuda.hu

Terv

● Tipikus “group-by” lekérdezés.○ intézményre és évre nézve

● DB oldalon VIEW létrehozása a DB függvény használata miatt (DATE_TRUNC)○ A VIEW-ban ki kell vezetni azokat a mezőket,

melyekre üzletileg szűrést kell eszközölni (valamint természetesen a csoportosító mezőket is)■ tantárgy (szűrés)■ intézmény és év (group-by)

Megjegyzés: Nem lehet a VIEW group-by lekérdezés, mert a tantárgy szűrés nem volna kivitelezhető! (ezen a mondaton érdemes gondolkodni…)

27

Page 28: School - users.nik.uni-obuda.hu

DB View

28

CREATE VIEW markdetail AS

SELECT

ROW_NUMBER() OVER() AS markdetail_id,

mark_subject_id AS markdetail_subject_id,

student_institute_id AS markdetail_institute_id,

mark_grade AS markdetail_grade,

DATE_TRUNC('year', mark_date) AS markdetail_year

FROM mark

INNER JOIN student ON ( mark_student_id = student_id )

WHERE ( 1 = 1 );

markdetail-view.sql

A VIEW-ból entitás lesz, minden entitásnak szükséges egy primary kulcs. A ROW_NUMBER() alkalmas erre (nem fogjuk update-elni, törölni a view egyetlen sorát sem, ráadásul group-by lekérdezés az egyéni sorokat sem fogja visszaadni.

Page 29: School - users.nik.uni-obuda.hu

VIEW “tesztelése”

29

SELECT

markdetail_institute_id,

markdetail_year,

AVG(markdetail_grade)

FROM

markdetail

WHERE ( 1 = 1 )

AND ( markdetail_subject_id = 2 )

GROUP BY

markdetail_institute_id,

markdetail_year

ORDER BY

markdetail_institute_id,

markdetail_year;

markdetail-test.sql

Ezt a lekérdezést kell előállítanunk JPA named query-ként.

Page 30: School - users.nik.uni-obuda.hu

View az ORM rétegbenUgyanolyan entitás mint a tábla

30

@Entity @Table(name = "markdetail")public class MarkDetail implements Serializable {

@Id@Column(name = "markdetail_id", nullable = false)private Long id;

@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, optional = false)@JoinColumn(name="markdetail_subject_id",referencedColumnName="subject_id",nullable=false)private Subject subject;

@Enumerated(EnumType.ORDINAL)@Column(name = "markdetail_institute_id", nullable = false)private Institute institute;

@Column(name = "markdetail_grade", nullable = false)private Integer grade;

@Column(name = "markdetail_year")private Date year;

}

MarkDetail.java

Primary Key kötelező

Minden mező pont ugyanúgy van kezelve, mint a többi entitásban. Ne zavarjon meg az, hogy a VIEW belül is tartalmaz pl. INNER JOIN-t. Ez az ORM réteg szempontjából láthatatlan. A Subject, Institute úgy van bekötve ide, mintha ezek egy táblában együtt jelen lennének.

Page 31: School - users.nik.uni-obuda.hu

Átlagszámítás - Részletek I.● webservice: MarkRestService, MarkRestServiceBean● ejbservice: MarkDetailStub● ejbservice: MarkFacade, MarkFacadeImpl● persistence: SubjectService, SubjectServiceImpl

○ SELECT s FROM Subject s WHERE s.name=:subjectName

● persistence: MarkService, MarkServiceImpl○ SELECT ???? FROM MarkDetail md WHERE

md.subject.id=:subjectId GROUP BY md.institute, md.year ORDER BY md.institute, md.year

○ ????: egy olyan “result”, mely tartalmaz egy intézményt, egy évet és egy származtatott értéket. Egy e célra létrehozott Result példányt kell visszaadnunk (ami NEM egy entitás lesz)!

31

Page 32: School - users.nik.uni-obuda.hu

Lekérdezés eredménye

32

public class MarkDetailResult {

private final Institute institute;

private final Date year;

private final double averageGrade;

public MarkDetailResult(Institute institute, Date year, double averageGrade) {

this.institute = institute;

this.year = year;

this.averageGrade = averageGrade;

}

...

}

MarkDetailResult.java

Nem entitás, egyszerű DTO. A konstruktor üzletileg fontos, nem kell default ctor-nak lennie (entitásnál kötelező).

Page 33: School - users.nik.uni-obuda.hu

Result példány

Named Query-ben a Result példány létrehozása:SELECT new hu.qwaevisz.school.persistence.result.MarkDetailResult(md.institute, md.year, AVG(md.grade)) FROM MarkDetail md WHERE md.subject.id=:subjectId GROUP BY md.institute, md.year ORDER BY md.institute, md.year

33

A Full Qualified Name megadása szükséges (mivel nincs lehetőség “import”-ra.

Page 34: School - users.nik.uni-obuda.hu

Átlagszámítás - Részletek II.● ejbservice: MarkConverter, MarkConverterImpl

○ getYearFromDate (Java konverzió)

34

@Override public List<MarkDetailStub> to(List<MarkDetailResult> results) {

final List<MarkDetailStub> stubs = new ArrayList<>();

for (final MarkDetailResult result : results) {

stubs.add(this.to(result));

}

return stubs; }

private MarkDetailStub to(final MarkDetailResult result) {

return new MarkDetailStub(result.getInstitute().toString(), this.getYearFromDate(result.getYear()), result.getAverageGrade()); }

private int getYearFromDate(Date date) {

final Calendar cal = Calendar.getInstance();

cal.setTime(date);

return cal.get(Calendar.YEAR);

}

MarkConverterImpl.java

Page 35: School - users.nik.uni-obuda.hu

Szolgáltatás35

Új érdemjegy rögzítésePUT http://localhost:8080/school/api/mark/add

Page 36: School - users.nik.uni-obuda.hu

addMark()

36

{

"subject": "Sybase PowerBuilder",

"neptun": "WI53085",

"grade": "WEAK",

"note": "Lorem ipsum"

}

Request payload

REQ: PUT http://localhost:8080/school/api/mark/addRESP: 200 OK

{

"subject": {

"name": "Sybase PowerBuilder",

"teacher": {

"name": "Richard B. Cambra",

"neptun": "UT84113"

},

"description": "Donec"

},

"grade": 2,

"note": "Lorem ipsum",

"date": 1443797867042

}

Response content

Page 37: School - users.nik.uni-obuda.hu

Részletek● webservice: MarkRestService, MarkRestServiceBean● ejbservice: MarkFacade, MarkFacadeImpl● persistence: SubjectService, SubjectServiceImpl (már

meglévő)○ SELECT s FROM Subject s WHERE

s.name=:subjectName● persistence: StudentService, StudentServiceImpl (már

meglévő)○ SELECT s FROM Student s JOIN s.marks WHERE

s.neptun=:studentNeptun● persistence: MarkService, MarkServiceImpl

○ INSERT INTO (nem Named Query, EntityManager operation)

● ejbservice: MarkConverter, MarkConverterImpl37

Page 38: School - users.nik.uni-obuda.hu

Rögzítés ORM oldalon

38

@Override

public Mark create(Long studentId, Long subjectId, Integer grade, String note) throws PersistenceServiceException {

try {

final Student student = this.studentService.read(studentId);

final Subject subject = this.subjectService.read(subjectId);

Mark mark = new Mark(student, subject, grade, note);

mark = this.entityManager.merge(mark);

this.entityManager.flush();

return mark;

} catch (final Exception e) {

throw new PersistenceServiceException("Unknown error during merging SubscriberGroup (studentId: " + studentId + ", subjectId: " + subjectId

+ ", grade: " + grade + ", note: " + note + ")! " + e.getLocalizedMessage(), e);

}

}

MarkService.java

Más körülmények között a validáció és az itt megjelenő service hívások azonos tranzakcióba is kerülhetnének.

flush(): empty the internal SQL instructions cache

Page 39: School - users.nik.uni-obuda.hu

Költség● Validation at EJB Service layer

○ Check Subject by Name (1 SELECT)■ Do not use JOIN FETCH s.teacher and Teacher’s FetchType is EAGER → +1 SELECT

to acquire Teacher○ Check Student by Neptun (1 SELECT)

■ Use several LEFT JOIN FETCH(es) in that query● Attach the entities at Persistence Service Layer

○ Acquire Student by ID (1 SELECT)■ Subject hasn’t got any EAGER relation

○ Acquire Subject by ID (1 SELECT)■ Do not use JOIN FETCH s.teacher and Teacher’s FetchType is EAGER → +1 SELECT

to acquire Teacher○ Merge Mark

■ We need to get all the marks (+1 SELECT with JOINS via Student’s LAZY Set<Mark> field). Hiberante creates that query and use the Mark’s EAGER Subject field in the same query, but (!)● Each Subject has an EAGER Teacher relation too, and Hibernate acquires

these values via N separate queries (+3 SELECT)■ Related Subject becomes attached via the previous query (NO SELECT required)

○ SELECT the Mark’s Sequence (+1 SELECT)○ Insert the new Mark (+1 INSERT) 39

Total: 11 SELECT + 1 INSERT

Page 40: School - users.nik.uni-obuda.hu

Továbbfejlesztés

You may push the validation at the Persistence Layer, but you have to pay attention not to detach the same entity twice (you will get “Multiple representations of the same entity are being merged.” exception in that case and for instance you have to remove the CascadeType.MERGE flag in some relations).

40

Page 41: School - users.nik.uni-obuda.hu

Szolgáltatás41

Hallgató törléseDELETE http://localhost:8080/school/api/student/{neptun}

Page 42: School - users.nik.uni-obuda.hu

removeStudent()Hibakezelés bemutatása

42

REQ: DELETE http://localhost:8080/school/api/student/ABC123RESP: 400 Bad Request

{ "code": 40, "message": "Resource not found", "fields": "ABC123" }

Response content

REQ: DELETE http://localhost:8080/school/api/student/WI53085RESP: 412 Precondition Failed

{ "code": 50, "message": "Has dependency", "fields": "WI53085" }

Response content

REQ: DELETE http://localhost:8080/school/api/student/TX78476RESP: 204 No Content

Page 43: School - users.nik.uni-obuda.hu

HTTP Status Codeshttp://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

43

Successful 2xx

● 200 OK● 201 Created● 202 Accepted● 204 No Content● 206 Partial Content

Redirection 3xx

● 300 Multiple Choices● 301 Moved Permanently● 302 Found● 303 See Other● 304 Not Modified● 307 Temporary Redirect

Client Error 4xx

● 400 Bad Request● 401 Unauthorized ● 402 Payment Required● 403 Forbidden● 404 Not Found● 405 Method Not Allowed● 408 Request Timeout● 412 Precondition Failed● 413 Request Entity Too Large● 414 Request-URI Too Long● 415 Unsupported Media Type

Server Error 5xx

● 500 Internal Server Error● 501 Not Implemented● 503 Service Unavailable

Page 44: School - users.nik.uni-obuda.hu

Részletek

● webservice: StudentRestService, StudentRestServiceBean

● ejbservice: StudentFacade, StudentFacadeImpl● persistence: StudentService, StudentServiceImpl

○ SELECT COUNT(s) FROM Student s WHERE s.neptun=:studentNeptun

● persistence: MarkService, MarkServiceImpl○ SELECT COUNT(m) FROM Mark m WHERE

m.student.neptun=:studentNeptun● persistence: StudentService, StudentServiceImpl

○ DELETE FROM Student s WHERE s.neptun=:studentNeptun

44Named Query mellett megvalósíható EntityManager műveletként is a törlés.

Page 45: School - users.nik.uni-obuda.hu

Publikus hiba üzenet: ErrorStub (json)

45

public class ErrorStub {

private int code;

private String message;

private String fields;

public ErrorStub(int code, String message, String fields) {

this.code = code;

this.message = message;

this.fields = fields;

}

...

}

ErrorStub.java

Muszáj hogy getter/setter metódusok is legyenek a Stub-ban, így a mezők ajánlottan ne legyen final-ek.

Page 46: School - users.nik.uni-obuda.hu

Hibalehetőségek, publikus infok

46

public enum ApplicationError {

UNEXPECTED(10, 500, "Unexpected error"), // Internal Server Error

NOT_EXISTS(40, 400, "Resource not found"), // Bad Request

HAS_DEPENDENCY(50, 412, "Has dependency"); // Precondition Failed

private final int code;

private final int httpStatusCode;

private final String message;

private ApplicationError( int code, int httpStatusCode, String message) {

this.code = code;

this.httpStatusCode = httpStatusCode;

this.message = message; }

public int getHttpStatusCode() { return this.httpStatusCode; }

public ErrorStub build(String field) {

return new ErrorStub(this.code, this.message, field);

}

}

ApplicationError.java

Az enum példány az ErrorStub factory-ja!

Page 47: School - users.nik.uni-obuda.hu

Hibaágak kezeléseÜzleti hibák detektálása és átalakítása “nyilvános” hibaüzenetté

47

@Overridepublic void removeStudent(String neptun) throws AdaptorException {

try {if (this.studentService.exists(neptun)) {

if (this.markService.count(neptun) == 0) {this.studentService.delete(neptun);

} else {throw new AdaptorException(ApplicationError.HAS_DEPENDENCY,

"Student has undeleted mark(s)", neptun);}

} else {throw new AdaptorException(ApplicationError.NOT_EXISTS, "Student

doesn't exist", neptun);}

} catch (final PersistenceServiceException e) {LOGGER.error(e, e);throw new AdaptorException(ApplicationError.UNEXPECTED,

e.getLocalizedMessage());}

}

StudentFacadeImpl.java

Page 48: School - users.nik.uni-obuda.hu

Továbbfejlesztés

Validate and Delete Student in the same transaction, because this is the safer solution!● We have to push the Application level

business error detection to the Persistence Layer.

● Need to change the TransactionAttributes of the related Business methods.

● The Persistence Layer will become more complex while the EJB Service Layer will be simpler.

48

Page 49: School - users.nik.uni-obuda.hu

Persistence LayerModifications

49

package hu.qwaevisz.school.persistence.util;public enum PersistenceApplicationError {

UNEXPECTED,NOT_EXISTS,HAS_DEPENDENCY;

}

PersistenceApplicationError.java

public class StudentServiceImpl implements StudentService {@EJBprivate MarkService markService;

@Override@TransactionAttribute(TransactionAttributeType.REQUIRED)public boolean exists(String neptun) throws PersistenceServiceException {

[..]}

}

StudentServiceImpl.java

public class AdvancedPersistenceServiceException extends PersistenceServiceException {private final PersistenceApplicationError error;private final String field;public AdvancedPersistenceServiceException(PersistenceApplicationError error, String

message, String field) {super(message);this.error = error;this.field = field;

}public PersistenceApplicationError getError() { return this.error; }public String getField() { return this.field; }

}

AdvancedPersistenceServiceException.java

We have to use the same attribute in the MarkServiceImpl count(studentNeptun) business method.

Page 50: School - users.nik.uni-obuda.hu

Persistence LayerNew Student delete Business method

50

public class StudentServiceImpl implements StudentService {@Overridepublic void deleteAdvanced(String neptun) throws PersistenceServiceException {

if (this.exists(neptun)) {if (this.markService.count(neptun) == 0) {

try {this.entityManager.createNamedQuery(StudentQuery.REMOVE_BY_NEPTUN).set

Parameter(StudentParameter.NEPTUN, neptun).executeUpdate();} catch (Exception e) {

throw new PersistenceServiceException("Unknown error when removing Student by neptun (" + neptun + ")! " + e.getLocalizedMessage(), e);

}} else {

throw new AdvancedPersistenceServiceException(PersistenceApplicationError.HAS_DEPENDENCY, "Student has undeleted mark(s)", neptun);

}} else {

throw new AdvancedPersistenceServiceException(PersistenceApplicationError.NOT_EXISTS, "Student doesn't exist", neptun);

}}

}

StudentServiceImpl.java

Page 51: School - users.nik.uni-obuda.hu

EJB Service LayerNew Student delete Business method

51

package hu.qwaevisz.school.ejbservice.facade;

public class StudentFacadeImpl implements StudentFacade {

@Overridepublic void removeStudentAdvanced(String neptun) throws AdaptorException {

try {this.studentService.deleteAdvanced(neptun);

} catch (AdvancedPersistenceServiceException e) {ApplicationError error = ApplicationError.valueOf(e.getError().name());throw new AdaptorException(error, e.getLocalizedMessage(), e.getField());

} catch (PersistenceServiceException e) {LOGGER.error(e, e);throw new AdaptorException(ApplicationError.UNEXPECTED,

e.getLocalizedMessage());}

}

}

StudentFacadeImpl.java

Page 52: School - users.nik.uni-obuda.hu

Transaction AttributesThe TransactionAttribute annotation specifies whether the container is to invoke a business method within a transaction context. The TransactionAttribute annotation can be used for session beans and message driven beans. It can only be specified if container managed transaction demarcation is used.MANDATORY: If a client invokes the enterprise bean's method while the client is associated with a transaction context, the container invokes the enterprise bean's method in the client's transaction context (must use the transaction of the client).NEVER: The client is required to call without a transaction context, otherwise an exception is thrown.NOT_SUPPORTED: The container invokes an enterprise bean method whose transaction attribute NOT_SUPPORTED with an unspecified transaction context (don’t need transactions, may improve performance).REQUIRED (default): If a client invokes the enterprise bean's method while the client is associated with a transaction context, the container invokes the enterprise bean's method in the client's transaction context.REQUIRES_NEW: The container must invoke an enterprise bean method whose transaction attribute is set to REQUIRES_NEW with a new transaction context.SUPPORTS: If the client calls with a transaction context, the container performs the same steps as described in the REQUIRED case (you should use the Supports attribute with caution).

52

Page 53: School - users.nik.uni-obuda.hu

53

“A” Business Operation { .. } “B” Business Operation { .. }

REQUIRED / MANDATORY / SUPPORTS

“A” Transaction

“A” Business Operation { .. } “B” Business Operation { .. }

REQUIRED / REQUIRES_NEW

“B” TransactionNo Transaction

“A” Business Operation { .. } “B” Business Operation { .. }

REQUIRES_NEW

“A” Transaction “B” Transaction

“A” Business Operation { .. } “B” Business Operation { .. }

NOT_SUPPORTED

“A” Transaction

“A” Business Operation { .. } “B” Business Operation { .. }

NOT_SUPPORTED / SUPPORTS / NEVER

No Transaction

No TransactionNo Transaction

Page 54: School - users.nik.uni-obuda.hu

Hiba esetek

54

“A” Business Operation { .. } “B” Business Operation { .. }

NEVER

RemoteException“A” Transaction

“A” Business Operation { .. } “B” Business Operation { .. }

MANDATORY

No Transaction TransactionRequiredException

Page 55: School - users.nik.uni-obuda.hu

Hogyan kapunk JSON hiba választ?

● Regisztrálva van egy olyan @Provider, mely ezt a kivételt átalakítja egy Response-á.

● A JAX-RS RESTful service kivételt dob.● A kliens a kivételből átalakított Response-t

fogja megkapni.

55

Page 56: School - users.nik.uni-obuda.hu

RESTful átalakítás

56

@Providerpublic class AdaptorExceptionMapper implements ExceptionMapper<AdaptorException> {

@Contextprivate HttpHeaders headers;@Overridepublic Response toResponse(AdaptorException e) {

return Response.status(e.getErrorCode().getHttpStatusCode() ).entity(e.build()) //

.header(SchoolCrossOriginRequestFilter.ALLOW_ORIGIN, "*") //

.header(SchoolCrossOriginRequestFilter.ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS, HEAD") //

.header(SchoolCrossOriginRequestFilter.MAX_AGE, "1209600") //

.header(SchoolCrossOriginRequestFilter.ALLOW_HEADERS, "x-requested-with, origin, content-type, accept, X-Codingpedia, authorization") //

.header(SchoolCrossOriginRequestFilter.ALLOW_CREDENTIALS, "true") //

.type(MediaType.APPLICATION_JSON ).build();}}

AdaptorExceptionMapper.java

AdaptorException build() metódus hívás!

Page 57: School - users.nik.uni-obuda.hu

CrossOriginRequestFilter

● Célja hogy a szolgáltatást meg lehessen hívni idegen host-ról.

● A Servlet API részeként definiálható Filter-ként érdemes megvalósítani.

● A HTTP Header-ben szükség néhány mező értékét beállítani (a példában mindent engedélyezünk).

● A REST hívások előtt egy HTTP OPTIONS method-dal “ping”-el a kliens, tesztelve hogy távolról el tudja-e érni a szolgáltatást. → ezért lett felvéve a REST service-ekbe egy általános választ adó művelet:

57

@OPTIONS

@Path("{path:.*}")

Response optionsAll(@PathParam("path") String path);

Page 58: School - users.nik.uni-obuda.hu

Debug Remote JVM (JBoss)

JBoss default debug-port: 8787

58

> [JBOSS_HOME]/bin/standalone.[bat|sh] --debug> [JBOSS_HOME]/bin/standalone.[bat|sh] --debug [DEBUG-PORT]

Bármely JVM-et lehet remote debug-olni (Java-nak kell az alábbi argumentumokat átadni (az -Xdebug a régebbi JVM beállítása, de az újabbak is felismerik):

-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=[DEBUG-PORT]-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=[DEBUG-PORT]

Listening for transport dt_socket at address: 8787

console/terminal

Page 59: School - users.nik.uni-obuda.hu

Eclipse - Debug

Run | Debug Configurations |● Remote Java Application

○ Helyi menü: New○ Project: Browse.. (egyébként ez lényegtelen)○ Connection Type: Standard (Socket Attach)

■ Host: localhost■ Port: 8787

○ Apply és Debug● Debug Perspective-ra váltás

○ Debug view-ban látni kell a thread-eket○ Ugyanitt: Helyi menü: Edit source lookup

■ Add Java Project(s)59

Page 60: School - users.nik.uni-obuda.hu

Egység tesztek készítése

Unit teszt írásának általános szabályai● Bedők Dávid: Programozási feladatok

megoldási módszertana (Óbudai Egyetem, 2015) 5.2 fejezet: Egység tesztelés

Mockolási technikák● Jelen labor keretein belül elsősorban ezen

lesz a hangsúly● Feltételezi az általános egység teszt írás

szabályainak ismeretét és alapvető technikáját

60

Page 61: School - users.nik.uni-obuda.hu

TestNG

● src/test/java könyvtár (source folder)● MarkFacadeImpl osztállyal azonos

package szinten létrehozzuk az osztályhoz készített egység tesztet.

61

package hu.qwaevisz.school.ejbservice.facade;

import org.testng.Assert;

import org.testng.annotations.Test;

public class MarkFacadeImplTest {

@Test

public void someTestMethod() {

Assert.assertEquals( true, true);

}

}

MarkFacadeImplTest.java

Page 62: School - users.nik.uni-obuda.hu

Mockitohttp://mockito.org/

Egy adott osztály egység tesztjében minden olyan osztályt, melyet ő felhasznál, mock-olni/fake-elni szükséges.

● Elsősorban azért, mert ha valós osztályként használnánk azokat, akkor ha a felhasznált osztályban hiba van, akkor nem csak annak az egység tesztje bukna el, hanem azok az osztályoknak az egység tesztjei is, melyek őt felhasználják.

● Kivételek mindig előfordulhatnak, ezt megfelelő egység teszt írási tapasztalat után a szakember érezni fogja.

62

ext {

testngVersion = '6.9.+'

mockitoVersion = '1.10.8'

}

subprojects {

dependencies {

testCompile group: 'org.testng', name: 'testng', version: testngVersion

testCompile group: 'org.mockito', name: 'mockito-core', version: mockitoVersion

}

}

build.gradle

Page 63: School - users.nik.uni-obuda.hu

Jegy statisztika egység teszteléseFacade rétegben

Mi a metódus felelőssége ebben a rétegben?● Egy bemeneti tantárgy megnevezés alapján előállítani egy kimeneti

MarkDetail stub listát.● A tárgy neve alapján kikérni a hozzá tartozó ID-t a persistence rétegtől

(hogy létezik-e az adott tantárgy)● Tantárgy ID alapján lekérni a hozzá tartozó statisztikát a persistence

rétegtől.● Kérni a konverziót az erre a célra szolgáló service-től hogy az

persistence réteg eredményéből Stub készüljön.● Ha hiba keletkezik a persistence rétegben, akkor nem várt hibát jelez.● Nem felelőssége ezen kívül semmi más (pl. a lekérdezés vagy a

konverzió mikéntje, részletei)!

63

List<MarkDetailStub> getMarkDetails(String subject) throws AdaptorException

Page 64: School - users.nik.uni-obuda.hu

PéldaInjectMocks és Mock annotációk használata

64

public class MarkFacadeImplTest {

@InjectMocks

private MarkFacadeImpl facade;

@Mock

private StudentService studentService;

@Mock

private SubjectService subjectService;

@Mock

private MarkService markService;

@Mock

private MarkConverter markConverter;

@BeforeMethod

public void setup() {

MockitoAnnotations.initMocks(this);

}

@Test

public void createListOfMarkDetailsFromSubjectName() throws AdaptorException, PersistenceServiceException {

}

}

MarkFacadeImplTest.java

Az @InjectMocks annotációval az az egyetlen osztály rendelkezik, melyet az adott egység tesztben tesztelünk. Ide kell a mockokat a keretrendszernek “inject”-álnia.

Az @Mock annotációval azok az osztályok szerepelnek, melyeknek egy fake-et/mock-ot kell gyártani, és melyek be lesznek inject-álva a tesztelendő osztályba.

Az MockitoAnnotataions.initMocks(this) nagyon fontos, hogy minden teszt metódus előtt lefusson. Ez végzi el az inject-álást. Ősteszt osztályba áthelyezhető a kódsor.

Page 65: School - users.nik.uni-obuda.hu

PéldaGyakorlati példa

65

public void createListOfMarkDetailsFromSubjectName() throws AdaptorException, PersistenceServiceException {

Subject subject = Mockito.mock(Subject.class);

Mockito.when(this.subjectService.read(SUBJECT_NAME)).thenReturn(subject);

Mockito.when(subject.getId()).thenReturn(SUBJECT_ID);

[..]

List<MarkDetailStub> stubs = new ArrayList<>();

MarkDetailStub neumannStub = Mockito.mock(MarkDetailStub.class);

stubs.add(neumannStub);

Mockito.when(this.markConverter.to(results)).thenReturn(stubs);

List<MarkDetailStub> markDetailStubs = this.facade.getMarkDetails(SUBJECT_NAME);

Mockito.verify(this.markService).read(SUBJECT_ID);

Assert.assertEquals(markDetailStubs.size(), stubs.size());

Assert.assertEquals(markDetailStubs.get(0), neumannStub);

[..]

}

MarkFacadeImplTest.java

Page 66: School - users.nik.uni-obuda.hu

Tipikus forgatókönyv

Subject subject = Mockito.mock(Subject.class);

● Létrehoz egy Subject mock-ot (a @Mock annotáció is ilyet hoz létre, azonban utóbbit inject-álja is a tesztelendő osztályba, ha erre kérjük).

Mockito.when(this.subjectService.read(SUBJECT_NAME)).thenReturn(subject);

● Felkészít egy mock-ot. Jelen esetben ha a read() metódusát egy adott String paraméterrel meghívjuk, adja vissza a “subject” példányt (ami egy mock, de ez lehet valós osztálypéldány is, vagy pl. érték).

Mockito.verify(this.markService).read(SUBJECT_ID);

● Ellenőrzi a tesztelendő metódus meghívása után a read() metódus meghívását a service mock-ján a megadott String példány paraméterrel. Ha nem hívódik meg (pontosan egyszer), akkor a teszt elbukik, mivel a hívás elvárt!

66

Page 67: School - users.nik.uni-obuda.hu

Advanced Mockito

● Lehetőség van when() során kivétel dobására (utóbbit a void visszatérési értékű metódusok is megtehetik).

● Van lehetőség Matcherek segítségével nem pontos értéket átadni paramétereknek, hanem pl. csak az a fontos hogy String osztály példánya legyen. Tetszőlegesen kombinálható mindez.

● Tudunk belső argumentumokat “elkapni” (mivel hívták meg a mock metódusát), majd az egység tesztben erre pl. egy Assert-et írni.

● Megadható pontosan hányszor hívtak meg egy metódust verify() során.

● Megadható when() során ha ugyanazt a metódus többször hívják, sorban miket adjon vissza eredményül.

● stb. 67

Page 68: School - users.nik.uni-obuda.hu

MockitoDo not overengineering

Természetesen a Mockito osztálykönyvtár/library számos egyéb lehetőséget tartalmaz, azonban nem szabad megfeledkezni arról sem, ha túlságosan “mélyen” teszteljük a vizsgált osztályt, akkor az nagyon érzékeny lesz az apróbb módosításokra is (nehezebben lesz refaktorálható). E miatt pl. a verify() használatát ahol lehet mellőzzük (a bemutatott példában pl. teljesen szükségtelen).

68

Page 69: School - users.nik.uni-obuda.hu

Szolgáltatás69

Diák jegyeinek lekérdezéseszűrési feltételekkel

POST http://localhost:8080/school/api/mark/get/{neptun}

Page 70: School - users.nik.uni-obuda.hu

addMark()

70

<markcriteria>

<subject>Python</subject>

<minimumgrade>2</minimumgrade>

<maximumgrade>4</maximumgrade>

</markcriteria>

Request payload

REQ: POST http://localhost:8080/school/api/mark/get/WI53085RESP: 200 OK

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<mark>

<date>2014-09-29T04:15:34+02:00</date>

<grade>3</grade>

<note>Phasellus</note>

<subject>

<description>Fusce ...</description>

<name>Python Programming</name>

<teacher>

<name>Christine W. Culp</name>

<neptun>OK73109</neptun>

</teacher>

</subject>

</mark>

Response content

Page 71: School - users.nik.uni-obuda.hu

REST Client alkalmazás

A RESTful service meghívása teljesen nyelvfüggetlen, HTTP request-ek gyártása kell csupán hozzá, “minden” prog. nyelvből lehetséges.Azonban type-safe módon készíthetünk Java klienst, mely gyorsabb és megbízhatóbb fejlesztést tesz lehetővé, ehhez kapunk támogatást 3rd party library-któl.Megjegyzés: Java-ból is lehetne HTTP kéréseket gyártani, majd a válasz payload-ját feldolgozni String-ként, de ezzel külön nem foglalkozunk. 71

Page 72: School - users.nik.uni-obuda.hu

REST kliensGradle konfiguráció

72

jar { archiveName 'sch-restclient.jar' }

dependencies {

compile group: 'org.jboss.spec', name: 'jboss-javaee-6.0', version: jbossjee6Version

compile group: 'org.jboss.resteasy', name:'resteasy-jaxrs', version: resteasyVersion

compile group: 'org.jboss.resteasy', name:'resteasy-jaxb-provider', version: resteasyVersion

compile group: 'commons-logging', name: 'commons-logging', version: commonsloggingVersion

}

build.gradle

ext {resteasyVersion = '2.3.7.Final'jbossjee6Version = '3.0.3.Final' commonsloggingVersion = '1.2'

}

JAXB Provider: XML szerializáláshoz és deszerializáláshoz.

Page 73: School - users.nik.uni-obuda.hu

REST “remote” interface

73

@Path("/mark")

public interface MarkRestService {

@POST

@Consumes("application/xml")

@Produces("application/xml")

@Path("/get/{neptun}")

ClientResponse<MarkStub> getMarks(@PathParam("neptun") String studentNeptun, MarkCriteria criteria);

}

MarkRestService.java

A server oldali MarkRestService ettől eltérő! Pl. a Consumes/Produces részek is külön-külün vezérelhetőek (de ez esetben biztosítani kell a (de)serializáláshoz szükséges library-kat!@POST@Consumes("application/xml")@Produces("application/xml")@Path("/get/{studentneptun}")MarkStub getMatchingMark(@PathParam("studentneptun") String studentNeptun, MarkCriteria criteria) throws AdaptorException;

A ClientResponse<T> osztály alkalmas arra, hogy a HTTP header részeit is fel tudjuk dolgozni kliens oldalon (pl. HTTP Response Code).

Page 74: School - users.nik.uni-obuda.hu

REST hívás

74

public MarkStub process(String studentNeptun, MarkCriteria criteria) {

URI serviceUri = UriBuilder.fromUri( "http://localhost:8080/school/api").build();

ClientRequestFactory crf = new ClientRequestFactory(serviceUri);

MarkRestService api = crf.createProxy( MarkRestService.class);

ClientResponse<MarkStub> response = api.getMarks(studentNeptun, criteria);

LOGGER.info("Response status: " + response.getStatus());

MultivaluedMap<String, Object> header = response.getMetadata();

for (String key : header.keySet()) {

LOGGER.info("HEADER - key: " + key + ", value: " + header.get(key));

}

MarkStub entity = response.getEntity();

LOGGER.info("Response entity: " + entity);

return entity;

}

SchoolRestClient.java

response.getMetadata();

response.getEntity();