School. Complex Persistence Layer, JAX-RS RESTful, Mockito. Óbudai Egyetem, Java Enterprise Edition Műszaki Informatika szak Labor 6

Hasonló dokumentumok
School #gradle. Complex Persistence, JAX-RS RESTful, Mockito, Transactions, Rest Client

Webes alkalmazások fejlesztése 8. előadás. Webszolgáltatások megvalósítása (ASP.NET WebAPI)

Széchenyi István Egyetem

Java. Perzisztencia. ANTAL Margit. Java Persistence API. Object Relational Mapping. Perzisztencia. Entity components. ANTAL Margit.

Perzisztencia. ANTAL Margit. Sapientia - EMTE. ANTAL Margit Java technológiák 11. előadás Perzisztencia

Symfony kurzus 2014/2015 I. félév. Controller, Routing

CREATE TABLE student ( id int NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name varchar(100) NOT NULL, address varchar(100) NOT NULL )

Bevezető. Servlet alapgondolatok

API tervezése mobil környezetbe. gyakorlat

WCF, Entity Framework, ASP.NET, WPF 1. WCF service-t (adatbázissal Entity Framework) 2. ASP.NET kliens 3. WPF kliens

SIP. Jelzés a telefóniában. Session Initiation Protocol

Adatbázisok webalkalmazásokban

Adatbázis-kezelés ODBC driverrel

Stateless Session Bean

Enterprise JavaBeans. Ficsor Lajos Általános Informatikai Tanszék Miskolci Egyetem. Az Enterprise JavaBeans

Ficsor Lajos Általános Informatikai Tanszék Miskolci Egyetem

Alapfogalmak, WWW, HTTP

Programozási nyelvek Java

Komponensek együttműködése web-alkalmazás környezetben. Jónás Richárd Debreceni Egyetem T-Soft Mérnökiroda KFT

A Java EE 5 plattform

Junior Java Képzés. Tematika

JAVA webes alkalmazások

JPA támogatás Eclipse-ben

A Java Persistence API PersistenceAPI / 3

Előszó. Bevezetés. Java objektumok leképzése relációs adatbázisokra OJB-vel Viczián István Viczián István

C#, OOP. Osztályok tervezése C#-ban

JEE tutorial. Zsíros Levente, 2012

Enterprise JavaBeans 1.4 platform (EJB 2.0)

Adatbázis rendszerek SQL nyomkövetés

JUnit. JUnit használata. IDE támogatás. Parancssori használat. Teszt készítése. Teszt készítése

C# versus Java. Óbudai Egyetem, Java Standard Edition Mérnök Informatikus szak, BSc Labor 3. Bedők Dávid v0.4

MDAC - Microsoft Data Access Components

Adatkezelés. 11. előadás (Entity Beans)

6. rész: EJB-k tervezése és implementálása

Osztott alkalmazások fejlesztési technológiái Áttekintés

1. Gyakorlat: Telepítés: Windows Server 2008 R2 Enterprise, Core, Windows 7

A legalacsonyabb szintű tesztelés. A programot felépítő egységek tesztelése Unit: egy rendszer legkisebb önálló egységként tesztlehető része.

Teszt topológia E1/1 E1/0 SW1 E1/0 E1/0 SW3 SW2. Kuris Ferenc - [HUN] Cisco Blog -

Webes alkalmazások fejlesztése 10. előadás. Webszolgáltatások tesztelése (ASP.NET Core) Cserép Máté

Eseményvezérelt alkalmazások fejlesztése II 12. előadás. Objektumrelációs adatkezelés (ADO.NET) Giachetta Roberto

Excel ODBC-ADO API. Tevékenységpontok: - DBMS telepítés. - ODBC driver telepítése. - DSN létrehozatala. -Excel-ben ADO bevonása

BookStore #gradle. Enterprise Application, Git, EJB, EAP/EAS, Logging, PostgreSQL/MySQL, JPA, Integration testing

8. rész: Implementáció JDeveloperben

Programozási nyelvek Java

WEBFEJLESZTÉS 2. ADATBÁZIS-KEZELÉS, OSZTÁLYOK

ADATBÁZIS-KEZELÉS - BEVEZETŐ - Tarcsi Ádám, ade@inf.elte.hu

Szálkezelés. Melyik az a hívás, amelynek megtörténtekor már biztosak lehetünk a deadlock kialakulásában?

