jersey guice aop
TRANSCRIPT
News ed Attivit in Corso
Jersey + Guice + AOP
Domenico [email protected]
Chi sono?
@PerRequestpublic class Presentation {
@GET @Path("/JugMilano/People/DomenicoBriganti") @Produces(MediaType.APPLICATION_JSON) public Response getUserDetails() {
UserDetails userdet = new UserDetails(); userdet.setCompany("Eidon srl"); userdet.setEmail("[email protected]"); userdet.setLinkedin("http://www.linkedin.com/in/dometec"); userdet.setBlog("http://tipsaboutmywork.blogspot.com/"); ... return Response.ok(userdet).build();
}
Agenda
JAX-RS
Jersey con Guice/AOP
Demos:
Log delle richieste
Trim dei parametri Stringa in ingresso
Evitare chiamate identiche su un cluster
Transazioni
Login cookie
JAX-RS
Java API for RESTful Web Services
Release 1.1, JSR 311, 2009, JEE6 Full
Release futura 2.0, JSR 339, (EDR2 2012), JEE7
Package: javax.ws.rest
Implementazioni: Jersey (RI), Apache CXF, RESTEasy, Apache Wink
Rel 1.1 Goals: POJO-based, HTTP-centric, Format Independence, Container Independence, Inclusion in Java EE.
La release 2.0 si focalizza su HATEOAS e implementazioni client, ma anche su Validation, MVC, Async, Filters/Handlers, migliorie al Content Negotiation. Attualmente Early Draft Review 2.
Jersey
Open source, RI for JAX-RS
Jersey 1.x (1.13b1) implements JAX-RS 1.1
Jersey 2.x (mileston 3) implements JAX-RS 2
CDDL + GPL 1.1
Jersey Hello World
import javax.ws.rs.GET;import javax.ws.rs.Path;import javax.ws.rs.PathParam;import javax.ws.rs.core.Response;@Path("/hello")public class HelloWorldService { @GET @Path("/{param}") public Response getMsg(@PathParam("param") String msg) { String output = "Echo: " + msg; return Response.ok(output).build(); }}
JAX-RS Annotation
Verbi HTTP: @GET, @POST, @PUT, @DELETE, @OPTION, @HEAD
Identificazione risorse: @Path
Input: @PathParam, @QueryParam, @MatrixParam, @HeaderParam, @CookieParam. @FormParam. @DefaultValue. MultivaluedMap
Content negotiation: @Produces, @Consume
@FormParam is slightly special because it extracts information from a request representation that is of the MIME media type "application/x-www-form-urlencoded"
JAX-RS Annotation e Facility
Ambiente: @Context (ServletConfig, ServletContext, HttpServletRequest, HttpServletResponse, SecurityContext, UriInfo, HttpHeaders)
Mapper, MessageBodyWriters, MessageBodyReaders: @Provider
ResponseBuilder e UriBuilder
DEMO 0
Esecuzione della slide di presentazione
Jersey-Guice integration
org.example.demo.GuiceConfig
GuiceFiltercom.google.inject.servlet.GuiceFilter
GuiceFilter/services/*/application.wadl/application.wadl/*
web.xml:
package org.example.demo;
public class GuiceConfig extends GuiceServletContextListener {
@Override protected Injector getInjector() {
return Guice.createInjector(new JerseyServletModule() { @Override protected void configureServlets() {
Map params = new HashMap();
params.put(PackagesResourceConfig.PROPERTY_PACKAGES, "org.example.demo"); params.put(ResourceConfig.FEATURE_TRACE, "true"); params.put(ResourceConfig.FEATURE_TRACE_PER_REQUEST, "true"); ...
web.xml
listener
Listener per la configurazione dell'injector (bind,AOP).Filter per il processing delle richieste.
Demo 1
Log delle richieste
Demo 1 Log delle richieste (interc.)
public class LogCall implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
Logger logger = LoggerFactory.getLogger(invocation.getThis().getClass());
String arg = Joiner.on(", ").useForNull("null").join(invocation.getArguments());logger.debug("{} ({}).", invocation.getMethod().getName(), arg);
Object result = invocation.proceed();
logger.trace("Output: {}.", result);return result;
}
}
Il log delle richieste gi lo abbiamo sul access log del nostro webserver o application server. Ma per quanto riquarda il body in POST o PUT non ci viene in aiuto. Con questo Interceptor possiamo loggare sul nostro file applicativo le richieste che arrivano con tutti i parametri in input e il THREAD che evade la chiamata
Demo 1 Log delle richieste (bind)
public class GuiceConfig extends GuiceServletContextListener {
@Override protected Injector getInjector() {
return Guice.createInjector(new JerseyServletModule() { @Override protected void configureServlets() {
Map params = new HashMap();
params.put(PackagesResourceConfig.PROPERTY_PACKAGES, "org.example.demo"); ...
filter("/*").through(GuiceContainer.class, params);
install(new Module() {
public void configure(Binder binder) {
LogCall logCall = new LogCall(); TrimAndNullInterceptor trimAndNullableInterceptor = new TrimAndNullInterceptor();
bindInterceptor( Matchers.annotatedWith(Path.class), Matchers.annotatedWith(GET.class).or(Matchers.annotatedWith(POST.class)) .or(Matchers.annotatedWith(PUT.class)).or(Matchers.annotatedWith(DELETE.class)), trimAndNullableInterceptor, logCall);
Demo 2 Trim parametri (interc.)
public class TrimAndNullInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
for (int i = 0; i < invocation.getArguments().length; i++) {
if (invocation.getArguments()[i] != null && invocation.getArguments()[i] instanceof String) {
String sparam = (String) invocation.getArguments()[i];
String trim = sparam.trim();if (trim.isEmpty())invocation.getArguments()[i] = null;elseinvocation.getArguments()[i] = trim;}
}
return invocation.proceed();
}}
Demo 2 Trim parametri (bind)
public class GuiceConfig extends GuiceServletContextListener {
@Override protected Injector getInjector() {
return Guice.createInjector(new JerseyServletModule() { @Override protected void configureServlets() {
Map params = new HashMap();
params.put(PackagesResourceConfig.PROPERTY_PACKAGES, "org.example.demo"); ...
filter("/*").through(GuiceContainer.class, params);
install(new Module() {
public void configure(Binder binder) {
LogCall logCall = new LogCall(); TrimAndNullInterceptor trimAndNullableInterceptor = new TrimAndNullInterceptor();
bindInterceptor( Matchers.annotatedWith(Path.class), Matchers.annotatedWith(GET.class).or(Matchers.annotatedWith(POST.class)) .or(Matchers.annotatedWith(PUT.class)).or(Matchers.annotatedWith(DELETE.class)), trimAndNullableInterceptor, logCall);
Evitare richieste duplicate
Problemi:Ristrasmissioni
Doppi submit (anche Tripli...) da browser
Timeout lato client che scatena altre prove di richieste
Rimedi:Hashtable con chiamate attualmente in corso
429 Too Many Requests (RFC 6585)
Infinispan con lock condiviso per sistemi cluster
RFC6585: Additional HTTP Status Codes, April 2012, tra le altre cose: 3. 428 Precondition 4. 429 Too Many Requests 5. 431 Request Header Fields Too Large 6. 511 Network Authentication Required
Demo 3 Richieste duplicate (uso)
@POST@UniqueCallOnClusterpublic Response getAccountBalance(@FormParam("fromuser")...
@POST@UniqueCallOnClusterpublic Response getAccountBalance(@KeyParameter @FormParam("fromuser")...
Come si usa
Demo 3 Richieste duplicate (Inter.)
public class UniqueCallOnClusterInterceptor implements MethodInterceptor { ... public Object invoke(MethodInvocation invocation) throws Throwable {
String classname = invocation.getMethod().getDeclaringClass().getSimpleName();String methodName = invocation.getMethod().getName();String key = classname + "_" + methodName + "_" + extractParameterValue(invocation);
TransactionManager tm = keyCallOnClusterService.getTransactionManager();
tm.begin();boolean success = keyCallOnClusterService.lock(key);
if (!success) {logger.info("Non posso effettuare il lock sul cluster per la chiave {}.", key);return Response.status(429).entity("Another call with same parameter is in progress.").build();}
String runningServer = (String) keyCallOnClusterService.get(key);if (runningServer != null) {logger.info("Chiamata gi in corso, server {}.", runningServer);return Response.status(429).entity("Another call with same parameter is in progress.").build();}
keyCallOnClusterService.put(key, "todo-hostname");tm.commit();...return invocation.proceed();...
keyCallOnClusterService.remove(key);
}
Demo 3 Richieste duplicate (bind)
public class GuiceConfig extends GuiceServletContextListener {
@Override protected Injector getInjector() {
return Guice.createInjector(new JerseyServletModule() { @Override protected void configureServlets() {
Map params = new HashMap();
params.put(PackagesResourceConfig.PROPERTY_PACKAGES, "org.example.demo"); ...
filter("/*").through(GuiceContainer.class, params);
install(new Module() {
public void configure(Binder binder) {
UniqueCallOnClusterInterceptor uniqueCallOnClusterInterceptor = new UniqueCallOnClusterInterceptor();requestInjection(uniqueCallOnClusterInterceptor);bindInterceptor(Matchers.any(), Matchers.annotatedWith(UniqueCallOnCluster.class),uniqueCallOnClusterInterceptor);
Transazioni con AOP
Transazioni con AOP
Transazioni con AOP
Demo 4 Transazioni (uso)
package org.example.demo.controller;
@PerRequest@Transactional@Path("services/transactionalresource3")public class TransactionalResource3Write extends AbstractTransactionalResource {
@Injectpublic TransactionalResource3Write() {}
@POST@Produces("text/plain")public String get() {
...
Demo 4 Transazioni (uso)
package org.example.demo.controller;
@PerRequest@Transactional(TransactionType.ReadOnly)@Path("services/transactionalresource1")public class TransactionalResource1ReadOnly extends AbstractTransactionalResource {
@Injectpublic TransactionalResource1ReadOnly() {}
@GET@Produces("text/plain")@SuppressWarnings("unchecked")public String get() {
List list = getSession().createCriteria(DatabaseLog.class).list();
if (list.size() == 0)return "No record!";
Demo 4 Transazioni (uso)
package org.example.demo.controller;
@PerRequest@Transactional(TransactionType.RequiredNew)@Path("services/transactionalresource5")public class TransactionalResource5ReqNew extends AbstractTransactionalResource {
@Injectpublic TransactionalResource5ReqNew() {}
...
Demo 4 Transazioni (uso)
public abstract class AbstractTransactionalResource {private Session session;
@NoTransactionalpublic void setSession(Session session) {this.session = session; }
@NoTransactionalpublic Session getSession() {return session; }
@Override@NoTransactionalprotected void finalize() throws Throwable {super.finalize(); }
}
Demo 4 Transazioni 1/2 (interc.)
public class TransactionInterceptor implements MethodInterceptor {
private final ThreadLocal sessionThreadLocal;
public Object invoke(MethodInvocation invocation) throws Throwable {...Transactional transactional = invocation.getMethod().getDeclaringClass().getAnnotation(Transactional.class);...Stack sessionStack = sessionThreadLocal.get();Transaction transaction = null;Session session = null;if (!sessionStack.isEmpty())session = sessionStack.peek();
if (session == null || transType.equals(TransactionType.RequiredNew)) {
boolean readonly = false;readonly = transType.equals(TransactionType.ReadOnly);session = hibernateSessionService.openSession(readonly);transaction = session.getTransaction();transaction.begin();sessionStack.push(session);}
Session oldSession = null;AbstractTransactionalResource service = ((AbstractTransactionalResource) invocation.getThis());oldSession = service.getSession();service.setSession(session);
Object result = invocation.proceed();
Demo 4 Transazioni 2/2 (interc.)
try {
Object result = invocation.proceed();
if (transaction != null) {session.flush();transaction.commit();}
return result;
} catch (Exception e) {
transaction.rollback();throw e;
} finally {
if (transaction != null) {hibernateSessionService.closeSession(session);sessionStack.pop();if (sessionStack.isEmpty())sessionThreadLocal.remove();}
service.setSession(oldSession);
}
}
}
Demo 4 Transazioni (bind)
public class GuiceConfig extends GuiceServletContextListener {
@Override protected Injector getInjector() {
return Guice.createInjector(new JerseyServletModule() { @Override protected void configureServlets() {
Map params = new HashMap();
params.put(PackagesResourceConfig.PROPERTY_PACKAGES, "org.example.demo"); ...
filter("/*").through(GuiceContainer.class, params);
install(new Module() {
public void configure(Binder binder) {
TransactionInterceptor transactionInterceptor = new TransactionInterceptor(); requestInjection(transactionInterceptor); bindInterceptor(Matchers.annotatedWith(Transactional.class), Matchers.not(Matchers.annotatedWith(NoTransactional.class)),TransactionInterceptor);
Demo 5 - Login Cookie (bind)
Annotation:AuthenticatedUser
ApplicationRolesAllowed
Provider:AuthenticatedUserProvider
Exception e ExceptionMapper:NotAuthenticatedException(+Mapper)
NotAuthorizedException(+Mapper)
Cookie: LoginCookieManager
Utente: DemoPrincipal
Interceptor: ApplicationRolesAllowedInterceptor
Grazie!
Domande??
Riferimenti:http://jersey.java.net
http://code.google.com/p/google-guice/
Demo webapp: https://github.com/dometec/shadedcode/tree/master/demo-webapp
Cliccate per modificare il formato del testo della strutturaSecondo livello strutturaTerzo livello strutturaQuarto livello strutturaQuinto livello strutturaSesto livello strutturaSettimo livello strutturaOttavo livello strutturaNono livello struttura
JUG Milano Meeting #48
Cliccate per modificare il formato del testo del titolo
Title of Your Presentation
Presenter's Name
Job Title
Company Name
Email