3
\$\begingroup\$

I practiced my Java knowledge in the last couple of days. For that, I want to create a Rest API in a trimmed scale, because there a lot of aspects which must be considered in such an API (Login, Session handling ...). A lot of details aren't implemented, but the top priority was to understand the interaction/communication of the different modules and using some best practices.

Here is my project so far.

The Interface for a Resource:

package server;

import java.util.HashMap;

public interface Resource {

    public Response run(HttpMethod method, HashMap<String, String> params) throws WebException;

}

The Base Class for a Resource:

package server;

import java.util.ArrayList;

public abstract class ResourceBase implements Resource {

    protected List<HttpMethod> allowedMethods = new ArrayList<>();
    protected HttpMethod httpMethod;
    protected HashMap<String, String> params;

    protected boolean needsAuthentication = true;

    protected abstract Response runResource() throws WebException;

    @Override
    public Response run(HttpMethod method, HashMap<String, String> params) throws WebException {

        this.httpMethod = method;
        this.params = params;

        if(!isMethodAllowed(method)) throw new MethodNotAllowedException("Method not allowed");
        if(this.needsAuthentication && !isUserAuthenticated(params.get("userName"))) throw new UnauthorizedException("no Session for User");

        return runResource();
    }

    private boolean isMethodAllowed(HttpMethod method) {
        return this.allowedMethods.stream().filter(m -> m == method).findAny().isPresent();
    }

    private boolean isUserAuthenticated(String userName) throws UnauthorizedException {
        return SessionHandler.getInstance().hasSession(userName) ? true : false;
    }

}

A concrete Implementation for a Resource. This a Resource for the User's Login:

package server;

import java.util.Optional;

public class LoginResource extends ResourceBase
{
    public LoginResource() {
        this.needsAuthentication = false;
        this.allowedMethods.add(HttpMethod.PUT);
    }

    @Override
    protected Response runResource() throws WebException {

        if(this.httpMethod == HttpMethod.PUT) return runPut();

        // will never reached because the allowed methods will be checked before
        return null;
    }

    private Response runPut() throws WebException {
        String paramUserName = this.params.get("userName");

        if(!SessionHandler.getInstance().hasSession(paramUserName)) {
            SessionHandler.getInstance().createSession(paramUserName);
        }

        return new Response(HttpCode.OK, Optional.empty());
    }
}

The Login Resource uses the SessionHandler, which will start/end the connections and will cache them (as a result I used the Singleton Pattern):

package server;

import java.util.ArrayList;

public class SessionHandler {

    private List<User> sessions = new ArrayList<>();

    public static SessionHandler getInstance() {
        return SingletonHelper.INSTANCE;
    }

    private static class SingletonHelper {
        private static final SessionHandler INSTANCE = new SessionHandler();
    }

    private SessionHandler() {}

    public void createSession(String userName) throws PermissionDeniedException {
        User user = UserDatabase.getInstance().getUser(userName).orElseThrow( () -> new PermissionDeniedException( "User doesn't exist!" ) );;
        sessions.add(user);
    }

    public boolean hasSession(String userName) {
        return sessions.stream().filter(u -> u.getName().equalsIgnoreCase(userName)).findAny().isPresent();
    }

    public void destroySession(String userName) {
        sessions = sessions.stream().filter(u -> !u.getName().equalsIgnoreCase(userName)).collect(Collectors.toList());
    }
}

The Response Class:

package server;

import java.util.Optional;

public class Response {

    private HttpCode httpCode;
    private Optional<String> value;

    Response(HttpCode httpCode, Optional<String> value) {
        this.httpCode = httpCode;
        this.value = value;
    }

    public boolean wasSuccessful() {
        return this.httpCode.getCode() >= 200 && this.httpCode.getCode() < 300 ? true : false;
    }

    public HttpCode getHttpCode() {
        return httpCode;
    }

    public void setHttpCode(HttpCode httpCode) {
        this.httpCode = httpCode;
    }

    public Optional<String> getValue() {
        return value;
    }

    public void setValue(Optional<String> value) {
        this.value = value;
    }
}

The "main" class for the Server's API is this:

package server;

import java.util.HashMap;

public class RestApi {

    public Response executeResource(String resourceName, HttpMethod method, HashMap<String, String> params) {

        try {
            Resource res = getResource(resourceName).orElseThrow(() -> new NotFoundException("Resource" + resourceName + "not found"));
            return res.run(method, params);
        }
        catch (WebException ex) {
            System.err.printf("Error during request: %s. Http Code: %d.%n", ex.getMessage(), ex.getHttpCode().getCode());
            return new Response(ex.getHttpCode(), Optional.empty());
        }
    }

    private Optional<Resource> getResource(String name)
    {
        switch(name) {
            case "login":
                return Optional.of(new LoginResource());

            case "update-role":
                return Optional.of(new RoleResource());

            default:
                return Optional.empty();
        }
    }
}

The concrete Resource will be determined on the passed resourceName.

And here are the Exception Types. Starting with the Base Class:

package server;

public class WebException extends Exception
{
    private static final long serialVersionUID = 1L;

    private HttpCode httpCode;

    public WebException(HttpCode httpCode, String errorMessage) {
        super(errorMessage);

        this.httpCode = httpCode;
    }

    public HttpCode getHttpCode() {
        return httpCode;
    }
}

And here is one subtype (the subtyes differ only in the http code):

package server;

public class MethodNotAllowedException extends WebException {

    private static final long serialVersionUID = 1L;

    public MethodNotAllowedException(String errorMessage) {
        super(HttpCode.METHOD_NOT_ALLOWED, errorMessage);
        // TODO Auto-generated constructor stub
    }
}

The enum for the possible http codes:

package server;

public enum HttpCode {

    OK(200),
    UNAUTHORIZED(401),
    PERMISSION_DENIED(403),
    NOT_FOUND(404),
    METHOD_NOT_ALLOWED(405);


    private final Integer code;

    HttpCode(Integer httpCode) {
        this.code = httpCode;
    }

    public Integer getCode() {
        return this.code;
    }
}

And the enum for the http methods:

package server;

public enum HttpMethod {

    PUT, 
    POST, 
    GET, 
    DELETE
}

And this is a call from the client for this resource:

return api.executeResource("login", HttpMethod.PUT, params).wasSuccessful() ? true : false;

It would be very nice if someone can check the source code and give me some hints, what an experienced progamer would do in a different way.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.