Elosztott alkalmazások készítése

Magas szintű adatbáziselérés

JPA

Írjunk egy olyan BookStore osztályt, ami JPA-n keresztül végzi az előbbi feladatban készített Book entitások kezelését. Valósítsuk meg a következő műveleteket:

// Konstruktor, egy persistence unit nevét kapja, és létre is hoz egy neki
// megfelelő `EntityManager`-t.
BookStore(String persistenceUnitName);

// Új könyvet visz fel az adatbázisba, és visszaadja a kapott azonosítóját.
int addBook(Book newBook);

// Törli a megadott azonosítójú könyvet, ha már szerepel az adatbázisban.
void removeBookById(int bookId);

// Lekérdezi az összes könyvet az adatbázisból.
java.util.List<Book> getAllBooks();

Készítsünk egy parancssori alkalmazást, ami teszteli az új BookStore osztályt.

JPA relációk

Készítsünk egy Author entitást, amely egy könyv szerzőjét képes reprezentálni:

Készítsünk egy, az eddigiekhez hasonló Book entitást, ami egy könyv következő adatait képes tárolni (jelenleg csak egy szerzője lehet!):

Írjuk felül az equals() és a hashCode() műveleteket úgy, hogy az elsődleges kulcs alapján működjenek!

Alakítsuk át az Author és Book entitásainkat úgy, hogy képesek legyenek egy könyvhöz több szerzőt is tárolni! Valósítsunk meg egy parancssoros alkalmazást, ami képes az alábbiakra:

// Szerző hozzáadása
Author addAuthor(String name, Date birthDate /* lehet null */);

// Szerző törlése
void removeAuthor(Author author);

// Könyv hozzáadása (legalább egy szerzővel, leltári állapot legyen `UNKNOWN`)
Book addBook(String title, Author author);

// Könyv társítása szerzőhöz
void assignAuthorToBook(Author author, Book book);

