Yet Another TEch bLog
"Everything there is to know about the shrimpin' business."
November 12, 2017
Blog moved to blog.kramolis.cz
I've moved the blog site to Github pages (https://github.com/shamoh/pages-blog) available on a new web site http://blog.kramolis.cz/.
Labels:
migration
May 6, 2016
Custom Restito Sequenced Stub Action
In this post I show how to write custom Restito Action to mock a single REST endpoint to response differently even is subsequent requests are identical.
Restito is lightweight testing framework that provides a Java DSL to:
Restito is lightweight testing framework that provides a Java DSL to:
- mimic REST server behavior,
- perform verification against happened calls.
It is based on Grizzly HTTP Server. See developer's guide for more details about all supported features.
Let's say I want to test business login of application that calls following REST API:
- Gets list of all open comments (GET /comments),
- Archives each comment that is older than a month (DELETE /comments/{id}),
- Lists all remaining open comments (GET /comments).
import java.util.List; import com.xebialabs.restito.builder.ensure.EnsureHttp; import com.xebialabs.restito.support.junit.StartServer; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import static com.xebialabs.restito.builder.stub.StubHttp.whenHttp; import static com.xebialabs.restito.semantics.Action.contentType; import static com.xebialabs.restito.semantics.Action.status; import static com.xebialabs.restito.semantics.Action.stringContent; import static com.xebialabs.restito.semantics.Condition.delete; import static com.xebialabs.restito.semantics.Condition.get; import static org.glassfish.grizzly.http.util.HttpStatus.NO_CONTENT_204; import static org.glassfish.grizzly.http.util.HttpStatus.OK_200; import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertThat; public class MyAppTest { private static final String GET_COMMENTS_BEFORE = "{\"comments\":[\n" + " {\"id\" : 1, \"content\" : \"Happy New Year ...\", \"date\" : \"2016-01-01\"},\n" + " {\"id\" : 2, \"content\" : \"Happy Valentine...\", \"date\" : \"2016-02-14\"},\n" + " {\"id\" : 3, \"content\" : \"I like Restito ...\", \"date\" : \"2016-05-06\"}\n" + "]}"; private static final String GET_COMMENTS_AFTER = "{\"comments\":[\n" + " {\"id\" : 3, \"content\" : \"I like Restito ...\", \"date\" : \"2016-05-06\"}\n" + "]}"; @Rule public StartServer startServer = new StartServer(); @Test public void testArchiveOldComments() { // 1. Stub for: Gets list of all open comments (GET /comments): whenHttp(startServer.getServer()). match(get("/comments")). then(status(OK_200), contentType("application/json"), stringContent(GET_COMMENTS_BEFORE)). mustHappen(); // 2. Stub for: Archives each comment that is older than a month (DELETE /comments/{id}): whenHttp(startServer.getServer()). match(delete("/comments/1")). then(status(NO_CONTENT_204)). mustHappen(); whenHttp(startServer.getServer()). match(delete("/comments/2")). then(status(NO_CONTENT_204)). mustHappen(); // 3. Stub for: Lists all remaining open comments (GET /comments): whenHttp(startServer.getServer()). match(get("/comments")). then(status(OK_200), contentType("application/json"), stringContent(GET_COMMENTS_AFTER)). mustHappen(); // call business logic MyApp myApp = new MyApp(); List<Comment> comments = myApp.archiveOldComments(); // remaining 1 comment: assertThat(comments, hasSize(1)); // ensure that each stubs has been invoked: EnsureHttp.ensureHttp(startServer.getServer()).gotStubsCommitmentsDone(); } }
But this test fails because DELETE stubs are not invoked.
MyAppTest > testArchiveOldComments FAILED java.lang.AssertionError at MyAppTest.java:72 Expected stub com.xebialabs.restito.semantics.Stub@775449c1 to be called 1 times, called 0 times instead java.lang.AssertionError: Expected stub com.xebialabs.restito.semantics.Stub@775449c1 to be called 1 times, called 0 times instead
Note: You can see the error output is not comprehensive. It is known issue, see https://github.com/mkotsur/restito/issues/45.
The reason why DELETE methods are not invoked is that the first GET call does not return JSON with 3 comments but just 1 (JSON in GET_COMMENTS_AFTER String). It means that the second GET /comments stub overrides the first one. Or, the first stub that returns JSON with 3 comments (GET_COMMENTS_BEFORE String) is ignored because the match Conditions for both stubs are identical.
Unfortunately Restito does not support directly this use case. Fortunately you can create custom Action or Condition. Described use-case can be solved by custom Action.
import java.util.LinkedList; import java.util.Queue; import com.xebialabs.restito.semantics.Action; import com.xebialabs.restito.semantics.Function; import org.glassfish.grizzly.http.server.Response; public class SequencedAction implements Function<Response, Response> { private Queue<Action> queue = new LinkedList<>(); public SequencedAction(Action... actions) { for (Action action : actions) { this.queue.offer(action); } } @Override public Response apply(Response input) { Action next = queue.poll(); if (next != null) { input = next.apply(input); } return input; } }
Custom SequencedAction keeps a queue of not yet processed Actions and polls just one available Action from the queue in apply method. I.e. subsequent SequencedAction.apply invocation behaves differently. If the queue is empty the SequencedAction.apply does nothing like noop action.
So now I can update test method to use custom action.
And the test is fixed.
You are right, it is a little bit too verbose to use custom action. So I have decided to contribute the sequenced action directly to Restito Action, method sequence, see or comment or vote for my GitHub pull request: https://github.com/mkotsur/restito/pull/49. 😇
@Test public void testArchiveOldComments() { // 1. Stub for both: Gets list of all open comments (GET /comments): whenHttp(startServer.getServer()). match(get("/comments")). then(status(OK_200), contentType("application/json"), // use custom action: Action.custom(new SequencedAction( // the first get: returns 3 comments stringContent(GET_COMMENTS_BEFORE), // the second get: returns 1 comment stringContent(GET_COMMENTS_AFTER) ))). // 2 actions in sequence: mustHappen(2); // 2. Stub for: Archives each comment that is older than a month (DELETE /comments/{id}): whenHttp(startServer.getServer()). match(delete("/comments/1")). then(status(NO_CONTENT_204)). mustHappen(); whenHttp(startServer.getServer()). match(delete("/comments/2")). then(status(NO_CONTENT_204)). mustHappen(); // call business logic MyApp myApp = new MyApp(); List<Comment> comments = myApp.archiveOldComments(); // remaining 1 comment: assertThat(comments, hasSize(1)); // ensure that each stubs has been invoked: EnsureHttp.ensureHttp(startServer.getServer()).gotStubsCommitmentsDone(); }
And the test is fixed.
You are right, it is a little bit too verbose to use custom action. So I have decided to contribute the sequenced action directly to Restito Action, method sequence, see or comment or vote for my GitHub pull request: https://github.com/mkotsur/restito/pull/49. 😇
Enjoy,
Libor
Libor
March 27, 2016
How to run standalone Java EE MVC application
This project demonstrates how to create standalone Java EE MVC
application.
MVC is newly developed Java EE JSR 371: Model-View-Controller (MVC 1.0) Specification.
it is easy to bootstrap and shutdown CDI container:
You can see we also start and stop CDI ApplicationScoped scope.
Important part is registering of Weld Servlet listeners: WeldInitialListener and WeldTerminalListener.
The instance of Jersey ServletContainer is provided as ApplicationScoped bean by custom CdiProducer using CdiClassIntrospecter.
You can see we need to customize "javax.mvc.engine.ViewEngine.viewFolder" property to support standalone application class-path behaviour.
CdiClassIntrospecter implements Undertow ClassIntrospecter interface and just delegates to CDI.
You can see I've chosen to use Mustache template engine, see hello.mustache:
The easiest way how to run the project is:
The application is then available on: http://localhost:8080/resources/hello?user=Libor.
MVC is newly developed Java EE JSR 371: Model-View-Controller (MVC 1.0) Specification.
The MVC API defines an action-oriented Web framework as an alternative to the component-oriented JSF. In an action-oriented framework, developers are responsible for all the controller logic and are given full control of the URI space for their application.
The MVC API is layered on top of JAX-RS over Servlets and integrates with existing EE technologies like CDI and Bean Validation.MVC spec is going to be part of next Java EE 8. MVC reference implementation named Ozark is hosted on java.net. MVC depends on Servlets, JAX-RS and CDI. I've chosen to use Undertow, Jersey and Weld. And DeltaSpike as CDI extension library. Finally, Gradle to build and run the project.
ext { undertowVersion = '1.3.18.Final'
weldVersion = '2.3.3.Final' deltaspikeVersion = '1.5.4' jerseyVersion = '2.22.2' ozarkVersion = '1.0.0-m02' } task wrapper(type: Wrapper) { gradleVersion = '2.12' }
Standalone CDI container
Weld contains module to support Java SE applications. Together with Apache DeltaSpike Java SE & Weld module:dependencies { compile "org.jboss.weld.se:weld-se:$weldVersion" compile "org.apache.deltaspike.cdictrl:deltaspike-cdictrl-weld:$deltaspikeVersion"
it is easy to bootstrap and shutdown CDI container:
import javax.enterprise.context.ApplicationScoped;
import org.apache.deltaspike.cdise.api.CdiContainer; import org.apache.deltaspike.cdise.api.CdiContainerLoader; import org.jboss.weld.servlet.WeldInitialListener; import org.jboss.weld.servlet.WeldTerminalListener; public class Main { static CdiContainer init() throws ServletException, NoSuchMethodException { CdiContainer cdiContainer = CdiContainerLoader.getCdiContainer(); cdiContainer.boot(); cdiContainer.getContextControl().startContext(ApplicationScoped.class); initServlet(); return cdiContainer; } static void destroy(CdiContainer cdiContainer) { cdiContainer.getContextControl().stopContext(ApplicationScoped.class); cdiContainer.shutdown(); } ...
You can see we also start and stop CDI ApplicationScoped scope.
Initialize Servlet
Next we need to register Jersey ServletContainer as Undertow servlet:dependencies { compile "io.undertow:undertow-core:$undertowVersion" compile "io.undertow:undertow-servlet:$undertowVersion" compile "org.glassfish.jersey.core:jersey-server:$jerseyVersion" compile "org.glassfish.jersey.containers:jersey-container-servlet-core:$jerseyVersion" compile "org.glassfish.jersey.ext.cdi:jersey-cdi1x-servlet:$jerseyVersion"
import javax.servlet.Servlet; import javax.servlet.ServletException; import io.undertow.Undertow; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import org.jboss.weld.servlet.WeldInitialListener; import org.jboss.weld.servlet.WeldTerminalListener; public class Main { ... private static void initServlet() throws ServletException, NoSuchMethodException { DeploymentInfo deploymentInfo = new DeploymentInfo() .addListener(Servlets.listener(WeldInitialListener.class)) .addListener(Servlets.listener(WeldTerminalListener.class)) .setContextPath("/") .setDeploymentName("standalone-javax-mvc-app") .addServlets( createServletInfo("/resources/*", "JAX-RS Resources", org.glassfish.jersey.servlet.ServletContainer.class) ) .setClassIntrospecter(CdiClassIntrospecter.INSTANCE) .setAllowNonStandardWrappers(true) .setClassLoader(ClassLoader.getSystemClassLoader()); ServletContainer servletContainer = Servlets.defaultContainer(); DeploymentManager deploymentManager = servletContainer.addDeployment(deploymentInfo); deploymentManager.deploy(); Undertow server = Undertow.builder() .addHttpListener(8080, "0.0.0.0") .setHandler(deploymentManager.start()) .build(); server.start(); } private static ServletInfo createServletInfo(String mapping, String servletName, Class<? extends Servlet> servlet) throws NoSuchMethodException { ServletInfo servletInfo = Servlets .servlet(servletName, servlet) .setAsyncSupported(true) .setLoadOnStartup(1) .addMapping(mapping); servletInfo.setInstanceFactory(CdiClassIntrospecter.INSTANCE.createInstanceFactory(servlet)); return servletInfo; } }
Important part is registering of Weld Servlet listeners: WeldInitialListener and WeldTerminalListener.
The instance of Jersey ServletContainer is provided as ApplicationScoped bean by custom CdiProducer using CdiClassIntrospecter.
import javax.enterprise.inject.Produces; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.servlet.ServletContainer; @ApplicationScoped public class CdiProducer { private long helloCounter; @Produces @ApplicationScoped public ServletContainer createServletContainer() { final ResourceConfig resourceConfig = new ResourceConfig(); resourceConfig.register(HelloController.class); resourceConfig.property("javax.mvc.engine.ViewEngine.viewFolder", "WEB-INF/views/"); return new ServletContainer(resourceConfig); } ...
You can see we need to customize "javax.mvc.engine.ViewEngine.viewFolder" property to support standalone application class-path behaviour.
CdiClassIntrospecter implements Undertow ClassIntrospecter interface and just delegates to CDI.
MVC
Now you can provide MVC @Controller methods:dependencies { compile "org.glassfish.ozark:ozark:$ozarkVersion" compile "org.glassfish.ozark.ext:ozark-mustache:$ozarkVersion"
import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import javax.mvc.Models; import javax.mvc.annotation.Controller; import javax.mvc.annotation.View; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; @Path("hello") @ApplicationScoped public class HelloController { @Inject private Models models; @GET @Controller @Produces("text/html") @View("hello.mustache") public void hello(@QueryParam("user") @DefaultValue("World") String user) { models.put("user", user); } }
You can see I've chosen to use Mustache template engine, see hello.mustache:
<html> <head> <title>Hello</title> </head> <body> <h1>Hello {{user}}!</h1> </body> </html>
Sources
Complete project sources are available on GitHub, https://github.com/shamoh/standalone-javax-mvc.The easiest way how to run the project is:
gradlew run
The application is then available on: http://localhost:8080/resources/hello?user=Libor.
April 24, 2015
JAX-RS Kryo Message Body providers are placed to Jersey incubator
I have good news, mine JAX-RS Kryo support has been moved to Jersey incubator.
Original GitHub repository has been updated to use Jersey module. And as a bonus I've added two ways how to try it.
This is true since Jersey 2.18. The version was not released yet in time of writing this blog post. So you need to use 2.18-SNAPSHOT version and add Maven snapshot repository to your project:
You can enable
Again, you can enable FINEST logging using logging.properties:
Response should look like:
You can just add dependency on the module and your application can produce and consume application/x-kryo entities:
<dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-kryo</artifactId> <version>${jersey.version}</version> </dependency>
Original GitHub repository has been updated to use Jersey module. And as a bonus I've added two ways how to try it.
This is true since Jersey 2.18. The version was not released yet in time of writing this blog post. So you need to use 2.18-SNAPSHOT version and add Maven snapshot repository to your project:
<repositories> <repository> <id>java.net.snapshot</id> <url>https://maven.java.net/content/repositories/snapshots/</url> </repository> </repositories>
Grizzly it
Maven packaging of this demo application iswar
. But it is possible to run the application using Grizzly HTTP container:mvn exec:java
You can enable
FINEST
logging using logging.properties
:mvn exec:java -Djava.util.logging.config.file=target/test-classes/logging.properties
Jetty it
Or you can run it as common webapp using Jetty container:mvn jetty:run-war
Again, you can enable FINEST logging using logging.properties:
mvn jetty:run-war -Djava.util.logging.config.file=target/test-classes/logging.properties
Check it
GET method is available at URL http://localhost:8080/, check it usingcurl
:curl -v http://localhost:8080/
Response should look like:
> GET / HTTP/1.1 > User-Agent: curl/7.40.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 200 OK < Content-Length: 20 < Content-Type: application/x-kryo < Server: Jetty(8.1.16.v20140903) < Salzbur�*Wolfgan�
Origin
The Jersey Kryo support started as a "friday project". Evolution of the Kryo support shows list of previous blog posts:April 10, 2015
JAX-RS Kryo Message Body Writer & Reader performance
In this blog post I'm going to return to one of my previous post about JAX-RS Message Body Providers using Kryo serialization framework. Today I'll show you performance statistics and compare it with some of Jersey build-in providers.
Comparing to ...
I have chosen to compare my Kryo provider with:
- JAXB RI providers to read from and write to XML format.
- Jackson providers to read from and write to JSON format.
- Custom provider that reads and writes test entity (Person class) by itself. (Note: Used format is really simple and implementation is Person specific, uses artificial MIKME type application/person.)
- Just plain text provider to read and write String only. It uses Jersey build-in provider.
Test applications are very similar to mine Jersey Kryo module and are part of Jersey source code:
- JAXB/XML test application
- Jackson/JSON test application
- Custom provider test application
- Plain text provider test application
Kryo provider
The Kryo provider has been introduced in previous blog post. I have done one change since then. Performance of Kryo provider was several times improved using KryoPool:
@Consumes("application/x-kryo") @Produces("application/x-kryo") public class KryoMessageBodyProvider implements MessageBodyWriter<Object>, MessageBodyReader<Object> { private final KryoPool kryoPool; public KryoMessageBodyProvider() { final KryoFactory kryoFactory = new KryoFactory() { public Kryo create() { final Kryo kryo = new Kryo(); // configure kryo instance, customize settings return kryo; } }; kryoPool = new KryoPool.Builder(kryoFactory).softReferences().build(); } // // MessageBodyWriter // @Override public long getSize(final Object object, final Class<?> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { return -1; } @Override public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { return true; } @Override public void writeTo(final Object object, final Class<?> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream) throws IOException, WebApplicationException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final Output output = new Output(baos); kryoPool.run(new KryoCallback() { public Object execute(Kryo kryo) { kryo.writeObject(output, object); return null; } }); output.close(); entityStream.write(baos.toByteArray()); } // // MessageBodyReader // @Override public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { return true; } @Override public Object readFrom(final Class<Object> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) throws IOException, WebApplicationException { try { final Input input = new Input(entityStream); final Object object = kryoPool.run(new KryoCallback() { public Object execute(Kryo kryo) { return kryo.readObject(input, type); } }); input.close(); return object; } catch (Exception e) { throw new WebApplicationException(e); } } }
Test environment
Each test application was executed using Grizzly HTTP Server, no servlet container, on 4 core VM. And load has been produced from another 2 client VMs using wrk - a HTTP benchmarking tool. GET, POST and PUT HTTP methods was measured. It is necessary to say each test works just with one instance of Person entity. It means data load is small. Test's focus is on throughput measurement.
Test results
Following table shows number of requests per second for each tested provider and mentioned HTTP method:
GET | POST | PUT | |
XML (JAXB RI) | 21705 | 11316 | 12080 |
JSON (Jackson) | 22097 | 18590 | 20116 |
Kryo | 21533 | 18371 | 21304 |
Custom | 21543 | 18884 | 20442 |
TEXT (String) | 21816 | 17695 | 19882 |
Conclusion
Please note the Kryo provider is not in production quality. It just demonstrates how simple it is to enhance JAX-RS runtime to support new entity media type. However it is naive implementation of message body provider it provides very good performance. Kryo also support class registration and compression features that can also affect performance.
And finally, test applications work just with one instance of entity (Person). I guess Kryo could show it's power while working with more complex objects or with collection of them. And Kryo compression feature would be very strong in such case. Maybe I could prepare such test in couple of next months. ;-)
And finally, test applications work just with one instance of entity (Person). I guess Kryo could show it's power while working with more complex objects or with collection of them. And Kryo compression feature would be very strong in such case. Maybe I could prepare such test in couple of next months. ;-)
Labels:
jaxrs,
jersey,
kryo,
performance
Subscribe to:
Posts (Atom)