Jax-rs 2.0
Contents
JAX-RS szerver
Inicializálás
A JAX-RS szerver futtatásához el kell indítsuk a Jersy servlet-et a WEB alkalmazásunkban, ami kiválóan megfér más servlet-ek mellett, pl JSF. A Jersey servletnek meg fogjuk mondani, hogy milyen PATH tartozik hozzá, így minden más erőforrás kérés továbbra is a JSF servlet-hez fog beesni.
Maven dependenciák
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.28</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>2.28</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-common</artifactId>
<version>2.28</version>
</dependency>
web.xml
Ha JSF-et akarunk párhuzamosan JAX-RS-el használni, akkor nincs más dolgunk, mint hogy mint két serlvet implementációt felvegyünk a web.xml-be. Elsőként a JSF implementációt, ami az esetünkben PrimeFaces lesz, aztán meg felvesszük a JAX-RS serlvetet, ami az esetünkben Glassfish-Jeresy lesz.
A Glassfish-Jeresy-t többféle képen lehet paraméterezni. Vagy itt, a web.xml-ben adjuk meg a szükséges paramétereket (provide-erek, mapperek, stb..) vagy implementáljuk a javax.ws.rs.core.Application osztályt, megadjuk a helyét a javax.ws.rs.Application paraméterrel, majd a konfigurációt többi részét az osztályon belül definiáljuk.
Elsőre nézzük az a web.xml-es konfigurációt. <init-param> szekciókkal kell megadni a Jersey paramétereit:
- jersey.config.server.provider.packages: meg kell adni azt a java csomagot, ahol a webservice implementációk és az exception mapper-ek vannak. Mi itt azt a csomagot adjuk meg, ahol a service implementációk vannak.
- jersey.config.server.provider.classnames: fel lehet sorolni konkrét class megadásokkal további implementációkat. Mi itt az Exception mapper-eket adjuk meg.
A servlet-mapping szekcióban elsőre megadjuk hogy a Jeresey servlet a /rest/ útvonalon fog hallgatózni. Aztán adjuk meg a JSF servletet, ami minden másra illeszkedni fog.
...
<!-- JSF servlet -->
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Jersey servlet -->
<servlet>
<servlet-name>Jersey Service</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>hu.adam.loginHelper.jaxrs.service</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>
hu.adam.loginHelper.jaxrs.mapper.AppExceptionMapper;
hu.adam.loginHelper.jaxrs.mapper.ErrorMessage;
hu.adam.loginHelper.jaxrs.mapper.WebServiceExceptionMapper
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Jersey mapping -->
<servlet-mapping>
<servlet-name>Jersey Service</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
<!-- JSF servlet -->
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.faces</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
Másik alternatíva lenne ha JAX-RS konfigurációt a javax.ws.rs.core.Application osztály implementációjában definiálnánk. Ekkor a web.xml-ben csak az implementációs osztályt kell megadni: ha a web.xml-ben az
<!-- Jersey servlet -->
<servlet>
<servlet-name>Jersey Service</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>hu.adam.MyApplication</param-value>
</init-param>
Majd a tovább konfigurációkat az implementációs osztályban definiáljuk:
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("/rest/")
public class MyApplication extends Application {
public Set<Class<?>> getClasses() {
Set<Class<?>> s = new HashSet<Class<?>>();
//Webservices
s.add(LoginService.class);
//Mappers
s.add(AppExceptionMapper.class);
s.add(GenericExceptionMapper.class);
return s;
}
}
Service osztály definiálása
A service osztályokat nagyon egyszerű definiálni, az alábbi osztály loginByEmail metódusa a POST:<app root>/rest/login/loginbyemail URL-en lesz elérhető. A HTTP body-ban a LoginByEmalLoginHelperRequest osztály JSON reprezentációját fogja várni, amit automatikusan mappelni fog a JAX-RS egy LoginByEmalLoginHelperRequest objektum példányba. A metódus a TokenResponse típusú objektummal tér vissza, mait a JAX-RS automatikusan JSON -ra fog mappelni anélkül, hogy vagy a request vagy a response objektumba bármilyen annotációt el kéne helyezni. Bonyolult, vagy nem egyértelmű struktúrák esetén szükség lehet rá, hogy annotációkkal támogassuk a JSON<->JAVA mapper-t, lásd lentebb.
import javax.ws.rs.POST;
import javax.ws.rs.Path;
@Path("/login/")
@Consumes({ "application/json" })
@Produces({ "application/json" })
public class LoginService {
@POST
@Path("/loginbyemail")
public TokenResponse loginByEmail(LoginByEmalLoginHelperRequest loginByEmalRequest) throws WebServiceException {
return new TokenResponse();
}
@GET
@Path("/book/{isbn}")
public Book getBook(@PathParam("isbn") String id, @QueryParam("title") String title) {
Book oneBook = new Book();
oneBook.setIsbn("ISBN11111");
oneBook.setTitle("Mastering Jax-RS");
return oneBook;
}
}
A második metódus (getBook) a GET:<app root>/rest/book/ URL-en érhető el. Vár egy PATH paramétert a /book/ után, valamint egy title nevű QUERY paramétert, tehát a teljes request valami ilyesmi lesz: GET:<app root>/rest/book/12345?title=adam
Hibakezelés
Ez egy sarkalatos pontja a REST interfész implementációnak. A @GET/POST/PUT annotációkkal ellátott metódusoknál csak azt tudjuk definiálni, hogy mi legyen a válasz típusa happy ágon, mikor a kérést rendeltetés szerűen ki tudta szolgálni a szerver, tehát a HTTP státusz kódja a válasznak a 200-as tartományba fog esni. Ha van visszatérési érték, akkor feltehetően ez 200 lesz, ha nincs, akkor 204, ezt automatikusan kezeli a JAX-RS.
Viszont mi van akkor ha hiba történik a feldolgozásban. A kliensnek semmilyen körülmények közözz nem szabad egy java stack trace-t visszaadni, minden körülmények között egy szabályos REST választ kell visszaküldeni, ahol a HTTP státusz megfelelően ki van töltve, és lehetőleg egy egységesített ERROR objektumban benne van a hiba leírása.
- 400-as hibák: A feldogozás az input adatok miatt nem sikerült, pl hiányzik valami, vagy nincs joga a kliensek a kérést végrehajtania
- 500-as hibák: Az input paraméterek megfelelőek voltak, de a szerver oldalon nem várt hiba történt. (pl nem tudtunk egy szükséges háttérrendszerhez csatlakozni)
Egy java metódusból úgy lehet az előre definiált visszatérési értéken felül más típusú választ kijuttatni, ha PL egy megfelelő exception-t eldobunk, amit elkapunk és lekezelünk. Itt is ezt fogjuk tenni.
Ahhoz hogy a hibaágakat is kezelni tudjuk, létre kell hozzunk egyedi Exception osztályokat, és hozzájuk tartozó response mapper-eket, amik az Exception eldobása estén megkapják a vezérlést, és az Exception alapján össze tudják állítani a választ. Ehhez három osztályt kell létrehozzunk:
- ErrorResponse osztály, egy egységesített struktúra, amit minden nem 200-as válaszban vár a kliens. (pl hibakódok, szöveges leírás)
- Saját Exception implementáció (WebServiceException), amibe minden olyan infónak szerepelnie kell, ami alapján az ErrorResponse osztály elkészíthető.
- Exception mapper osztály, ami megkapja a vezérlést ha WebServiceException dobódott, és a benne található információk alapján példányosítja a ErrorResponse osztályt, majd beleteszi a REST válaszba, amit a JAX-RS JSON formátumra fog hozni.
Elsőként definiáljuk azt az osztályt ami alapján a nem 200-as státusz esetén a válasz objektumot létre akarjuk hozni:
public class ErrorMessageResponse implements Serializable {
private String correlationId;
private String errorCode;
//Getters and setters ...
}
A HTTP válaszba JSON alakban ez így fog kinézni:
{"correlationId":"XXX", "errorCode":"111"}
Azt szeretnénk elér, hogy minden körülmények között, nem 200-as státusz esetén ez menjen vissza.
Hozzunk létre egy egyedi Exception implementációt
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import hu.adam.loginHelper.exception.WebServiceException;
@Provider
public class WebServiceExceptionMapper implements ExceptionMapper<WebServiceException> {
@Override
public Response toResponse(WebServiceException exception) {
return Response.status(exception.getStatus())
.entity(exception.getErrorMessageResponse())
.type(MediaType.APPLICATION_JSON).
build();
}
}