// Könyv törlése
void removeBook(Book book);`

Az entitásokat egy lokális books nevű adatbázisban tároljuk. A szerzők és könyvek közötti kapcsolaton próbáljuk ki a különböző CascadeType beállításokat!

JPA feladat

Készíts olyan JPA programot, amely egy egyetemet modellez az alábbiak szerint. Az egyetemnek vannak hallgatói (Student) és oktatói (Professor), valamint vannak kurzusok (Course). Mindegyik rendelkezik automatikusan generált azonosítóval (id) és névvel (name). A hallgatók a kurzusokat hallgatják (studies, egy hallgató több kurzust is hallgathat), az oktatók oktatják (teaches, egy oktató több kurzust is oktathat). A kurzusokhoz ismert a tárgy neve (subjectName, de még elegánsabb külön Subject entitás felvételével), valamint az, hogy mikor kezdődik: dayOfWeek (ez DayOfWeek felsorolási típusként ábrázolva), hour, minute.

Az adatok egyszerű feltöltéséhez legyenek student.csv, professor.csv, course.csv fájljaink, ezek soronként, vesszővel elválasztva tartalmazzák egy-egy entitás adatait. A program kezdetben töltse fel a university persistence unitot a fájlokból felolvasott adatokkal, majd a sztenderd bemenetről várt sorok utasítanak a következőkre. (A nagybetűs részek változtatható paraméterek, a kisbetűsek fix részei a parancsoknak. Feltételezhető, hogy a bemeneten csak érvényes parancsok érkeznek, nem szükséges a hibás eseteket – pl. hiányzó paraméterek – kezelni.)

  1. day-off S: kilistázza azokat a napokat, amelyeken S hallgatónak nincsen órája.
  2. colleagues T: kilistázza azokat az oktatókat, akiknek van T-vel közös tárgyuk.
  3. teaches X Y: X oktató oktatja-e Y hallgatót?
  4. kind X: az X névről megmondja, hogy hallgató, oktató vagy kurzus neve-e, vagy nincsen a rendszerben. A sztenderd kimenetre ennek megfelelően íródjon ki a student, Professor, course, unknown szöveg. Az egyszerűség kedvéért tegyük fel, hogy a négy lehetőség közül pontosan egy teljesül.
  5. merge C1 C2: a C1 kurzus megszűnik, a hallgatói a C2 kurzust hallgatják a továbbiakban.
  6. swap C1 C2: a két kurzus oktatói cserélődjenek fel.
  7. between D H1 H2: kilistázza a D napon H1 óra és H2 óra között kezdődő órák neveit. H2 óra 0 perc már nem értendő bele az intervallumba.

Webkonténer

Szervlet

Készítsünk egy webalkalmazást, ami egy ScopeTest nevű szervletet tartalmaz. A szervlet legyen elérhető a /test URL-en keresztül a webalkalmazás gyökeréhez képest. A szervlet a következőket tegye:

A szervlet működését próbáljuk ki egyszerre több, különböző böngészőben, vagy a munkamenet sütiket időnként eltávolítva; illetve az alkalmazásszervert közben újraindítva is! Ezek az események a különböző élettartamú attribútumok megszűnését eredményezik, így megfigyelhető viselkedésük.

Webalkalmazás

Készítsünk egy webalkalmazást, ami egy FormTest nevű szervletet tartalmaz. A szervlet legyen elérhető a /form URL-en keresztül a webalkalmazás gyökeréhez képest. A szervlet a következőket tegye:

A megfelelően elkészített alkalmazás a POST-Redirect-GET mechanizmust szemlélteti. A felhasznált munkamenet változó azt a gyakran “flash scope”-nak is nevezett objektum élettartamot szemlélteti, amikor az érték csupán a következő felhasználásig marad meg a munkamenetben.

Webalkalmazás

Készítsünk egy webalkalmazást, ami a korábban látott könyv (Book) entitásokat képes egy űrlapon keresztül hozzáadni, illetve ezeket egy táblázatban listázni. Ehhez készítsünk két szervletet:

Az adatok perzisztálásához használjunk JPA-t, az adatforrás pedig az alkalmazás-szerverben legyen beállítva.

Enterprise JavaBeans

EJB: stateless EJB

Készítsünk egy szerializálható Book osztályt, ami egy könyv következő adatait képes tárolni:

Készítsünk egy BookStore nevű interfészt az alábbi műveletekkel:

// Hozzáad egy könyvet. Ha már létezik ilyen című, hamissal tér vissza,
// ellenkező esetben tényleg elmenti a könyvet, és igazat ad.
boolean addBook(Book newBook);

// Az összes könyv lekérdezése.
List<Book> getAllBooks();

// Lekérdez egy könyvet a címe alapján. Ha nem található, null-t ad vissza.
Book getBookByTitle(String title);

Implementáljuk a fenti interfészt egy stateless session bean távoli interfészeként. A bean neve legyen BookStoreBean. Tároljuk a könyveket egy hasítótáblában, a címükkel azonosítva őket (ez egy java.util.HashMap, neve books).

Teszteljük a bean működését egy parancssoros alkalmazással!

EJB: lokális EJB

Az előző feladatot egészítsük ki egy második, lokális felületű bean-nel, ami megadja egy könyvről, hogy oldalszáma 200 felett van-e:

boolean isALongBook(Book book);

Ezt az új, lokális LongBookFilterBean-t használjuk fel a BookStoreBean-ben, hogy csak azokat a könyveket adjuk vissza a getAllBooks eredményeként, amikre ez a művelet igazat ad. Az új bean-t a @EJB annotációval injektáljuk az előzőbe.

Készítsünk az előző feladatban szereplő bean-hez egy application client container-ben futó, függőség-injektálást alkalmazó klienst a teszteléshez.

EJB: stateful bean

Készítsünk egy szerializálható Book osztályt, ami egy könyv következő adatait képes tárolni:

Készítsünk egy könyvespolcot szimuláló Bookshelf nevű interfészt az alábbi műveletekkel:

// Hozzáad egy könyvet. Ha már létezik ilyen című, hamissal tér vissza,
// ellenkező esetben tényleg elmenti a könyvet, és igazat ad.
boolean addBook(Book newBook);

// "Lezárja a polcot", tehát több könyvet nem lehet rátenni.
// A lekérdezés után a megvalósító bean példány többé nem használható!
// Visszaadja a polcon lévő könyvek összesített oldalszámát.
int closeShelf();`

Implementáljuk a fenti interfészt egy stateful session bean távoli interfészeként. A bean neve legyen BookshelfBean. Tároljuk a könyveket egy hasítótáblában, a címükkel azonosítva őket (ez egy java.util.HashMap, neve books).

Teszteljük a bean működését egy parancssoros alkalmazással!

EJB: session bean

Készítsünk egy ShelfPages entitást, ami az alábbi mezőkből áll:

Az előző feladatot egészítsük ki egy ShelfStatistics interfészű bean-el, ami az alábbi távoli műveletet támogatja:

// Megadja minden korábbi polchoz, hogy mennyi volt a rajtuk lévő könyvek
// összesített darabszáma.
List<ShelfPages> getStatistics();

Az implementáció egy állapotmentes session bean-ben kapjon helyet. Az adatokat JPA-val perzisztáljuk, egy lokális ShelfPersistence interfészen keresztül:

