Lottery JMS, Message Driven Bean, JMX, Singleton Session Bean Óbudai Egyetem, Java Enterprise Edition Műszaki Informatika szak Labor 7 Bedők Dávid 2016.10.04. v0.9
JBoss management console http://localhost:9990/ Létre kell hozni egy management user-t ehhez. [JBOSS-HOME]/bin/add-user.[bat sh] Management User (enter) Username: admin (are you sure? yes) Password: AlmafA11# Enter, Yes, Yes, Enter http://localhost:9990/ Try Again és Login (BASIC AUTH) > /jboss-eap-6.4/bin/add-user.sh admin AlmafA11# 2
Feladat Készítsünk egy szolgáltatást, mely tárolja az ötös lottóhúzás eredményeit, ezek időpontját, nyereményalapját és a számot kihúzó személy nevét (legyen kinek megköszönni). A kihúzott számokat egy JMS queue interface-en* keresztül fogja a szolgáltatás megkapni (machine-to-machine interface). RESTful interface-en keresztül adjunk lehetőséget arra, hogy lekérdezzük az aktuális, illetve az összes sorsolás adatát. * itt most interface alatt két rendszer közötti kommunikációs réteget értve 3
Kiegészítés RESTful interface-en keresztül lehessen 5 szám megadásával megkapni a nyereményt! Minden sorsolásnak van egy nyereményalapja, melyet 100%-ban kiosztanak a nyertesek között. A szolgáltatás tartsa karban, hogy az aktuális jogszabályok szerint e nyereményalap mennyi százaléka jár az 1, 2, 3, 4 és 5 találatos játékosoknak. Megjegyzés: Mindez természetesen egyszerűsítés és nem is garantálja a helyes szétosztást, hiszen nem számol azzal, hogy pl. mennyi öt találatos szelvény volt. A nyereményeloszlás százalékos paramétereit szabvány management felületen (JMX) lehessen konfigurálni! 4
Ismeretszerzés Java Message Service (JMS) JMS Queue JMS Topic (feladat nem érinti) Message Driven Bean (listener) JMS Client Java Management extension (JMX) Standard Management Bean (MBean) jconsole Singleton Session Bean speciális Statefull Session Bean EntityManager persist művelet JOIN FETCH 5
Project struktúra lottery (root project) lot-jmsclient (JMS client alkalmazás) lot-ejbservice (EJB service réteg) lot-persistence (persistence service réteg) lot-webservice (RESTfull service réteg) Része az EAR-nak: sárga Standalone alkalmazás: kék A jms client alkalmazás classpathához jelenleg nem lesz szükség pl. egy lot-ejbserviceclient jar-ra, mivel a kommunikáció szabványos (nem kell pl. remote interface), és szöveges üzeneteket fogunk küldeni (nem kellenek stub-ok). Utóbbi megléte esetén megjelenne egy lot-ejbserviceclient jar is, mely része lenne mind az EAR mind a JMS client classpathának. 6
Adatbázis oldal CREATE TABLE event ( event_id SERIAL NOT NULL, event_puller CHARACTER VARYING(100) NOT NULL, event_prizepool INTEGER NOT NULL, event_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, CONSTRAINT PK_EVENT_ID PRIMARY KEY (event_id) ); create-schema.sql CREATE TABLE drawnnumber ( drawnnumber_id SERIAL NOT NULL, drawnnumber_event_id INTEGER NOT NULL, drawnnumber_value INTEGER NOT NULL, CONSTRAINT PK_DRAWNNUMBER_ID PRIMARY KEY (drawnnumber_id), CONSTRAINT FK_DRAWNNUMBER_EVENT FOREIGN KEY (drawnnumber_event_id) REFERENCES event (event_id) MATCH SIMPLE ON UPDATE RESTRICT ON DELETE RESTRICT ); 7
Legfrissebb és az összes sorsolás RESTful services LotteryRestService List<EventStub> getallevents() throws AdaptorException; http://localhost:8080/lottery/api/service/event/list EventStub getlatestevent() throws AdaptorException; http://localhost:8080/lottery/api/service/event/latest LotteryFacade List<EventStub> getallevents() throws AdaptorException; EventStub getlatestevent() throws AdaptorException; EventService Event readlatest() throws PersistenceServiceException; SELECT e FROM Event e JOIN FETCH e.numbers ORDER BY e.date DESC SetMaxResult(1) List<Event> readall() throws PersistenceServiceException; SELECT e FROM Event e JOIN FETCH e.numbers ORDER BY e.prizepool EventConverter EventStub to(event event); List<EventStub> to(list<event> events); 8
JOIN FETCH SELECT FROM event0_.event_id AS event_id1_1_0_, numbers1_.drawnnumber_id AS drawnnum1_0_1_, event0_.event_date AS event_da2_1_0_, event0_.event_prizepool AS event_pr3_1_0_, event0_.event_puller AS event_pu4_1_0_, numbers1_.drawnnumber_event_id AS drawnnum3_0_1_, numbers1_.drawnnumber_value AS drawnnum2_0_1_, numbers1_.drawnnumber_event_id AS drawnnum3_1_0, numbers1_.drawnnumber_id AS drawnnum1_0_0 event event0_ INNER JOIN drawnnumber numbers1_ ON event0_.event_id=numbers1_.drawnnumber_event_id ORDER BY event0_.event_date DESC server.log A Set<DrawnNumber> LAZY módon van kapcsolva az Event-hez. A JOIN beköti a select-be a táblát (ez esetben a lekérdezés azonos lesz a bemutatottal), de nem attacholja az entitásokat (ha bejárjuk (pl. size()), akkor külön select-ben lekéri és attacholja. A JOIN FETCH e plusz lekérdezés nélkül attacholja is, és ez a hatékony megoldás ez esetben! 9
Új sorsolás adatainak rögzítése 10
Java Message Service (JMS) Üzenetküldés alapú kommunikáció lazán kötődő komponensek (beékelődik a kommunikációba az üzeneteket kezelő/tároló komponens) Message-Oriented Middleware (MOM) JMS 1.1 (2002, JSR914, JEE6), JMS 2.0 (2013, JSR343, JEE7) Típusai point-to-point (queue) producer üzeneteket küld a queue-ba consumer üzenetet kiolvas a queue-ból egy üzenetet egy fogadó dolgoz fel (ack küldés is van) producer és consumer nem kell hogy egy időben online legyen publish-subscribe (topic) publish üzeneteket küld a topic-ba subscriber(ek) megkapják a topic-ba küldött üzenetet egy üzenet több fogadó is feldolgoz(hat) publisher és subscriber között van időbeli függés, tankönyvi eset szerint a komponensek egyszerre online -ok (de vannak speciális feliratkozások) 11
JBoss 6.4 - MOM, JMS provider HornetQ messaging http://hornetq.jboss.org/ Deprecated, JBoss 7.x-től JBoss A-MQ váltja fel http://www.jboss.org/products/amq/overview/ JMS 1.1 és JMS 2.0 támogatás Verzió: 2.3.25.Final (JBoss 6.4 esetén) 12
JMS Queue létrehozása <?xml version="1.0" encoding="utf-8"?> <messaging-deployment xmlns="urn:jboss:messaging-deployment:1.0"> <hornetq-server> <jms-destinations> <jms-queue name="lotteryqueue"> <entry name="jms/queue/lotteryqueue" /> <entry name="java:jboss/exported/jms/queue/lotteryqueue" /> </jms-queue> </jms-destinations> </hornetq-server> </messaging-deployment> JNDI név szabványok : java:/jms/queue/lotteryqueue lesz a valós JNDI név. Remote JMS client a java:jboss/exported/ előtagot automatikusan fogja használni (JBoss JMS Client jar használata esetén) lotteryqueue-jms.xml local JNDI name remote JNDI name A file nevének *-jms.xml-nek kell lennie, és a deployments könyvtárba másolással létrehozható, de pl. standalone.xml-ben is lehet defininálni, illetve programozottan runtime is létrehozható. 13
Lottery Listener Message Driven Bean package hu.qwaevisz.lottery.ejbservice.listener; @MessageDriven(name = "LotteryListener", activationconfig = { @ActivationConfigProperty(propertyName = "destinationtype", propertyvalue = "javax.jms.queue"), @ActivationConfigProperty(propertyName = "destination", propertyvalue = "lotteryqueue"), @ActivationConfigProperty(propertyName = "acknowledgemode", propertyvalue = "Auto-acknowledge") ) public class LotteryListener implements MessageListener { @EJB private LotteryFacade facade; @PostConstruct public void initialize() {... @Override public void onmessage( final Message message) {... LotteryListener.java Amikor a lotteryqueue -ba üzenet érkezik, a LotteryListener MDB aktiválódik, és az onmessage() metódusa meghívásra kerül a feldolgozandó üzenettel. Ha kivételt dob a metódus, az üzenet feldolgozás rollback-elődik, és nem kerül ki a sorból! 14
Lottery Listener - onmessage() Message Driven Bean BytesMessage MapMessage ObjectMessage StreamMessage TextMessage public void onmessage( final Message message) { try { final Queue destination = (Queue) message.getjmsdestination(); final String queuename = destination.getqueuename(); LOGGER.debug("New JMS message arrived into " + queuename + " queue (correlation id: " + message.getjmscorrelationid() + ")"); if (message instanceof TextMessage) { else { final TextMessage textmessage = (TextMessage) message; String content = textmessage.gettext(); [..] // parse content to int[] numbers this.facade.createnewevent(numbers); LOGGER.error("..."); catch (final JMSException AdaptorException NumberFormatException e) { LOGGER.error(e, e); LotteryListener.java queue name: kinyerhető (hasznos, ha egy listener több queue-ra figyel egyidejűleg (erre van lehetőség) correlation id: tipikusan kliens hozza létre, és elküldi a JMS message-el, hogy később pl. egy async válasz során azonosítani tudja a választ. 15
JMS Client Application Távolról JMS üzenetet küld a lotteryqueue-ba, melyet az elindított JBoss EAS által indított HornetQ mint JMS MOM fog fogadni. Ahhoz, hogy meg tudjuk szólítani ezt a szolgáltatást, az alábbiak szükségesek: JBoss initial context factory osztály neve: org.jboss.naming.remote.client.initialcontextfactory a classpath-on legyen elérhető ez az osztály compile group: 'org.jboss.as', name: 'jboss-as-jms-client-bom', version: '7.2.0.Final' JBoss EAS host-ja (localhost) és remote portja (def: 4447) stanalone.xml socket-binding-group <socket-binding name="remoting" port="4447"/> JMS Connection Factory JNDI neve (jms/remoteconnectionfactory) Egy min. guest role-lal rendelkező user authentikációja (username és password) A cél queue JNDI neve (jms/queue/lotteryqueue) Ha TextMessage helyett pl. ObjectMessage-et küldünk, akkor szükség volna egy serviceclient.jar -ra, mely tartalmazza a Serializable DTO-kat (hasonlóan az ejb client-nél alkalmazottak szerint). 16
JMS user létrehozása > \jboss-eap-6.4\bin\add-user.sh -a -u jmstestuser -p User#70365 -g guest 17
JMS Client Application A csatlakozás lényegi részeit kiemelve final Properties environment = new Properties(); environment.put(context.initial_context_factory, "org.jboss.naming.remote.client.initialcontextfactory"); environment.put(context.provider_url, "remote://localhost:4447"); environment.put(context.security_principal, "jmstestuser"); environment.put(context.security_credentials, "User#70365"); final Context context = new InitialContext(environment); final ConnectionFactory connectionfactory = (ConnectionFactory) context.lookup( "jms/remoteconnectionfactory"); final Destination destination = (Destination) context.lookup( "jms/queue/lotteryqueue"); Connection connection = connectionfactory.createconnection( "jmstestuser", "User#70365"); final Session session = connection.createsession(false, Session.AUTO_ACKNOWLEDGE); final MessageProducer producer = session.createproducer(destination); connection.start(); final TextMessage textmessage = session.createtextmessage( "1, 2, 3, 4, 5"); producer.send(textmessage); SimpleClient.java 18
JMS (Remote) Connection Factory A standalone-full.xml-ből előre konfigrálva kapjuk <subsystem xmlns="urn:jboss:domain:messaging:1.4"> <jms-connection-factories> <connection-factory name=" RemoteConnectionFactory"> <connectors> <connector-ref connector-name="netty"/> </connectors> <entries> <entry name="java:jboss/exported/ jms/remoteconnectionfactory"/> </entries> </connection-factory> </jms-connection-factories> </subsystem> A java:jboss/exported prefix-et a ClassPath-on lévő JBoss JMS Client jar adja hozzá a JNDI névhez. standalone(-full).xml 19
Session Beans Concurrency Management CMC - Container-Managed Concurrency (def.) @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) BMC - Bean-Managed Concurrency @ConcurrencyManagement(ConcurrencyManagementType.BEAN) Kizárólag Singleton Session Bean-ek esetén van értelmezve a Bean-Managed Concurrency! Utóbbi esetén engedélyezett pl. a synchronized és volatile kulcsszavak használata. 20
Singleton Session Bean Az EJB container garantálja, hogy a Singleton Session Bean-ből ugyanazt a példányt fogja minden szálon használni. Természetesen nem arról van szó, hogy minden a SSB-t használó klienst szépen sorbaállít a container (ez bottleneck-je lenne az egész rendszernek). Vannak READ és vannak WRITE lock-kal rendelkező metódusai (kizólag CMC esetén használható). READ: párhuzamosan több szálon is futhat (állapot olvasás) WRITE (def.): kizárólag egy szálon futhat (állapot módosítás) 21
StateHolder Sorsoló és a nyereményalap lekérdezése StateHolderImpl.java package hu.qwaevisz.lottery.ejbservice.holder; @Singleton(mappedName = "ejb/lotterystate") @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) public class LotteryStateHolderImpl implements LotteryStateHolder { private String puller; @PostConstruct public void initialize() { this.puller = "Juanita A. Jenkins"; @Override @Lock(LockType.READ) public String getcurrentpuller() { return this.puller; @Override @Lock(LockType.WRITE) public void setcurrentpuller(string name) { this.puller = name; A prizepool tárolása és getter/setter üzleti metódusa mindezzel teljesen azonosan elkészíthető. A SSB-nek természetesen illendő interface-t készíteni (LotteryStateHolder ), mely jelen esetben a @Local annotációt megkapja. 22
LotteryFacade kiegészítése LotteryListener hívja package hu.qwaevisz.lottery.ejbservice.facade; @Stateless(mappedName = "ejb/lotteryfacade") public class LotteryFacadeImpl implements LotteryFacade { @EJB private EventService eventservice; @EJB private LotteryStateHolder stateholder; LotteryFacadeImpl.java Az EventService a persistence rétegben egy tranzakción belül be kell hogy insertálja az új event sort, illetve az 5 új drawnumber sort ehhez az eseményhez. @Override public void createnewevent(int[] numbers) throws AdaptorException { try { this.eventservice.create(this.stateholder.getcurrentpuller(), this.stateholder.getcurrentprizepool(), numbers); catch (final PersistenceServiceException e) { LOGGER.error(e, e); throw new AdaptorException(e.getLocalizedMessage()); 23
EventService kiegészítése Persistence réteg implementációja Fontos! Az Event entitás Set<DrawnNumber> numbers field-je @OneToMany annotációjában a cascade értéke CascadeType.ALL vagy PERSIST legyen! EventServiceImpl.java package hu.qwaevisz.lottery.persistence.service; public class EventServiceImpl implements EventService { @PersistenceContext(unitName = "lot-persistence-unit") private EntityManager entitymanager; @Override public void create(string puller, Integer prizepool, int[] numbers) throws PersistenceServiceException { try { final Event event = new Event(puller, prizepool); for (final int number : numbers) { event.addnumber(number); this.entitymanager.persist(event); catch (final Exception e) { throw new PersistenceServiceException("Unknown error when fetching Events! " + e.getlocalizedmessage(), e); persist: egy új (vagy egy törlésre jelölt) entitás létrehozása, és egyben managed állapotba hozása public void addnumber(integer number) { this.numbers.add(new DrawnNumber(number, this)); merge: egy detached (nem managed) entitás létrehozása (a metódus visszaadja a managed entitást, az átadott detached nem bántja) 24
Java Management extension A JMX technológia a JavaSE része, és természetesen a JEE is támogatja, szerver oldali komponensek monitorozására is használható. Managed Bean-ek létrehozása szükséges hozzá (MBean), melyeket az MBean server észlel és kezel. JMX klienst könnyedén írhatunk, de a szabvány csatorna lévén erre legtöbbször nincsen szükség (pl. jconsole egy Java SE-vel szállított kliens alkalmazás). Az MBean-eknek követniük kell a JMX specifikációban leírt szabályokat (JMX kliensek szabvány elérése ezáltal garantált). Simple Network Management Protocol (SNMP) 25
MBean készítésének szabályai Ha az implementáció Something class, akkor az interface SomethingMBean kell hogy legyen. Az MBean-ben műveleteket (operations) és attribútumokat (attributes) definiálhatunk. Read-only A típusú xyz attribútum esetén léteznie kell egy A getxyz() metódusnak. Írható/olvasható A típusú xyz attribútum esetén létezni kell egy A getxyz() és void setxyz( A ) metódusnak. Minden olyan metódus, mely nem getter illetve setter, automatikusan műveletnek számít. Nem lehet a getter/setter-t másra használni, nem lehet azonos névvel overload-olt gettert készíteni, nem lehet más az összetartozó getter/setter paraméterezése/visszatérési értékének típusa. Egyszerű esetben a használható/javasolt típusok a java primitívek, tömbök, String-ek legyenek, de létezik komplexebb típus is (pl. TabularData). 26
LotteryMonitor JMX MBean készítése package hu.qwaevisz.lottery.ejbservice.management; LotteryMonitor.java public class LotteryMonitor implements LotteryMonitorMBean { @EJB private LotteryStateHolder stateholder; @Override public String getpuller() { return this.stateholder.getcurrentpuller(); @Override public void setpuller(string name) { this.stateholder.setcurrentpuller(name); public void start() throws Exception { LOGGER.info("Start Lottery MBean"); public void stop() throws Exception { LOGGER.info("Stop Lottery MBean"); A getprizepool() és setprizepool() implementációja a puller alapján egyértelmű. A start() és a stop() metódusok a JMX MBean életciklusa során meghívódnak. Használatuk opcionális. 27
MBean regisztrációja EJBService project <?xml version="1.0" encoding="utf-8"?> <server xmlns="urn:jboss:service:7.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation="urn:jboss:service:7.0 jboss-service_7_0.xsd"> <mbean code=" hu.qwaevisz.lottery.ejbservice.management.lotterymonitor" name="lottery.mbean:service=lotterymonitormbean"> </mbean> </server> src/main/resources/jboss-service.xml lottery.mbean lesz a topológiában a helye, LotteryMonitorMBean pedig ezen belül az MBean neve A code értékénél az osztályt kell megadni, mely megfelel mindenben a JMX MBean szabványoknak! Ez egy JBoss specifikus állomány, neve kötelezően jboss-service.xml kell hogy legyen. 28
jconsole [JRE HOME]/bin/jconsole.[bat sh] DE: JBoss esetén a jconsole classpath-ához hozzá kell fűzni további osztályokat (pl. a jboss-cli-client.jar -t), ezért a [JBOSS HOME]/bin/jconsole.[bat sh] parancsal indítsuk el (mely hivatkozik a [JRE HOME]-ban lévőre. A JBoss AS látszódni fog a Local process-ek között (de ugyanezen klienssel Remote JVM-hez is tudunk csatlakozni). Megjegyzés: MAC OS-en előfordul(hat) hogy a JBoss nem találja meg a jconsole.sh futtatásakor a JRE HOME-ot, ilyenkor lefutattva a JBoss alatti jconsole.sh-t a CLASSPATH-t beállítjuk a terminálban, és elindítjuk ugyanebben a terminálban mi a JRE HOME alatti jconsole-t. 29
Nyeremény ellenőrzése és kiszámolása Új ismeretet nem tartalmazó üzleti metódus implementációja, mely során a kliens RESTful interface-en keresztül beküld 5 számot, és az alkalmazás ellenőrzi hogy az aktuális sorsolás során e számok jók-e, avagy sem, és a nyereményalapot és a nyeremények aktuális jogszbályban definiált eloszlási százalékuk ismeretében visszaadja a nyeremény összegét. A nyereményalap adatbázisból olvasható ki, míg az eloszlási százalékok MBean-en keresztül írhatóak/olvashatóak (MBean operations). 30
Gradle - Deploy to JBoss gradle.build ext { deploylocation = '/jboss-eap-6.4/standalone/deployments/' task deployclean ( type: Delete ) { delete deploylocation + "${project.name-${version.ear" sleep(2000) task deployear ( type: Copy ) { dependson 'deployclean' from "build/libs/${project.name-${version.ear" into deploylocation gradle clean build deployear 31