Janik Vonrotz


8 min read

Build a Java 3-tier application from scratch – Part 4: Webservice

Our ORM is working so we are ready to create our JAX-RS restful webservice and implement a custom authentication mechanism. In addition I’ll provide you a class to insert dummy data into the database, so you don’t have to do it manually.

RESTeasy servlet

RESTeasy is our JAX-RS provider and client component. In this part we will conigure a RESTeasy servlet and add some routes to handle by the webservice.

The resources class mapps actions to url paths, a provider hooks up into every request done by a client such as authentication or access filtering.

Our Route class file has the most lines of code. But don’t be anxious, it contains a lot of repetetive configurations. For every model we have to define the CRUD methods. A DRY (don’t repeat yourself) approach is very difficult here, as a lot is going on behind the scenes. I only could stack the get methods together. That means 5 models * 3 CUD-methods + 2 R-methods + 1 login method = 18 path actions for our webservice.

Route.java

package ch.issueman.webservice;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import ch.issueman.common.Employer;
import ch.issueman.common.Model;
import ch.issueman.common.Person;
import ch.issueman.common.User;
import ch.issueman.common.Project;
import ch.issueman.common.Comment;
import ch.issueman.common.DAO;

@Path("/")
public class Route{
	
	private Map <String, DAO<?, Integer>> hm = new HashMap<String, DAO<?, Integer>>();

	public Route(){
		hm.put("person", new Controller<Person, Integer>(Person.class));
		hm.put("user", new Controller<User, Integer>(User.class));
		hm.put("employer", new Controller<Employer, Integer>(Employer.class));
		hm.put("project", new Controller<Project, Integer>(Project.class));
		hm.put("comment", new Controller<Comment, Integer>(Comment.class));	
	}
	
	@RolesAllowed("Administrator")
	@GET
	@Path("{entity}/{id}")
	@Produces(MediaType.APPLICATION_JSON)
	public Model getEntityById(@PathParam("entity") String entity, @PathParam("id") int id) {
		return (Model) hm.get(entity).getById(id);
	} 	
	
	@PermitAll
	@SuppressWarnings("unchecked")
	@GET
	@Path("{entity}")
	@Produces(MediaType.APPLICATION_JSON)
	public List<Model> getAll(@PathParam("entity") String entity) {
		return (List<Model>) hm.get(entity).getAll();
	}	
	
	@PermitAll
	@SuppressWarnings({ "unchecked" })
	@POST
	@Path("login")
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	public Response login(User user) {
		
		List<User> users = ((List<User>) hm.get("user").getAll()).stream()
				.filter(p -> p.getEmail().equals(user.getEmail()))
				.filter(p -> p.getPassword().equals(user.getPassword()))
				.collect(Collectors.toList());
		
		if(users.size() == 1){
			return Response.status(Status.OK).entity(users.get(0)).build();
		}else{
			return Response.status(Status.UNAUTHORIZED).build();
		}
	}
	
	/**
	 * Person
	 */
	@RolesAllowed("Administrator")
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@DELETE
	@Path("person/{id}")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response deletePerson(@PathParam("id") int id) {
		((DAO) hm.get("person")).delete(((DAO) hm.get("person")).getById(id));
		return Response.status(Status.OK).entity("Person deleted").build();
	}		
	@RolesAllowed("Administrator")
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@PUT
	@Path("person")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response updatePerson(Person t) {
		((DAO) hm.get("person")).update(t);
		return Response.status(Status.OK).entity("Person updated").build();
	}		
	@RolesAllowed("Administrator")
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@POST
	@Path("person")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response persistPerson(Person t) {
		((DAO) hm.get("person")).persist(t);
		return Response.status(Status.OK).entity("Person added").build();
	}
		
	/**
	 * Employer
	 */
	@RolesAllowed("Administrator")
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@DELETE
	@Path("employer/{id}")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response deleteEmployer(@PathParam("id") int id) {
		((DAO) hm.get("employer")).delete(((DAO) hm.get("employer")).getById(id));
		return Response.status(Status.OK).entity("Employer deleted").build();
	}		
	@RolesAllowed("Administrator")
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@PUT
	@Path("employer")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response updateEmployer(Employer t) {
		((DAO) hm.get("employer")).update(t);
		return Response.status(Status.OK).entity("Employer updated").build();
	}		
	@RolesAllowed("Administrator")
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@POST
	@Path("employer")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response persistEmployer(Employer t) {
		((DAO) hm.get("employer")).persist(t);
		return Response.status(Status.OK).entity("Employer added").build();
	}
	
	/**
	 * User
	 */
	@RolesAllowed("Administrator")
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@DELETE
	@Path("user/{id}")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response deleteUser(@PathParam("id") int id) {
		((DAO) hm.get("user")).delete(((DAO) hm.get("user")).getById(id));
		return Response.status(Status.OK).entity("User deleted").build();
	}		
	@RolesAllowed("Administrator")
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@PUT
	@Path("user")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response updateUser(User t) {
		((DAO) hm.get("user")).update(t);
		return Response.status(Status.OK).entity("User updated").build();
	}		
	@RolesAllowed("Administrator")
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@POST
	@Path("user")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response persistUser(User t) {
		((DAO) hm.get("user")).persist(t);
		return Response.status(Status.OK).entity("User added").build();
	}
	
