package org.rakam.server.http;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Throwables;
import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.util.List;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static java.lang.String.format;
import static org.rakam.server.http.HttpServer.*;
public class JsonParametrizedRequestHandler implements HttpRequestHandler {
private final ObjectMapper mapper;
private final List<IRequestParameter> bodyParams;
private final MethodHandle methodHandle;
private final HttpService service;
private final boolean isAsync;
private final List<RequestPreprocessor> jsonPreprocessors;
private final boolean bodyExtractionRequired;
static final String bodyError;
static {
try {
bodyError = DEFAULT_MAPPER.writeValueAsString(errorMessage("Body must be an json object.", BAD_REQUEST));
} catch (JsonProcessingException e) {
throw Throwables.propagate(e);
}
}
private final List<ResponsePostProcessor> postProcessors;
private final HttpServer httpServer;
private final boolean isJson;
public JsonParametrizedRequestHandler(HttpServer httpServer, ObjectMapper mapper,
List<IRequestParameter> bodyParams,
MethodHandle methodHandle,
List<ResponsePostProcessor> postProcessors,
HttpService service,
List<RequestPreprocessor> jsonPreprocessors,
boolean isAsync, boolean isJson) {
this.mapper = mapper;
this.httpServer = httpServer;
this.bodyParams = bodyParams;
this.methodHandle = methodHandle;
this.service = service;
this.postProcessors = postProcessors;
this.isAsync = isAsync;
this.jsonPreprocessors = jsonPreprocessors;
this.isJson = isJson;
this.bodyExtractionRequired = bodyParams.stream().anyMatch(a -> a instanceof IRequestParameter.BodyParameter || a instanceof IRequestParameter.FullBodyParameter);
}
@Override
public void handle(RakamHttpRequest request) {
if(bodyExtractionRequired) {
request.bodyHandler(body -> {
ObjectNode node;
try {
// TODO: use custom deserialization to avoid the overhead of garbage generated by Jackson
node = (ObjectNode) mapper.readTree(body);
} catch (ClassCastException e) {
request.response(bodyError, BAD_REQUEST).end();
return;
} catch (UnrecognizedPropertyException e) {
returnError(request, "Unrecognized field: " + e.getPropertyName(), BAD_REQUEST);
return;
} catch (InvalidFormatException e) {
returnError(request, format("Field value couldn't validated: %s ", e.getOriginalMessage()), BAD_REQUEST);
return;
} catch (JsonMappingException e) {
returnError(request, e.getCause() != null ? e.getCause().getMessage() : e.getMessage(), BAD_REQUEST);
return;
} catch (JsonParseException e) {
returnError(request, format("Couldn't parse json: %s ", e.getOriginalMessage()), BAD_REQUEST);
return;
} catch (IOException e) {
returnError(request, format("Error while mapping json: ", e.getMessage()), BAD_REQUEST);
return;
}
handleInternal(request, node);
});
} else {
handleInternal(request, null);
}
}
private void handleInternal(RakamHttpRequest request, ObjectNode node) {
try {
if(!jsonPreprocessors.isEmpty()) {
for (RequestPreprocessor preprocessor : jsonPreprocessors) {
preprocessor.handle(request, node);
}
}
} catch (Throwable e) {
httpServer.requestError(e, request, postProcessors);
return;
}
Object[] values = new Object[bodyParams.size() + 1];
values[0] = service;
for (int i = 0; i < bodyParams.size(); i++) {
IRequestParameter param = bodyParams.get(i);
Object value;
try {
value = param.extract(node, request);
} catch (Exception e) {
httpServer.requestError(e, request, postProcessors);
return;
}
values[i + 1] = value;
}
Object invoke;
try {
invoke = methodHandle.invokeWithArguments(values);
} catch (Throwable e) {
httpServer.requestError(e, request, postProcessors);
return;
}
if(isJson) {
httpServer.handleRequest(mapper, isAsync, invoke, request, postProcessors);
}
}
}