Lottery. WebLogic JMS, Jersey, JMX, JNDI. Óbudai Egyetem, Java Enterprise Edition Műszaki Informatika szak Labor 8. Bedők Dávid v0.

JavaServer Pages (JSP) (folytatás)

Flash és PHP kommunikáció. Web Konferencia 2007 Ferencz Tamás Jasmin Media Group Kft

Java-ról Kotlinra. Ekler Péter AutSoft BME AUT. AutSoft

MVC Java EE Java EE Kliensek JavaBeanek Java EE komponensek Web-alkalmazások Fejlesztői környezet. Java Web technológiák

Java és web programozás

ROS Remote Operations Service

Mobil Informatikai Rendszerek

Inventory. [gradle maven]\jbossinventory

Vizuális programozás gyakorlat

JNDI - alapok. Java Naming and Directory Interface

Tartalom. Az EJB 2.1 problémái Az EJB 3 megoldásai

Java technológiák - ANTAL Margit. komponensek. A HTTP protokoll. Webkonténerek és szervletek. Egyszerű HTTP. ANTAL Margit.

Java és web programozás

DCOM Áttekintés. Miskolci Egyetem Általános Informatikai Tanszék. Ficsor Lajos DCOM /1

Bánsághi Anna

Adabáziselérés ODBC-n keresztül utasításokkal C#-ban

C# nyelv alapjai. Krizsán Zoltán 1. Objektumorientált programozás C# alapokon tananyag. Általános Informatikai Tanszék Miskolci Egyetem

Adattípusok. Max. 2GByte

Using the CW-Net in a user defined IP network

Tartalom DCOM. Történeti áttekintés. Történeti áttekintés. Történeti áttekintés. Történeti áttekintés

Rétegezett architektúra HTTP. A hálózatfejlesztés motorját a hálózati alkalmazások képezik. TCP/IP protokoll készlet

MVC. Model View Controller

SQL/PSM kurzorok rész

Adattípusok. Max. 2GByte

Egészítsük ki a Drupal-t. Drupal modul fejlesztés

Csomag. Adatbázis-objektum Programozási eszközök gyűjteménye Két részből áll. specifikáció törzs (opcionális)

8. Gyakorlat SQL. DDL (Data Definition Language) adatdefiníciós nyelv utasításai:

OO PDO. Tehát PDO használatával, könnyen átállhatunk egy másik adatbáziskezelőre, anélkül hogy a kódot teljes egészében újraírnánk.

Collections. Összetett adatstruktúrák

Adatbázis másolás Slony-I segítségével