	/**
	 * Project
	 */
	@RolesAllowed("Administrator")
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@DELETE
	@Path("project/{id}")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response deleteProject(@PathParam("id") int id) {
		((DAO) hm.get("project")).delete(((DAO) hm.get("project")).getById(id));
		return Response.status(Status.OK).entity("Project deleted").build();
	}		
	@RolesAllowed("Administrator")
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@PUT
	@Path("project")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response updateProject(Project t) {
		((DAO) hm.get("project")).update(t);
		return Response.status(Status.OK).entity("Project updated").build();
	}		
	@RolesAllowed("Administrator")
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@POST
	@Path("project")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response persistProject(Project t) {
		((DAO) hm.get("project")).persist(t);
		return Response.status(Status.OK).entity("Project added").build();
	}
	
	/**
	 * Comment
	 */
	@RolesAllowed("Administrator")
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@DELETE
	@Path("comment/{id}")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response deleteComment(@PathParam("id") int id) {
		((DAO) hm.get("comment")).delete(((DAO) hm.get("comment")).getById(id));
		return Response.status(Status.OK).entity("Comment deleted").build();
	}		
	@RolesAllowed("Administrator")
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@PUT
	@Path("comment")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response updateComment(Comment t) {
		((DAO) hm.get("comment")).update(t);
		return Response.status(Status.OK).entity("Comment updated").build();
	}		
	@RolesAllowed("Administrator")
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@POST
	@Path("comment")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response persistComment(Comment t) {
		((DAO) hm.get("comment")).persist(t);
		return Response.status(Status.OK).entity("Comment added").build();
	}
}

Holy guacamoly, that’s a brain f**ker. Let’s do a deep dive.

I hope you get the other lines of code. Lets move on to the authentication provider.

Authentication provider

Our application uses basic http authentication header. That means we have to add a username and a password into the request header for every client access. With the authentication provider we check every request for a valid user and it’s roles. The authentication provider implements the ContainerRequestFilter which allows you to filter specific client-server communication directions.

Request and Response Filter

ContainerRequestFilter - Filter/modify inbound requests ContainerResponseFilter - Filter/modify outbound responses ClientRequestFilter - Filter/modify outbound requests ClientResponseFilter - Filter/modify inbound responses

This class is an implementation of the ContainerRequestFilter interface. Wasn’t sure wether I should explain this in detail. It’s actually very simple: the request context contains our header data, from there it extracts, converts and validates the basic authentication data. Next the provider checks for added security annotations and runs related checks. With an additional function it looks up the user credentials and roles in our database via the user model controller.

This was the last piece of our controller. Finally we want to insert some data to dispaly and work with in the client.

Insert fake data

The javafaker library creates random data on every method call. We will use this feature to fill our database.

Seed.java

package ch.issueman.webservice;

import java.util.ArrayList;
import java.util.List;

import ch.issueman.common.Comment;
import ch.issueman.common.Employer;
import ch.issueman.common.Person;
import ch.issueman.common.Project;
import ch.issueman.common.User;

import com.github.javafaker.Faker;

public class Seed {

	public static void main(String[] args) {
		
		Faker faker = new Faker();

		Controller<Person, Integer> personcontroller = new Controller<Person, Integer>(Person.class);
		Controller<User, Integer> usercontroller = new Controller<User, Integer>(User.class);
		Controller<Employer, Integer> employercontroller = new Controller<Employer, Integer>(Employer.class);
		Controller<Project, Integer> projectcontroller = new Controller<Project, Integer>(Project.class);
		Controller<Comment, Integer> commentcontroller = new Controller<Comment, Integer>(Comment.class);

		usercontroller.deleteAll();
		employercontroller.deleteAll();
		projectcontroller.deleteAll();
		personcontroller.deleteAll();
		commentcontroller.deleteAll();
		
		int i = 0;
		int j = 0;
		
		for (i = 0; i <= 20; i++) {

			Person person = new Person(faker.name().firstName());
			User user = new User(faker.name().firstName(), faker.internet().emailAddress(), faker.letterify("??????"), "Administrator");
			Employer employer = new Employer(faker.name().firstName(), faker.name().lastName());

			personcontroller.persist(person);
			usercontroller.persist(user);
			employercontroller.persist(employer);

			if (i % 4 == 0) {
				
				Project project = new Project("Project: " + faker.name().lastName(), employer);
				
				List<Comment> comments = new ArrayList<Comment>();
				
				for (j = 0; j <= 10; j++) {
					
					Comment comment = new Comment(faker.lorem().paragraph(),user);
					comments.add(comment);
					project.setComments(comments);
				}

				projectcontroller.persist(project);
			}
		}
		
		System.out.println("Seeded: " + i*3 + " people");
		System.out.println("Seeded: " + (i/4+1) + " projects");
		System.out.println("Seeded: " + j*(i/4+1) + " comments");
	}
}

Do not run this class within eclipse, we will use gradle to execute the main method. But before we do that we have to compile the classes and update the eclipse project metadata.

Now we are ready to seed.

You should something like this:

Seeded: 63 people
Seeded: 6 projects
Seeded: 66 comments

We got the code we got the data, it’s time to run the webservice.

If you see some json data, you did very well. If not, post the exception in the comment section and I’ll help you.

In the next two part we are going to write a simple client application that consumes our webservice. See you.

Update

Links

Source

Securing JAX-RS RESTful services JAX-RS RESTEasy basic authentication and authorization tutorial
Filtering JAX-RS Entities with Standard Security Annotations

Categories: Software development
Tags: java , jax-rs , jetty , resteasy , tomcat , webservice
Improve this page
Show statistic for this page