All Downloads are FREE. Search and download functionalities are using the official Maven repository.

restx.entity.StdEntityRoute Maven / Gradle / Ivy

There is a newer version: 1.2.0-rc2
Show newest version
package restx.entity;

import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import restx.*;
import restx.endpoint.*;
import restx.endpoint.mappers.EndpointParameterMapper;
import restx.factory.ParamDef;
import restx.http.HttpStatus;
import restx.security.Permission;
import restx.security.PermissionFactory;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.*;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * User: xavierhanin
 * Date: 1/19/13
 * Time: 8:10 AM
 */
public abstract class StdEntityRoute extends StdRoute {
    protected static final Supplier EMPTY_LIST_SUPPLIER = new Supplier() {
        @Override
        public List get() {
            return Collections.emptyList();
        }
    };
    protected static final Supplier EMPTY_SET_SUPPLIER = new Supplier() {
        @Override
        public Set get() {
            return Collections.emptySet();
        }
    };
    protected static final Supplier EMPTY_ITERABLE_SUPPLIER = new Supplier() {
        @Override
        public Iterable get() {
            return Collections.emptyList();
        }
    };
    protected static final Supplier EMPTY_COLLECTION_SUPPLIER = new Supplier() {
        @Override
        public Collection get() {
            return Collections.emptySet();
        }
    };

    public static class Builder {
        protected EntityRequestBodyReader entityRequestBodyReader;
        protected EntityResponseWriter entityResponseWriter;
        protected EndpointParameterMapperRegistry registry;
        protected String name;
        protected Endpoint endpoint;
        protected ParamDef[] queryParameters = new ParamDef[0];
        protected HttpStatus successStatus = HttpStatus.OK;
        protected RestxLogLevel logLevel = RestxLogLevel.DEFAULT;
        protected PermissionFactory permissionFactory;
        protected MatchedEntityRoute matchedEntityRoute;

        public Builder entityRequestBodyReader(final EntityRequestBodyReader entityRequestBodyReader) {
            this.entityRequestBodyReader = entityRequestBodyReader;
            return this;
        }

        public Builder entityResponseWriter(final EntityResponseWriter entityResponseWriter) {
            this.entityResponseWriter = entityResponseWriter;
            return this;
        }

        public Builder name(final String name) {
            this.name = name;
            return this;
        }

        public Builder permissionFactory(final PermissionFactory permissionFactory) {
            this.permissionFactory = permissionFactory;
            return this;
        }

        public Builder endpoint(final Endpoint endpoint) {
            this.endpoint = endpoint;
            return this;
        }

        public Builder registry(final EndpointParameterMapperRegistry registry) {
            this.registry = registry;
            return this;
        }

        public Builder queryParameters(final ParamDef[] queryParameters) {
            this.queryParameters = queryParameters;
            return this;
        }

        public Builder successStatus(final HttpStatus successStatus) {
            this.successStatus = successStatus;
            return this;
        }

        public Builder logLevel(final RestxLogLevel logLevel) {
            this.logLevel = logLevel;
            return this;
        }

        public Builder matchedEntityRoute(final MatchedEntityRoute matchedEntityRoute) {
            this.matchedEntityRoute = matchedEntityRoute;
            return this;
        }

        public StdEntityRoute build() {
            checkNotNull(matchedEntityRoute, "you must provide a matchedEntityRoute");
            return new StdEntityRoute(
                    name, entityRequestBodyReader == null ? voidBodyReader() : entityRequestBodyReader,
                    entityResponseWriter,
                    endpoint, successStatus, logLevel, permissionFactory, registry, queryParameters) {
                @Override
                protected Optional doRoute(RestxRequest restxRequest, RestxResponse response, RestxRequestMatch match, I i) throws IOException {
                    return matchedEntityRoute.route(restxRequest, match, i);
                }
            };
        }

        /*
           We want to give a default value to entityRequestBodyReader to void.
           It would be better to do that only if I is Void, but generics aren't reified so we can't check that.
           So we need to cast it to I, without really knowing if I is Void.
         */
        @SuppressWarnings("unchecked")
        private EntityRequestBodyReader voidBodyReader() {
            return (EntityRequestBodyReader) VoidContentTypeModule.VoidEntityRequestBodyReader.INSTANCE;
        }
    }

    public static  Builder builder() {
        return new Builder<>();
    }

    private static class EndpointParameterMapperAndDef {
        EndpointParameterMapper mapper;
        EndpointParamDef endpointParamDef;

        public EndpointParameterMapperAndDef(EndpointParameterMapper mapper, EndpointParamDef endpointParamDef) {
            this.mapper = mapper;
            this.endpointParamDef = endpointParamDef;
        }
    }

    private final EntityRequestBodyReader entityRequestBodyReader;
    private final EntityResponseWriter entityResponseWriter;
    private final RestxLogLevel logLevel;
    private final Endpoint endpoint;
    private final PermissionFactory permissionFactory;
    private final Map cachedQueryParameterMappers;