abkezel.java import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.sql.*; public class abkezel extends JFrame {

Java és web programozás

Segédanyag: Java alkalmazások gyakorlat

Osztályok. 4. gyakorlat

Adatbázisok* tulajdonságai

Modbus kommunikáció légkondícionálókhoz

10. Gyakorlat: Alkalmazások publikálása Remote Desktop Szervízen keresztül

Shopping. JDBC, Datasource, Jasper Report. Óbudai Egyetem, Java Enterprise Edition Műszaki Informatika szak Labor 12. Bedők Dávid v0.

A könyv tartalomjegyzéke

Tranzakciókezelés PL/SQL-ben

Segédanyag: Java alkalmazások gyakorlat

A gyakorlat során MySQL adatbázis szerver és a böngészőben futó phpmyadmin használata javasolt. A gyakorlat során a következőket fogjuk gyakorolni:

Hello Gradle. TestNG, Eclipse, IntelliJ IDEA. Óbudai Egyetem, Java Enterprise Edition Műszaki Informatika szak Labor 2. Bedők Dávid v0.

KROMESCH SÁNDOR APP FELHŐ. API-k és Webszolgáltatások a Cloudban. Magyarországi Web Konferencia November 8.

Java bevezet o Kab odi L aszl o Kab odi L aszl o Java bevezet o

WebStore. JAX-WS SOAP WebServices, Stateful Session Bean. Óbudai Egyetem, Java Enterprise Edition Műszaki Informatika szak Labor 9

Mobil Informatikai Rendszerek

Határidős accountok WiFi rendszerekhez

ios alkalmazásfejlesztés Koltai Róbert

Hálózatbiztonság Androidon. Tamas Balogh Tech AutSoft

Szerializáció. Tóth Zsolt. Miskolci Egyetem. Tóth Zsolt (Miskolci Egyetem) Szerializáció / 22

Access adatbázis elérése OLE DB-n keresztül

Átírás:

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

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

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

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

Adatbázis oldal Schema létrehozása (create-schema.sql): GIT \jboss\school\sch-persistence\database 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

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

Root gradle project version = '1.0' ext {... jaxrsversion = '2.0.1' school/build.gradle A kimeneti ear nevében a version információ bekerül. Tetszőleges szabad-szöveges érték lehet.... webservicearchivename = 'sch-webservice.war' webservicecontextpath = 'school' dependencies { deploy project('sch-ejbservice') deploy project('sch-persistence') 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. deploy project(path: 'sch-weblayer', configuration: 'archives') deploy project(path: 'sch-webservice', configuration: 'archives') 7

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

Subject-Teacher relation 1 tantárgynak 1 oktatója van @Entity @Table(name = "subject") public class Subject implements Serializable { 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) @ManyToOne(fetch = FetchType.EAGER, optional = false) @JoinColumn(name = "subject_teacher_id", referencedcolumnname = "teacher_id", nullable = false) private Teacher teacher; A @JoinColumn és @Column annotációk... hasonlóan a @Table annotációhoz a fizikai adatbázissal való kapcsolatot szolgálják. 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. 9

Student-Mark relation 1 diáknak számos jegye van @Entity @Table(name = "student") public class Student implements Serializable { Student.class @OneToMany(fetch = FetchType.LAZY, targetentity = Mark.class, mappedby = "student") private final Set<Mark> marks; public Student() { this.marks = new HashSet<>(); 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). 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). 10

Subject-Mark relation 1 tantárgyhoz is számos jegy tartozik @Entity @Table(name = "subject") public class Subject implements Serializable { Subject.class @OneToMany(fetch = FetchType.LAZY, targetentity = Mark.class, mappedby = "subject") private final Set<Mark> marks; public Subject() { this.marks = new HashSet<>(); 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). 11

Mark entitás Student-Subject N-M kapcsolótábla @Entity Mark.class @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;... 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. 12

Java API for RESTful WebServices JAX-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

sch-webservice child project apply plugin: 'war' build.gradle 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 14

RESTful services StudentRestService StudentStub getstudent( String neptun ) List<StudentStub> getallstudent( ) removestudent( String neptun ) MarkRestService List<MarkDetailStub> getmarkdetails( String subject ) MarkStub addmark( MarkInputStub stub ) 15

Hallgató adatainak lekérdezése GET http://localhost:8080/school/api/student/{neptun Szolgáltatás 16

Diák adatainak lekérdezése { "name": "Juanita A. Jenkins", "neptun": "WI53085", "institute": "BANKI", "marks": [{ REQ: GET http://localhost:8080/school/api/student/wi53085 RESP: 200 OK "subject": {, "name": "C/C++ Programming", "teacher": {, "name": "Lorine B. Pine", "neptun": "MD21921" "description": "Maecenas..." "grade": 2, "note": "Vivamus", "date": 1401956934000,...] EPOCH timestamp 17

getstudent() Kezdjük a legegyszerűbb esettel :-) 1. webservice: StudentRestService a. StudentStub getstudent( String neptun ) throws AdaptorException; b. StudentStub, MarkStub, SubjectStub, TeacherStub 2. webservice: StudentRestServiceBean a. call ejbservice layer 3. ejbservice: StudentFacade a. StudentStub getstudent( String neptun ) throws AdaptorException; 4. ejbservice: StudentFacadeImpl a. call persistence layer b. call converter service 5. persistence: StudentService a. Student read( String neptun ) throws PersistenceServiceException; 6. persistence: StudentServiceImpl a. 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: StudentConverter a. ejbservice: StudentConverterImpl b. ejbservice: MarkConverter c. ejbservice: MarkConverterImpl 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. 18

Generált natív lekérdezés SELECT student0_.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_ FROM student 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 WHERE student0_.student_neptun=? 19

Összes hallgató adatainak lekérdezése GET http://localhost:8080/school/api/student/list Szolgáltatás 20

getallstudents()... for (final Student student : result) {... REQ: GET http://localhost:8080/school/api/student/list RESP: 200 OK student.getmarks().size(); StudentServiceImpl.java 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! Named Query: StudentQuery.GET_ALL SELECT s FROM Student s ORDER BY s.name 21

Átlag eredmény statisztika POST http://localhost:8080/school/api/mark/stat Szolgáltatás 22

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

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). REQ: POST http://localhost:8080/school/api/mark/stat RESP: 200 OK Payload Sybase PowerBuilder [{, {, {, { ] "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 24

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

Natív lekérdezés SELECT markdetail.student_institute_id, markdetail.mark_year, AVG(markdetail.mark_grade) FROM ( SELECT mark_subject_id, student_institute_id, mark_grade, DATE_TRUNC('year', mark_date) AS mark_year FROM mark INNER JOIN student ON ( mark_student_id = student_id ) WHERE ( 1 = 1 ) ) AS markdetail WHERE ( 1 = 1 ) AND ( markdetail.mark_subject_id = 2 ) GROUP BY markdetail.student_institute_id, markdetail.mark_year ORDER BY markdetail.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. 26

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

DB View 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. 28

VIEW tesztelése SELECT FROM markdetail_institute_id, markdetail_year, AVG(markdetail_grade) 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. 29

View az ORM rétegben Ugyanolyan entitás mint a tábla @Entity @Table(name = "markdetail") public class MarkDetail implements Serializable { @Id @Column(name = "markdetail_id", nullable = false) private Long id; MarkDetail.java Primary Key kötelező @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; 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. 30

Á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 31

Lekérdezés eredménye public class MarkDetailResult { MarkDetailResult.java private final Institute institute; private final Date year; private final double averagegrade; Nem entitás, egyszerű DTO. A konstruktor üzletileg fontos, nem kell default ctor-nak lennie (entitásnál kötelező). { public MarkDetailResult(Institute institute, Date year, double averagegrade) this.institute = institute; this.year = year; this.averagegrade = averagegrade;... 32

Result példány Named Query-ben a Result példány létrehozása: SELECT new hu.qwaevisz.school.persistence.result.markdetailresult(m d.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 A Full Qualified Name megadása szükséges (mivel nincs lehetőség import -ra. 33

Átlagszámítás - Részletek II. ejbservice: MarkConverter, MarkConverterImpl getyearfromdate (Java konverzió) @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) { MarkConverterImpl.java 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); 34

Új érdemjegy rögzítése PUT http://localhost:8080/school/api/mark/add Szolgáltatás 35

addmark() REQ: PUT http://localhost:8080/school/api/mark/add RESP: 200 OK { "subject": "Sybase PowerBuilder", Request payload "neptun": "WI53085", "grade": "WEAK", "note": "Lorem ipsum" { Response content "subject": { "name": "Sybase PowerBuilder", "teacher": { "name": "Richard B. Cambra", "neptun": "UT84113", "description": "Donec", "grade": 2, "note": "Lorem ipsum", "date": 1443797867042 36

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) 37

Rögzítés ORM oldalon @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 flush(): empty the internal SQL instructions cache 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. 38

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) Total: 11 SELECT + 1 INSERT 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) 39 Insert the new Mark (+1 INSERT)

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

Hallgató törlése DELETE http://localhost:8080/school/api/student/{neptun Szolgáltatás 41

removestudent() Hibakezelés bemutatása REQ: DELETE http://localhost:8080/school/api/student/abc123 RESP: 400 Bad Request Response content { "code": 40, "message": "Resource not found", "fields": "ABC123" REQ: DELETE http://localhost:8080/school/api/student/wi53085 RESP: 412 Precondition Failed Response content { "code": 50, "message": "Has dependency", "fields": "WI53085" REQ: DELETE http://localhost:8080/school/api/student/tx78476 RESP: 204 No Content 42

HTTP Status Codes http://www.w3.org/protocols/rfc2616/rfc2616-sec10.html 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 43

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 Named Query mellett megvalósíható EntityManager műveletként is a törlés. 44

Publikus hiba üzenet: ErrorStub (json) public class ErrorStub { ErrorStub.java private int code; private String message; private String fields; Muszáj hogy getter/setter metódusok is legyenek a Stub-ban, így a mezők ajánlottan ne legyen final-ek. public ErrorStub(int code, String message, String fields) { this.code = code; this.message = message; this.fields = fields;... 45

Hibalehetőségek, publikus infok public enum ApplicationError { ApplicationError.java 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; Az enum példány az ErrorStub factory-ja! 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); 46

Hibaágak kezelése Üzleti hibák detektálása és átalakítása nyilvános hibaüzenetté StudentFacadeImpl.java @Override public 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()); 47

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

Persistence Layer Modifications StudentServiceImpl.java PersistenceApplicationError.java package hu.qwaevisz.school.persistence.util; public enum PersistenceApplicationError { UNEXPECTED, NOT_EXISTS, HAS_DEPENDENCY; public class StudentServiceImpl implements StudentService { @EJB private MarkService markservice; We have to use the same attribute in the MarkServiceImpl count(studentneptun) business method. @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public boolean exists(string neptun) throws PersistenceServiceException { [..] AdvancedPersistenceServiceException.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; 49

Persistence Layer New Student delete Business method StudentServiceImpl.java public class StudentServiceImpl implements StudentService { @Override public 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); 50

EJB Service Layer New Student delete Business method package hu.qwaevisz.school.ejbservice.facade; StudentFacadeImpl.java public class StudentFacadeImpl implements StudentFacade { @Override public 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()); 51

Transaction Attributes The 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

A Business Operation {.. B Business Operation {.. A Transaction REQUIRED / MANDATORY / SUPPORTS NOT_SUPPORTED A Business Operation {.. B Business Operation {.. A Transaction No Transaction REQUIRED / REQUIRES_NEW A Business Operation {.. B Business Operation {.. No Transaction B Transaction NOT_SUPPORTED / SUPPORTS / NEVER A Business Operation {.. B Business Operation {.. No Transaction No Transaction REQUIRES_NEW A Business Operation {.. B Business Operation {.. A Transaction B Transaction 53

Hiba esetek MANDATORY A Business Operation {.. B Business Operation {.. No Transaction TransactionRequiredException NEVER A Business Operation {.. B Business Operation {.. A Transaction RemoteException 54

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

RESTful átalakítás @Provider public class AdaptorExceptionMapper implements ExceptionMapper<AdaptorException> { @Context private HttpHeaders headers; @Override public Response toresponse(adaptorexception e) { return AdaptorExceptionMapper.java AdaptorException build() metódus hívás! 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(); 56

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: @OPTIONS @Path("{path:.*") Response optionsall(@pathparam("path") String path); 57

Debug Remote JVM (JBoss) > [JBOSS_HOME]/bin/standalone.[bat sh] --debug > [JBOSS_HOME]/bin/standalone.[bat sh] --debug [DEBUG-PORT] JBoss default debug-port: 8787 console/terminal Listening for transport dt_socket at address: 8787 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] 58

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

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

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. MarkFacadeImplTest.java 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); 61

Mockito http://mockito.org/ ext { 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. build.gradle 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 62

Jegy statisztika egység tesztelése Facade rétegben List<MarkDetailStub> getmarkdetails(string subject) throws AdaptorException 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

Példa InjectMocks és Mock annotációk használata 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() { @Test MockitoAnnotations.initMocks(this); 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. 64

Példa Gyakorlati példa 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); MarkFacadeImplTest.java 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); [..] 65

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)).thenR eturn(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

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

Mockito Do 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

Diák jegyeinek lekérdezése szűrési feltételekkel POST http://localhost:8080/school/api/mark/get/{neptun Szolgáltatás 69

addmark() REQ: POST http://localhost:8080/school/api/mark/get/wi53085 RESP: 200 OK Request payload <markcriteria> <subject>python</subject> <minimumgrade>2</minimumgrade> <maximumgrade>4</maximumgrade> </markcriteria> <?xml version="1.0" encoding="utf-8" standalone="yes"?> <mark> </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> Response content 70

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

REST kliens Gradle konfiguráció jar { archivename 'sch-restclient.jar' build.gradle 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 JAXB Provider: XML szerializáláshoz és deszerializáláshoz. ext { resteasyversion = '2.3.7.Final' jbossjee6version = '3.0.3.Final' commonsloggingversion = '1.2' 72

REST remote interface @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 ClientResponse<T> osztály alkalmas arra, hogy a HTTP header részeit is fel tudjuk dolgozni kliens oldalon (pl. HTTP Response Code). 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; 73

REST hívás public MarkStub process(string studentneptun, MarkCriteria criteria) { URI serviceuri = UriBuilder.fromUri( "http://localhost:8080/school/api").build(); SchoolRestClient.java 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(); response.getmetadata(); LOGGER.info("Response entity: " + entity); response.getentity(); return entity; 74