This tutorial demonstrates REST with WoOF.
The example used in this tutorial is two end points:
To configure REST end-points with WoOF, use the following from the palette:
The HTTP Continuation is separate, as HTTP redirects are continuations via GET methods. Therefore, it is possible to connect to this to trigger a redirect to the respective URL.
The configuration of the two end points are as follows:
The handling of the end-points is provided by connecting them to other items. In this case, procedures provide the implementation of the end points.
Similar to AJAX with templates tutorial object parsing and responding is via the @HttpObject and ObjectResponse. The below is the object used in this tutorial:
@Entity @HttpObject @Data @NoArgsConstructor @RequiredArgsConstructor public class Vehicle { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @NonNull @Column(name = "VEHICLE_TYPE") private String vehicleType; @NonNull private Integer wheels; }
For convenience, the Entity object is re-used for parsing out the POST pay load.
The implementation of the POST handling is the following:
public void createVehicle(Vehicle vehicle, EntityManager entityManager) { if ((vehicle == null) || (vehicle.getVehicleType() == null)) { throw new HttpException(new HttpStatus(444, "Must have vehicleType")); } entityManager.persist(vehicle); }
As the JSON data is loaded onto the object, it is passed to the EntityManager to store within the database.
Should there be invalid data, a HttpException can be thrown to specify the response status code and entity pay load. In this case, a custom status code is used.
Using {...} within the path specifies a path parameter.
In the case of this tutorial, this is used to specify the vehicle identifier within the URL.
Note that multiple path parameters may be specified in the URL along with ability to specify them where ever appropriate in the URL. The only requirement is that path parameters in the URL are separated by at least one character.
The following demonstrates passing the path parameter value to the handling logic:
public void getVehicle(@HttpPathParameter("id") String vehicleId, EntityManager entityManager, ObjectResponse<Vehicle> responder) { responder.send(entityManager.find(Vehicle.class, Integer.parseInt(vehicleId))); }
The @HttpPathParameter annotation specifies to load the path value. See the other annotations in the package for extracting various aspects of the HTTP request.
The following shows the ease of testing the REST end points:
@RegisterExtension public final MockWoofServerExtension server = new MockWoofServerExtension(); @Test public void postMissingData() throws Exception { // POST with missing data MockHttpResponse response = this.server .send(MockHttpServer.mockRequest("/vehicle").method(HttpMethod.POST).entity("{}")); response.assertResponse(444, "{\"error\":\"Must have vehicleType\"}"); } @Test public void postEntry(EntityManager entityManager) throws Exception { // POST to create row and validate successful MockHttpResponse response = this.server.send(MockHttpServer.mockRequest("/vehicle").method(HttpMethod.POST) .header("content-type", "application/json").entity("{ \"vehicleType\": \"bike\", \"wheels\": 2 }")); response.assertResponse(204, ""); // Ensure row created Vehicle vehicle = entityManager.createQuery("SELECT V FROM Vehicle V WHERE vehicleType = 'bike'", Vehicle.class) .getSingleResult(); assertNotNull(vehicle, "Should have row created"); assertEquals(2, vehicle.getWheels().intValue(), "Incorrect row"); } @Test public void getEntry(EntityManager entityManager) throws Exception { // Create entry Vehicle vehicle = new Vehicle("car", 4); entityManager.persist(vehicle); JpaManagedObjectSource.commitTransaction(entityManager); // GET entry MockHttpResponse response = this.server.send(MockHttpServer.mockRequest("/vehicle/" + vehicle.getId())); assertEquals(200, response.getStatus().getStatusCode(), "Should be successful"); response.assertHeader("content-type", "application/json"); JsonNode entity = new ObjectMapper().readTree(response.getEntity(null)); assertEquals(vehicle.getId().intValue(), entity.get("id").asInt(), "Incorrect id"); assertEquals("car", entity.get("vehicleType").asText(), "Incorrect vehicle type"); assertEquals(4, entity.get("wheels").asInt(), "Incorrect wheels"); }
However, as the logic is a POJO (plain old java object), the logic can also be unit tested as follows:
@Test public void createWithMissingData(EntityManager entityManager) { try { new RestLogic().createVehicle(new Vehicle(), entityManager); fail("Should not be successful"); } catch (HttpException ex) { assertEquals(444, ex.getHttpStatus().getStatusCode(), "Incorrect status"); assertEquals("Must have vehicleType", ex.getMessage(), "Incorrect reason"); } } @Test public void createVehicle(EntityManager entityManager) { // Create the vehicle new RestLogic().createVehicle(new Vehicle("tricycle", 3), entityManager); // Ensure row created Vehicle vehicle = entityManager .createQuery("SELECT V FROM Vehicle V WHERE vehicleType = 'tricycle'", Vehicle.class).getSingleResult(); assertNotNull(vehicle, "Should have row created"); assertEquals(3, vehicle.getWheels().intValue(), "Incorrect row"); } @Test public void getVehicle(EntityManager entityManager) { // Create entry Vehicle created = new Vehicle("unicycle", 1); entityManager.persist(created); // Obtain the vehicle MockObjectResponse<Vehicle> response = new MockObjectResponse<>(); new RestLogic().getVehicle(String.valueOf(created.getId()), entityManager, response); Vehicle vehicle = response.getObject(); assertEquals("unicycle", vehicle.getVehicleType(), "Incorrect row sent"); }
The next tutorial covers serving static (files) content for single page applications.