// Elmenti az adott oldalszámot egy új `ShelfPages` entitás segítségével.
void saveNewShelfWith(int totalPages);`

A BookshelfBean ezen a lokális interfészen keresztül tudja lementeni az adatokat. Az előző klienst egészítsük ki úgy, hogy a statisztika is lekérdezhető legyen!

EJB: interceptor

Adjunk egy olyan interceptor metódust a BookshelfBean-hez, ami az addBook metódus végrehajtása esetén naplózza a 200 oldalnál hosszabb könyvek címét, ezen kívül pedig teljesen transzparens a működése.

EJB: singleton bean

Készítsünk egy singleton session bean-t, ami különböző számlálókat képes karbantartani, és értéküket lekérdezni. A következő Counters interfészen keresztül legyen elérhető:

// Megnöveli egy számláló értékét 1-el. Ha nem létezik számláló a megadott
// néven, akkor hozzon létre egy újat, és értéke legyen 1.
void increment(String name);

// Visszaadja egy számláló értékét. Ha nincs ilyen néven számláló, akkor nullát
// adjon vissza.
int getValue(String name);`

A számlálók tárolását JPA-val valósítsuk meg egy adatbázisban. A bean működjön gyorsítótárként, az alábbiak szerint:

Használjuk a konténer által biztosított konkurenciakezelést, az adatokat író és olvasó metódusokat lássuk el a megfelelő annotációkkal.

Parancssoros klienssel teszteljük a megoldást.

EJB: singleton + konkurencia

Készítsük el az előző feladatban létrehozott singleton session bean egy olyan változatát, ami a bean által kezelt konkurenciát alkalmaz.

EJB: tranzakciók, kivételek

Készítsünk egy alkalmazást, ami bemutatja a @TransactionAttribute annotáció eltérő értékeinek működését!

Ennek tesztelésére készítsünk két stateless session bean-t:

Készítsünk egy parancssori klienst a WrapperBean elérésére.

Java Enterprise Edition egyéb eszközei

JMS

Készítsünk egy alkalmazást, ami teszteli a normál és a tartós feliratkozás (durable subscription) közötti különbséget!

Egy servlet egy űrlap elküldésének hatására írjon üzeneteket egy JMS témába (topic). Ugyanezt a témát olvassuk egy olyan klienssel, aminek paraméterként megadható, hogy normál vagy tartós feliratkozást végezzen.

Küldjünk üzeneteket a témára, miközben a kliens alkalmazás fut, illetve akkor is, mikor nem. Figyeljük meg a normál és tartós feliratkozás közötti különbséget!

JAX-RS

Készítsünk egy BookService szolgáltatást, ami a books url alatt érhető el, és a korábban készített Book entitások lekérdezésére, törlésére, hozzáadására, illetve módosítására alkalmas:

Alacsony szintű adatbáziselérés

Szerializáció

Készítsünk egy szerializálható Book osztályt, ami egy könyv következő adatait képes tárolni:

Egy BookStore nevű osztályban tároljuk a könyveket egy hasítótáblában, a címükkel azonosítva őket (ez egy java.util.HashMap, neve books). Az osztály valósítsa meg az alábbi műveleteket:

// Néhány előre létrehozott könyvet tesz a hasítótáblába, kezdeti adatnak.
void addSomeBooks();

// Betölti a egy megadott fájlból a könyveket.
// Ha sikerült, true-t ad vissza, egyébként false-t.
boolean loadBooks(String fileName);

// Kiírja egy megadott fájlba a könyveket.
// Ha sikerült, true-t ad vissza, egyébként false-t.
boolean saveBooks(String fileName);`

Készítsünk egy parancssori alkalmazást, ami kipróbálja a fenti műveleteket.

Socket

Készítsünk egy egyszerű kliens-szerver alkalmazást, amik TCP kapcsolaton kommunikálnak. A megoldáshoz használjuk fel az előző feladat Book és BookStore osztályait!

A szerver a következőket teszi egy kérés beérkezésekor:

  1. a kérésben egy könyv címe érkezik
  2. létrehoz egy BookStore példányt az előző feladatból
  3. megpróbálja betölteni a könyveket egy fájlból
  4. kikeresi a címe alapján a kért könyvet
  5. elmenti a könyveket abba a fájlba, ahol korábban kerestük

A kliens a következőket teszi:

  1. kapcsolódik a szerverhez
  2. elküldi egy könyv címét a szervernek
  3. feldolgozza a választ, amihez először egy logikai értéket olvas

RPC

Készíts egy TCP-n kommunikáló szervert, ami egy-egy külön szálon szolgálja ki a kliensek kéréseit. A kliens egy sztringet küld a szervernek, amire az visszaadja, hányszor kaptott már ugyanilyen kérést (ez egy egész szám).

Ehhez a szervernek belső állapottal kell rendelkeznie, ami például egy java.util.HashMap<String, Integer> típusú mezővel kivitelezhető. Figyeljünk oda, hogy az egyes szálak versengenek az állapot frissítéséért, ezért szinkronizálni kell a hozzáférést. (Először kiolvassa, majd megnövelve visszaírja az értéket. Ha ez nem atomi műveletként történik meg, a számláló nem lesz pontos.)

RMI

Készíts egy Java RMI-n keresztül elérhető szolgáltatást megvalósító szervert, és egy hozzá kapcsolódó klienst, ami az alábbi műveleteket valósítja meg:

// Új könyv hozzáadása. Ha szerepelt már ilyen című, adjon vissza false-t,
// egyébként true-t.
boolean add(Book newBook);

// Könyv lekérdezése cím alapján. Ha nem található, adjon null-t.
Book getByTitle(String title);

// Könyv eltávolítása cím alapján. Ha nem vol ilyen, adjon vissza false-t,
// egyébként true-t.
boolean removeByTitle(String title);`