    public StdEntityRoute(String name,
                          EntityRequestBodyReader entityRequestBodyReader,
                          EntityResponseWriter entityResponseWriter,
                          Endpoint endpoint,
                          HttpStatus successStatus,
                          RestxLogLevel logLevel,
                          PermissionFactory permissionFactory,
                          EndpointParameterMapperRegistry registry) {
        this(name, entityRequestBodyReader, entityResponseWriter, endpoint, successStatus,
                logLevel, permissionFactory, registry, new ParamDef[0]);
    }

    public StdEntityRoute(String name,
                          EntityRequestBodyReader entityRequestBodyReader,
                          EntityResponseWriter entityResponseWriter,
                          Endpoint endpoint,
                          HttpStatus successStatus,
                          RestxLogLevel logLevel,
                          PermissionFactory permissionFactory,
                          EndpointParameterMapperRegistry registry,
                          ParamDef[] queryParametersDefinition
    ) {
        super(name, new StdRestxRequestMatcher(endpoint), successStatus);
        this.endpoint = endpoint;
        this.permissionFactory = permissionFactory;
        this.entityRequestBodyReader = checkNotNull(entityRequestBodyReader);
        this.entityResponseWriter = checkNotNull(entityResponseWriter);
        this.logLevel = checkNotNull(logLevel);
        this.cachedQueryParameterMappers = cacheQueryParameterMappers(registry, endpoint, queryParametersDefinition);
    }

    private static Map cacheQueryParameterMappers(
            EndpointParameterMapperRegistry registry, Endpoint endpoint, ParamDef[] parameters) {
        Map cachedParameterMappers = new HashMap<>();
        for(ParamDef parameter : parameters){
            EndpointParamDef endpointParamDefDef = new EndpointParamDef(endpoint, parameter);
            cachedParameterMappers.put(
                    parameter.getName(),
                    new EndpointParameterMapperAndDef(registry.getEndpointParameterMapperFor(endpointParamDefDef), endpointParamDefDef));

        }
        return cachedParameterMappers;
    }

    /**
     * The Java type of I, the entity into which request body will be unmarshalled.
     *
     * @return I type
     */
    public Type getEntityRequestBodyType() {
        return entityRequestBodyReader.getType();
    }

    /**
     * The Java type of O, the entity from which response body will be marshalled.
     *
     * @return O type
     */
    public Type getEntityResponseType() {
        return entityResponseWriter.getType();
    }

    protected  T mapQueryObjectFromRequest(Class targetType, String parameterName, RestxRequest request, RestxRequestMatch match, EndpointParameterKind endpointParameterKind){
        return mapQueryObjectFromRequest(targetType, parameterName, request, match, endpointParameterKind, null);
    }

    protected  T mapQueryObjectFromRequest(Class targetType, String parameterName, RestxRequest request, RestxRequestMatch match, EndpointParameterKind endpointParameterKind, Supplier nullResultDefaultValueSupplier){
        EndpointParameterMapperAndDef endpointParameterMapperAndDef = cachedQueryParameterMappers.get(parameterName);
        if(endpointParameterMapperAndDef == null) {
            throw new IllegalStateException("No cachedQueryParameterMappers for parameter "+parameterName+" : please provide corresponding ParamDef at instanciation time !");
        }

        T result = endpointParameterMapperAndDef.mapper.mapRequest(
                endpointParameterMapperAndDef.endpointParamDef,
                request, match, endpointParameterKind);

        // In case we have a null result *and* a null result default value supplier, let's use it
        if(nullResultDefaultValueSupplier != null && result == null) {
            result = nullResultDefaultValueSupplier.get();
        }

        return result;
    }

    @Override
    public void handle(RestxRequestMatch match, RestxRequest req, RestxResponse resp, RestxContext ctx) throws IOException {
        RouteLifecycleListener lifecycleListener = ctx.getLifecycleListener();
        resp.setLogLevel(logLevel);

        lifecycleListener.onRouteMatch(this, req, resp);
        I input = entityRequestBodyReader.readBody(req, ctx);
        Optional optionalInput = Optional.fromNullable(input);
        lifecycleListener.onEntityInput(this, req, resp, optionalInput);
        Optional result = doRoute(req, resp, match, input);
        lifecycleListener.onEntityOutput(this, req, resp, optionalInput, result);
        if (result.isPresent()) {
            entityResponseWriter.sendResponse(getSuccessStatus(), result.get(), req, resp, ctx);
        } else {
            notFound(match, resp);
        }
    }

    protected abstract Optional doRoute(RestxRequest restxRequest, RestxResponse restxResponse, RestxRequestMatch match, I i) throws IOException;

    // Aliases to permissionFactory allowing to have a more readable generated code through APT
    protected Permission hasRole(String role){ return permissionFactory.hasRole(role); }
    protected Permission anyOf(Permission... permissions){ return permissionFactory.anyOf(permissions); }
    protected Permission allOf(Permission... permissions){ return permissionFactory.allOf(permissions); }
    protected Permission open(){ return permissionFactory.open(); }
    protected Permission isAuthenticated(){ return permissionFactory.isAuthenticated(); }
}