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<I,O> extends StdRoute {
protected static final Supplier<List> EMPTY_LIST_SUPPLIER = new Supplier<List>() {
@Override
public List get() {
return Collections.emptyList();
}
};
protected static final Supplier<Set> EMPTY_SET_SUPPLIER = new Supplier<Set>() {
@Override
public Set get() {
return Collections.emptySet();
}
};
protected static final Supplier<Iterable> EMPTY_ITERABLE_SUPPLIER = new Supplier<Iterable>() {
@Override
public Iterable get() {
return Collections.emptyList();
}
};
protected static final Supplier<Collection> EMPTY_COLLECTION_SUPPLIER = new Supplier<Collection>() {
@Override
public Collection get() {
return Collections.emptySet();
}
};
public static class Builder<I,O> {
protected EntityRequestBodyReader<I> entityRequestBodyReader;
protected EntityResponseWriter<O> 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<I,O> matchedEntityRoute;
public Builder<I,O> entityRequestBodyReader(final EntityRequestBodyReader<I> entityRequestBodyReader) {
this.entityRequestBodyReader = entityRequestBodyReader;
return this;
}
public Builder<I,O> entityResponseWriter(final EntityResponseWriter<O> entityResponseWriter) {
this.entityResponseWriter = entityResponseWriter;
return this;
}
public Builder<I,O> name(final String name) {
this.name = name;
return this;
}
public Builder<I,O> permissionFactory(final PermissionFactory permissionFactory) {
this.permissionFactory = permissionFactory;
return this;
}
public Builder<I,O> endpoint(final Endpoint endpoint) {
this.endpoint = endpoint;
return this;
}
public Builder<I,O> registry(final EndpointParameterMapperRegistry registry) {
this.registry = registry;
return this;
}
public Builder<I,O> queryParameters(final ParamDef[] queryParameters) {
this.queryParameters = queryParameters;
return this;
}
public Builder<I,O> successStatus(final HttpStatus successStatus) {
this.successStatus = successStatus;
return this;
}
public Builder<I,O> logLevel(final RestxLogLevel logLevel) {
this.logLevel = logLevel;
return this;
}
public Builder<I,O> matchedEntityRoute(final MatchedEntityRoute<I, O> matchedEntityRoute) {
this.matchedEntityRoute = matchedEntityRoute;
return this;
}
public StdEntityRoute<I,O> build() {
checkNotNull(matchedEntityRoute, "you must provide a matchedEntityRoute");
return new StdEntityRoute<I, O>(
name, entityRequestBodyReader == null ? voidBodyReader() : entityRequestBodyReader,
entityResponseWriter,
endpoint, successStatus, logLevel, permissionFactory, registry, queryParameters) {
@Override
protected Optional<O> doRoute(RestxRequest restxRequest, 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<I> voidBodyReader() {
return (EntityRequestBodyReader<I>) VoidContentTypeModule.VoidEntityRequestBodyReader.INSTANCE;
}
}
public static <I,O> Builder<I,O> 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<I> entityRequestBodyReader;
private final EntityResponseWriter<O> entityResponseWriter;
private final RestxLogLevel logLevel;
private final Endpoint endpoint;
private final PermissionFactory permissionFactory;
private final Map<String, EndpointParameterMapperAndDef> cachedQueryParameterMappers;
public StdEntityRoute(String name,
EntityRequestBodyReader<I> entityRequestBodyReader,
EntityResponseWriter<O> 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<I> entityRequestBodyReader,
EntityResponseWriter<O> 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<String, EndpointParameterMapperAndDef> cacheQueryParameterMappers(
EndpointParameterMapperRegistry registry, Endpoint endpoint, ParamDef[] parameters) {
Map<String, EndpointParameterMapperAndDef> 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> T mapQueryObjectFromRequest(Class<T> targetType, String parameterName, RestxRequest request, RestxRequestMatch match, EndpointParameterKind endpointParameterKind){
return mapQueryObjectFromRequest(targetType, parameterName, request, match, endpointParameterKind, null);
}
protected <T> T mapQueryObjectFromRequest(Class<T> targetType, String parameterName, RestxRequest request, RestxRequestMatch match, EndpointParameterKind endpointParameterKind, Supplier<T> 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<I> optionalInput = Optional.fromNullable(input);
lifecycleListener.onEntityInput(this, req, resp, optionalInput);
Optional<O> result = doRoute(req, 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<O> doRoute(RestxRequest restxRequest, 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(); }
}