Fork me on GitHub

REST Tutorial

This tutorial demonstrates REST with WoOF.

The example used in this tutorial is two end points:

  • POST /vehicle {"vehicleType":"car","wheels":4} to create a vehicle entry
  • GET /vehicle/{id} to obtain the vehicle entry by the vehicle's identifier

Tutorial Source

WoOF configuration

To configure REST end-points with WoOF, use the following from the palette:

  • HTTP Continuation to provide GET end points
  • HTTP Input to provide other HTTP method end points

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:

RestHttpServer screen shot.

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.

Object parsing / responding

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.

Path parameters

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.

Testing

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");
	}

Next

The next tutorial covers serving static (files) content for single page applications.