diff --git a/simpleexample2/restapi/README.md b/simpleexample2/restapi/README.md index 814d1ce68147d0da76a726d98aded291897c8642..6ed376f4977c240dc1e52078a1d422db74ef29f8 100644 --- a/simpleexample2/restapi/README.md +++ b/simpleexample2/restapi/README.md @@ -8,22 +8,45 @@ Et REST-API er på en måte et objekt (eller sett med objekter) som leses og/ell Denne modulen inneholder bare logikken til "REST-tjeneste"-objektet (**LatLongsService**), mens oppsettet av serveren ligger i [restserver-modulen](../restserver/README.md). En slik oppdeling gjør løsningen ryddigere (selv om det øker antall moduler). -REST-API-et vårt er programmert iht. [JAX-RS-standarden](https://en.wikipedia.org/wiki/Java_API_for_RESTful_Web_Services), som lar en angi koblingen mellom HTTP og metodene på REST-tjeneste-objektet vha. Java-annotasjoner. F.eks. angir @Path-annotasjonen på **LatLongsService**-klassen at vår tjeneste er knyttet til **latLongs**-prefikset. Altså vil vår tjeneste aktiveres ved å angi **latLongs** først i stien til URL-en (men bak stien til serveren). Anta f.eks. at stien til serveren er "http://min-server:8080/". Da vil stien til vår tjeneste være "http://min-server:8080/latLongs". +REST-API-et vårt er programmert iht. [JAX-RS-standarden](https://en.wikipedia.org/wiki/Java_API_for_RESTful_Web_Services), som lar en angi koblingen mellom HTTP og metodene på REST-tjeneste-objektet vha. Java-annotasjoner. Tjenesteobjektet trenger faktisk ikke implementere noe spesifikt grensesnitt eller arve fra noen spesifikk superklasse, slik en servlet må. F.eks. angir @Path-annotasjonen på **LatLongsService**-klassen at vår tjeneste er knyttet til **latLongs**-prefikset. Altså vil vår tjeneste aktiveres ved å angi **latLongs** først i stien til URL-en (men bak stien til serveren). Anta f.eks. at stien til serveren er "http://min-server:8080/". Da vil stien til vår tjeneste være "http://min-server:8080/latLongs". Som nevnt er JAX-RS *annotasjonsdrevet*. Det betyr at Java-annotasjoner ved hver metode angir detaljer ved forespørslen som trigger metoden. F.eks. vil en metode annotert med **@GET** bare kalles når HTTP-metoden er "GET". En kan bruke **@Path** for å angi et påkrevd sti-segment og **@PathParam** for å bruke dette sti-segmentet som argument til metoden. Hvis metoden bruker ekstra data sendt i tillegg til URL-en, så angis formatet med **@Consumes**-annotasjonen. Tilsvarende angis kodingen av resultatet med **@Produces**-annotasjonen. Vi bruker json-kodete data, så derfor bruker vi **MediaType.APPLICATION_JSON** (konstant for "application/json") som argument for begge disse. -Vårt REST-API gir tilgang til *ett* **LatLongs**-objekt og tilbyr fem metoder: +Vårt REST-API gir tilgang til *ett* **LatLongs**-objekt, som refereres til av **latLongs**-felter. **@Inject**-annotasjonen er vesentlig, den signaliserer at feltet (kan/skal) settes "automagisk" *utenifra* til et objekt av den angitte typen. Hvordan dette skjer styres av web-serveren som tjenesteklassen vår kjører på, se [restserver-modulen](../restserver/README.md). Slik "injeksjon" er en vanlig teknikk for å gjøre en klasse mer uavhengig av sammenhengen den inngår i. + +REST-API-et tilbyr fem metoder: * lese innholdet (alle **LatLong**-objektene) - GET til **tjeneste-URL** * lese innholdet til et spesifikt **LatLong**-element (angitt med posisjonen **num**) - GET (**@GET**) til **tjeneste-URL/num** (**@Path** og **@PathParam**) -* legge til LatLongs-objekter i spesifikk posisjon - POST (**@POST**) av json-kodet LatLong-array til **tjeneste-URL/num** (**@Path** og **@PathParam**) +* legge til LatLongs-objekter - POST (**@POST**) av json-kodet LatLong-array til **tjeneste-URL** * endre LatLong-objekt i spesifikk posisjon - PUT (**@PUT**) av json-kodet LatLong-objekt til **tjeneste-URL/num** (**@Path** og **@PathParam**) * fjerne LatLong-objekt i spesifikk posisjon - DELETE (**@DELETE**) til **tjeneste-URL/num** (**@Path** og **@PathParam**) Merk at navnet på implementasjonsmetoden i **LatLongsService**-klassen ikke spiller noen rolle, det er annotasjonene som er viktige. +Her er et eksempel på forløp, hvor det antas at **LatLongs**-objektet er tomt i starten: + +```plantuml +client -> LatLongsService: GET /latLongs +LatLongsService --> client: [ ] +client -> LatLongsService: POST /latLongs ~[[63.1, 11.2], [63.2, 11.0]] +LatLongsService --> client: 0 +client -> LatLongsService: GET /latLongs/1 +LatLongsService --> client: { "latitude": 63.2, "longitude": 11.0 } +client -> LatLongsService: PUT /latLongs/0 { "latitude": 64.0, "longitude": 9.1 } +LatLongsService --> client: 0 +client -> LatLongsService: GET /latLongs +LatLongsService --> client: [ { "latitude": 64.0, "longitude": 9.1 }, { "latitude": 63.2, "longitude": 11.0 } ] +``` + +Først er altså **LatLongs**-objektet er tomt. Det legges så til to **LatLong**-objekter, som havner i posisjon 0, før **LatLong**-objekter i posisjon 1 hentes. Så endres **LatLong**-objektet i posisjon 0, før alle hentes. Legg merke til at en json-*array* med to tall, f.eks. **[63.1, 11.2]**, kan deserialiseres (leses) som et **LatLong**-objekt, men at det serialiseres (skrives) som et json-*objekt*, f.eks. **{ "latitude": 64.0, "longitude": 9.1 }**. + ## JSON-koding av data -Når vi bruker **@Consumes** og **@Produces** med **MediaType.APPLICATION_JSON** som argument, så må vi også konfigurere tjenesten slik at kodingen av json-data bruker vår logikk, altså den som er programmert i [**core**-modulen](../core/README.md). Dette gjøres i **LatLongObjectMapperProvider**-klassen. Det er ikke så mye å si om den klassen, annet enn at den bruker **LatLongsModule**-klassen fra core-modulen. +Når vi bruker **@Consumes** og **@Produces** med **MediaType.APPLICATION_JSON** som argument, så må vi også konfigurere tjenesten slik at kodingen av json-data bruker vår logikk, altså den som er programmert i [core-modulen](../core/README.md). Ellers kan det blir forskjell på logikken i JavaFX-app-en som sender forespørsler og og mottar svar, og REST-tjenesten, som mottar forespørsel og sender svar. Konfigureringen gjøres i **LatLongObjectMapperProvider**-klassen. Det er ikke så mye å si om den klassen, annet enn at den bruker den samme **LatLongsModule**-klassen fra core-modulen, som JavaFX-app-en bruker. + +## Bygging med Gradle + +REST-API-koden er satt opp som et prosjekt av typen **java-library**, siden det ikke er noe selvstendig program. Vi har avhengighet til JSON-biblioteket Jackson og JAX-RS-standarden. Vi trenger imidlertid ikke noen avhengighet til en implementasjon av denne standarden, den "byrden" dyttes over på [restserver-modulen](../restserver/README.md). diff --git a/simpleexample2/restapi/src/main/java/simpleex/restapi/LatLongsService.java b/simpleexample2/restapi/src/main/java/simpleex/restapi/LatLongsService.java index 9fbec9c50d68c465fe8b001c589684668a55373e..4948bde320b1092234c209e1ba733bbe3b3a6868 100644 --- a/simpleexample2/restapi/src/main/java/simpleex/restapi/LatLongsService.java +++ b/simpleexample2/restapi/src/main/java/simpleex/restapi/LatLongsService.java @@ -67,3 +67,18 @@ public class LatLongsService { return latLongs.removeLatLong(num); } } + +/** + * @startuml + * client -> LatLongsService: GET /latLongs + * LatLongsService --> client: [ ] + * client -> LatLongsService: POST /latLongs ~[[63.1, 11.2], [63.2, 11.0]] + * LatLongsService --> client: 0 + * client -> LatLongsService: GET /latLongs/1 + * LatLongsService --> client: { "latitude": 63.2, "longitude": 11.0 } + * client -> LatLongsService: PUT /latLongs/0 { "latitude": 64.0, "longitude": 9.1 } + * LatLongsService --> client: 0 + * client -> LatLongsService: GET /latLongs + * LatLongsService --> client: [ { "latitude": 64.0, "longitude": 9.1 }, { "latitude": 63.2, "longitude": 11.0 } ] + * @enduml + **/ diff --git a/simpleexample2/restserver/README.md b/simpleexample2/restserver/README.md index 0edd3dc817cc518fbda480a0aee57b603f108b65..a2c0c40a938f0239c22654c62b0d8a36d5a4e114 100644 --- a/simpleexample2/restserver/README.md +++ b/simpleexample2/restserver/README.md @@ -4,4 +4,27 @@ Dette prosjektet inneholder web-serveren med REST-api-et for [simpleexample2](.. ## Web-serveren +REST-tjenestelogikken i [restapi-modulen](../restapi/README.md) trenger en web-server for å bli gjort tilgjengelig. Vi må naturlig nok ha en web-server med støtte for [JAX-RS-standarden](https://en.wikipedia.org/wiki/Java_API_for_RESTful_Web_Services), siden det er den REST-API-logikken er programmert iht. Det er flere å velge mellom, vi har valgt å bruke [Jersey](https://eclipse-ee4j.github.io/jersey/), som er åpen kildekode (og nylig overført til Eclipse-stiftelsen). + +Jersey er i seg selv ikke en komplett web-server, men bygger bro mellom en underliggende HTTP-tjener og JAX-RS-baserte REST-API-klasser. Derfor trenger vi også en ren HTTP-tjener, og av mange alternativer bruker vi her bruker vi [grizzly2](https://javaee.github.io/grizzly/). + +Koden består av to "små" klasser: + +* **LatLongConfig** registrerer klassene som utgjør og støtter REST-tjenesten vår. +* **LatLongGrizzlyApp** inneholder oppstartskode. + +### LatLongConfig + +Konfigurasjonsteknikken består i å oppgi hvilke klasser og evt. objekter som "samarbeider" om REST-tjenesten. Hvilken rolle disse spiller bestemmes av typene, Jersey sørger for å instansiere dem og koble instansene sammen. Detaljene trenger vi ikke å kjenne til, vi må bare vite hvilke klasser vi må registrere. Den viktigste er REST-tjenesteklassen **LatLongsService**, mens de to andre er støtteklasser. + +Vi registrerer også et *objekt*, dette er det ene **LatLongs**-objektet som REST-tjenesten opererer på. Siden typen stemmer overens med det **@Inject**-annoterte *latLongs*-feltet i **LatLongsService**-klassen, vil det bli "injisert" (automagisk satt) og på den måten gjøres tilgjengelig for alle REST-API-metodene. + +### LatLongGrizzlyApp + +Denne klassen brukes bare ved oppstart av serveren, f.eks. hvis den kjøres som en selvstendig applikasjon. Klassen har egne metoder for å start og stoppe serveren, og disse brukes av den varianten av JavaFX som bruker REST-API-et. En slik server kjører som en uavhengig "tråd", slik at serveren ikke nødvendigvis er kommet helt i gang når koden vår får referansen til det nyopprettede **HttpServer**-objektet (returnert av kallet til **GrizzlyHttpServerFactory.createHttpServer**). Derfor har **startServer**-metoden bygget inn muligheten til å vente på at serveren svarer før metoden returnerer. + ## Bygging med gradle + +Avhengighetene viser at vi bruker en rekke rammeverk og biblioteker. Detaljene har vi lest oss frem til i [dokumentasjonen til Jersey](https://jersey.github.io/documentation/latest/deployment.html#deployment.http). + +Versjonsnumrene er viktige, siden alt må spille sammen. For å gjøre det ryddigere er variabler med versjonsnumrene satt i en **ext**-blokk og angitt i identifikasjonsstrengen med $-notasjonen. Merk at for at versjonsvariabelverdiene skal bli "skutt inn" i strengen, så må en bruke " og ikke ' rundt strengen.