Az interfész neve legyen BookService. Felhasználhatók az előző gyakorlaton írt Book és BookStore osztályok, vagy azoknak az alábbi, egyszerűsített változata:

// Book.java ///////////////////////////////////////////////////////////////////

import java.io.Serializable;

public class Book implements Serializable {
    public String title;
    public String author;
    public int year;
    public int pages;
}
// BookStore.java //////////////////////////////////////////////////////////////

import java.util.HashMap;

public class BookStore {
    public HashMap<String, Book> books = new HashMap<>();

    public void BookStore() {
        for (int i = 0; i < 10; ++i) {
            Book book = new Book("Book #" + i, "Author #" + i, 2000 + i, 300 + 10 * i);
            books.put(book.getTitle(), book);
        }
    }
}

Derby elérése parancssorból

  1. Indítsunk el egy Apache Derby adatbázis-szervert a startNetworkServer paranccsal.
  2. Csatlakozzunk az elindított szerverhez az ij kliens használatával.
  3. Miután az ij kliens elindult, annak promptján belül adjuk ki a következő parancsokat.

    1. Hozzunk létre egy adatbázist azáltal, hogy csatlakozunk hozzá:

      CONNECT 'jdbc:derby://localhost:1527/books;create=true';

    2. Készítsünk egy BOOK nevű táblát, amiben a könyveket tárolhatunk.
      • Ennek az entitásnak a mezői megegyeznek a korábbi gyakorlatokon készített Book osztály mezőivel.
      • Az elsődleges kulcs legyen a könyv címe, a terjedelem pedig legyen opcionális mező.
      • Használjuk ehhez a CREATE TABLE SQL parancsot.
      • A year név foglalt, ezért vagy "year" alakban kell leírnunk, vagy más nevet kell helyette választani.
    3. Jelenítsük meg a táblákat a SHOW TABLES paranccsal, majd jelenítsük meg az új táblánk oszlopait a DESCRIBE BOOK paranccsal.

    4. Szúrjunk be néhány könyvet, és listázzuk ki őket, próbáljuk ki az alapvető SQL műveleteket (SELECT, INSERT, UPDATE, DELETE).

    5. Végül állítsuk le az adatbázis-szervert a stopNetworkServer paranccsal.

+1. Alternatív megoldás: használjunk beágyazott Derby szervert, olyan módon, hogy nem indítunk adatbázis-szervert, és az ij-vel az alábbi kapcsolatot kérjük:

`CONNECT 'jdbc:derby:books;create=true';`{.java}

JDBC

Készítsünk egy JDBC-t használó alkalmazást, ami az előző gyakorlatokon készített könyv (Book) entitásokat képes perzisztálni! Ehhez használjuk az Apache Derby adatbázis-szervert. A könyvek tárolásának módját és egyéb részleteket az előző feladat tartalmazza.

Valósítsunk meg egy új BookStore osztályt úgy, hogy adatbázist használjon:

// Konstruktor, csatlakozik az adatbázishoz. Kivétel esetén hibát ír a konzolra.
BookStore(String host, int port);

// Új könyvet visz fel az adatbázisba. Hiba esetén hamisat ad, különben igaz
// logikai értéket.
boolean addBook(Book newBook);

// Töröl egy könyvet a címe alapján. Hiba esetén hamisat ad, különben igaz
// logikai értéket.
boolean removeBookByTitle(String title);

// Lekérdezi az összes könyvet az adatbázisból. Hiba esetén üres listát ad.
java.util.List<Book> getAllBooks();

JDBC

Készítsünk egy, az eddigiekhez hasonló Book entitást, ami egy könyv következő adatait képes tárolni:

A könyvek a books nevű táblában legyenek tárolva.