use of org.folio.rest.jaxrs.model.Error in project raml-module-builder by folio-org.
the class RestVerticle method route.
/**
* Handler for all url calls other then documentation.
* @param mappedURLs maps paths found in raml to the generated functions to route to when the paths are requested
* @param urlPaths set of exposed urls as declared in the raml
* @param regex2Pattern create a map of regular expression to url path
* @param rc RoutingContext of this URL
*/
private void route(MappedClasses mappedURLs, Set<String> urlPaths, Map<String, Pattern> regex2Pattern, RoutingContext rc) {
long start = System.nanoTime();
try {
// list of regex urls created from urls declared in the raml
Iterator<String> iter = urlPaths.iterator();
boolean validPath = false;
boolean[] validRequest = { true };
// the ramls and we return an error - this has positive security implications as well
while (iter.hasNext()) {
String regexURL = iter.next();
// try to match the requested url to each regex pattern created from the urls in the raml
Matcher m = regex2Pattern.get(regexURL).matcher(rc.request().path());
if (m.find()) {
validPath = true;
// get the function that should be invoked for the requested
// path + requested http_method pair
JsonObject ret = mappedURLs.getMethodbyPath(regexURL, rc.request().method().toString());
// if a valid path was requested but no function was found
if (ret == null) {
// assume a cors request
if (rc.request().method() == HttpMethod.OPTIONS) {
rc.response().end();
return;
}
// the url exists but the http method requested does not match a function
// meaning url+http method != a function
endRequestWithError(rc, 400, true, messages.getMessage("en", MessageConsts.HTTPMethodNotSupported), validRequest);
return;
}
Class<?> aClass;
try {
if (validRequest[0]) {
int groups = m.groupCount();
// pathParams are the place holders in the raml query string
// for example /admin/{admin_id}/yyy/{yyy_id} - the content in between the {} are path params
// they are replaced with actual values and are passed to the function which the url is mapped to
String[] pathParams = new String[groups];
for (int i = 0; i < groups; i++) {
pathParams[i] = m.group(i + 1);
}
// create okapi headers map and inject into function
Map<String, String> okapiHeaders = new CaseInsensitiveMap<>();
String[] tenantId = new String[] { null };
getOkapiHeaders(rc, okapiHeaders, tenantId);
String reqId = okapiHeaders.get(OKAPI_REQUESTID_HEADER);
if (reqId != null) {
MDC.put("reqId", "reqId=" + reqId);
}
if (tenantId[0] == null && !rc.request().path().startsWith("/admin")) {
// if tenant id is not passed in and this is not an /admin request, return error
endRequestWithError(rc, 400, true, messages.getMessage("en", MessageConsts.UnableToProcessRequest) + " Tenant must be set", validRequest);
}
if (validRequest[0]) {
// get interface mapped to this url
String iClazz = ret.getString(AnnotationGrabber.CLASS_NAME);
// convert from interface to an actual class implementing it, which appears in the impl package
aClass = InterfaceToImpl.convert2Impl(RTFConsts.PACKAGE_OF_IMPLEMENTATIONS, iClazz, false).get(0);
Object o = null;
// passing the vertx and context objects in to it.
try {
o = aClass.getConstructor(Vertx.class, String.class).newInstance(vertx, tenantId[0]);
} catch (Exception e) {
// if no such constructor was implemented call the
// default no param constructor to create the object to be used to call functions on
o = aClass.newInstance();
}
final Object instance = o;
// function to invoke for the requested url
String function = ret.getString(AnnotationGrabber.FUNCTION_NAME);
// parameters for the function to invoke
JsonObject params = ret.getJsonObject(AnnotationGrabber.METHOD_PARAMS);
// all methods in the class whose function is mapped to the called url
// needed so that we can get a reference to the Method object and call it via reflection
Method[] methods = aClass.getMethods();
// what the api will return as output (Accept)
JsonArray produces = ret.getJsonArray(AnnotationGrabber.PRODUCES);
// what the api expects to get (content-type)
JsonArray consumes = ret.getJsonArray(AnnotationGrabber.CONSUMES);
HttpServerRequest request = rc.request();
// check that the accept and content-types passed in the header of the request
// are as described in the raml
checkAcceptContentType(produces, consumes, rc, validRequest);
// create the array and then populate it by parsing the url parameters which are needed to invoke the function mapped
// to the requested URL - array will be populated by parseParams() function
Iterator<Map.Entry<String, Object>> paramList = params.iterator();
Object[] paramArray = new Object[params.size()];
parseParams(rc, paramList, validRequest, consumes, paramArray, pathParams, okapiHeaders);
// Get method in class to be run for this requested API endpoint
Method[] method2Run = new Method[] { null };
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(function)) {
method2Run[0] = methods[i];
break;
}
}
// is function annotated to receive data in chunks as they come in.
// Note that the function controls the logic to this if this is the case
boolean streamData = isStreamed(method2Run[0].getAnnotations());
// check if we are dealing with a file upload , currently only multipart/form-data and application/octet
// in the raml definition for such a function
final boolean[] isContentUpload = new boolean[] { false };
final int[] uploadParamPosition = new int[] { -1 };
params.forEach(param -> {
if (((JsonObject) param.getValue()).getString("type").equals(FILE_UPLOAD_PARAM)) {
isContentUpload[0] = true;
uploadParamPosition[0] = ((JsonObject) param.getValue()).getInteger("order");
} else if (((JsonObject) param.getValue()).getString("type").equals("java.io.InputStream")) {
// application/octet-stream passed - this is handled in a stream like manner
// and the corresponding function called must annotate with a @Stream - and be able
// to handle the function being called repeatedly on parts of the data
uploadParamPosition[0] = ((JsonObject) param.getValue()).getInteger("order");
isContentUpload[0] = true;
}
});
// pass to implementing function just like any other call
if (isContentUpload[0] && !streamData) {
// looks something like -> multipart/form-data; boundary=----WebKitFormBoundaryzeZR8KqAYJyI2jPL
if (consumes != null && consumes.contains(SUPPORTED_CONTENT_TYPE_FORMDATA)) {
// multipart
handleMultipartUpload(rc, request, uploadParamPosition, paramArray, validRequest);
request.endHandler(a -> {
if (validRequest[0]) {
// if request is valid - invoke it
try {
invoke(method2Run[0], paramArray, instance, rc, tenantId, okapiHeaders, new StreamStatus(), v -> {
LogUtil.formatLogMessage(className, "start", " invoking " + function);
sendResponse(rc, v, start, tenantId[0]);
});
} catch (Exception e1) {
log.error(e1.getMessage(), e1);
rc.response().end();
}
}
});
} else {
// assume input stream
handleInputStreamUpload(method2Run[0], rc, request, instance, tenantId, okapiHeaders, uploadParamPosition, paramArray, validRequest, start);
}
} else if (streamData) {
handleStream(method2Run[0], rc, request, instance, tenantId, okapiHeaders, uploadParamPosition, paramArray, validRequest, start);
} else {
if (validRequest[0]) {
// if request is valid - invoke it
try {
invoke(method2Run[0], paramArray, instance, rc, tenantId, okapiHeaders, new StreamStatus(), v -> {
LogUtil.formatLogMessage(className, "start", " invoking " + function);
sendResponse(rc, v, start, tenantId[0]);
});
} catch (Exception e1) {
log.error(e1.getMessage(), e1);
rc.response().end();
}
}
}
} else {
endRequestWithError(rc, 400, true, messages.getMessage("en", MessageConsts.UnableToProcessRequest), validRequest);
return;
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
endRequestWithError(rc, 400, true, messages.getMessage("en", MessageConsts.UnableToProcessRequest) + e.getMessage(), validRequest);
return;
}
}
}
if (!validPath) {
// invalid path
endRequestWithError(rc, 400, true, messages.getMessage("en", MessageConsts.InvalidURLPath, rc.request().path()), validRequest);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
use of org.folio.rest.jaxrs.model.Error in project raml-module-builder by folio-org.
the class ValidationHelper method createValidationErrorMessage.
public static Errors createValidationErrorMessage(String field, String value, String message) {
Errors e = new Errors();
Error error = new Error();
Parameter p = new Parameter();
p.setKey(field);
p.setValue(value);
error.getParameters().add(p);
error.setMessage(message);
error.setCode("-1");
error.setType(RTFConsts.VALIDATION_FIELD_ERROR);
List<Error> l = new ArrayList<>();
l.add(error);
e.setErrors(l);
return e;
}
use of org.folio.rest.jaxrs.model.Error in project raml-module-builder by folio-org.
the class RestVerticle method parseParams.
private void parseParams(RoutingContext rc, Iterator<Map.Entry<String, Object>> paramList, boolean[] validRequest, JsonArray consumes, Object[] paramArray, String[] pathParams, Map<String, String> okapiHeaders) {
HttpServerRequest request = rc.request();
MultiMap queryParams = request.params();
int[] pathParamsIndex = new int[] { pathParams.length };
paramList.forEachRemaining(entry -> {
if (validRequest[0]) {
String valueName = ((JsonObject) entry.getValue()).getString("value");
String valueType = ((JsonObject) entry.getValue()).getString("type");
String paramType = ((JsonObject) entry.getValue()).getString("param_type");
int order = ((JsonObject) entry.getValue()).getInteger("order");
Object defaultVal = ((JsonObject) entry.getValue()).getValue("default_value");
boolean emptyNumeircParam = false;
// to their async upload - so explicitly skip them
if (AnnotationGrabber.NON_ANNOTATED_PARAM.equals(paramType) && !FILE_UPLOAD_PARAM.equals(valueType)) {
try {
// this will also validate the json against the pojo created from the schema
Class<?> entityClazz = Class.forName(valueType);
if (!valueType.equals("io.vertx.core.Handler") && !valueType.equals("io.vertx.core.Context") && !valueType.equals("java.util.Map") && !valueType.equals("java.io.InputStream") && !valueType.equals("io.vertx.ext.web.RoutingContext")) {
// we have special handling for the Result Handler and context, it is also assumed that
// an inputsteam parameter occurs when application/octet is declared in the raml
// in which case the content will be streamed to he function
String bodyContent = rc.getBodyAsString();
log.debug(rc.request().path() + " -------- bodyContent -------- " + bodyContent);
if (bodyContent != null) {
if ("java.io.Reader".equals(valueType)) {
paramArray[order] = new StringReader(bodyContent);
} else if (bodyContent.length() > 0) {
try {
paramArray[order] = MAPPER.readValue(bodyContent, entityClazz);
} catch (UnrecognizedPropertyException e) {
log.error(e.getMessage(), e);
endRequestWithError(rc, RTFConsts.VALIDATION_ERROR_HTTP_CODE, true, JsonUtils.entity2String(ValidationHelper.createValidationErrorMessage("", "", e.getMessage())), validRequest);
return;
}
}
}
Errors errorResp = new Errors();
if (!allowEmptyObject(entityClazz, bodyContent)) {
// right now - because no way in raml to make body optional - do not validate
// TenantAttributes object as it may be empty
// is this request only to validate a field value and not an actual
// request for additional processing
List<String> field2validate = request.params().getAll("validate_field");
Object[] resp = isValidRequest(rc, paramArray[order], errorResp, validRequest, field2validate, entityClazz);
boolean isValid = (boolean) resp[0];
paramArray[order] = resp[1];
if (!isValid) {
endRequestWithError(rc, RTFConsts.VALIDATION_ERROR_HTTP_CODE, true, JsonUtils.entity2String(errorResp), validRequest);
return;
} else if (isValid && !field2validate.isEmpty()) {
// valid request for the field to validate request made
AsyncResponseResult arr = new AsyncResponseResult();
ResponseImpl ri = new ResponseImpl();
ri.setStatus(200);
arr.setResult(ri);
// right now this is the only flag available to stop
// any additional respones for this request. to fix
validRequest[0] = false;
sendResponse(rc, arr, 0, null);
return;
}
}
// complex rules validation here (drools) - after simpler validation rules pass -
Error error = new Error();
FactHandle handle = null;
FactHandle handleError = null;
try {
// if no /rules exist then drools session will be null
if (droolsSession != null && paramArray[order] != null && validRequest[0]) {
// add object to validate to session
handle = droolsSession.insert(paramArray[order]);
handleError = droolsSession.insert(error);
// run all rules in session on object
droolsSession.fireAllRules();
}
} catch (Exception e) {
error.setCode("-1");
error.setType(RTFConsts.VALIDATION_FIELD_ERROR);
errorResp.getErrors().add(error);
endRequestWithError(rc, RTFConsts.VALIDATION_ERROR_HTTP_CODE, true, JsonUtils.entity2String(errorResp), validRequest);
} finally {
// remove the object from the session
if (handle != null) {
droolsSession.delete(handle);
droolsSession.delete(handleError);
}
}
populateMetaData(paramArray[order], okapiHeaders, rc.request().path());
}
} catch (Exception e) {
log.error(e);
endRequestWithError(rc, 400, true, "Json content error " + e.getMessage(), validRequest);
}
} else if (AnnotationGrabber.HEADER_PARAM.equals(paramType)) {
// handle header params - read the header field from the
// header (valueName) and get its value
String value = request.getHeader(valueName);
// set the value passed from the header as a param to the function
paramArray[order] = value;
} else if (AnnotationGrabber.PATH_PARAM.equals(paramType)) {
// these are placeholder values in the path - for example
// /patrons/{patronid} - this would be the patronid value
paramArray[order] = pathParams[pathParamsIndex[0] - 1];
pathParamsIndex[0] = pathParamsIndex[0] - 1;
} else if (AnnotationGrabber.QUERY_PARAM.equals(paramType)) {
String param = queryParams.get(valueName);
// support enum, numbers or strings as query parameters
try {
if (valueType.contains("String")) {
// regular string param in query string - just push value
if (param == null && defaultVal != null) {
// no value passed - check if there is a default value
paramArray[order] = defaultVal;
} else {
paramArray[order] = param;
}
} else if (valueType.contains("int") || valueType.contains("Integer")) {
// cant pass null to an int type
if (param == null) {
if (defaultVal != null) {
paramArray[order] = Integer.valueOf((String) defaultVal);
} else {
paramArray[order] = 0;
}
} else if ("".equals(param)) {
emptyNumeircParam = true;
} else {
paramArray[order] = Integer.valueOf(param);
}
} else if (valueType.contains("boolean") || valueType.contains("Boolean")) {
if (param == null) {
if (defaultVal != null) {
paramArray[order] = Boolean.valueOf((String) defaultVal);
}
} else {
paramArray[order] = Boolean.valueOf(param);
}
} else if (valueType.contains("List")) {
List<String> vals = queryParams.getAll(valueName);
if (vals == null) {
paramArray[order] = null;
} else {
paramArray[order] = vals;
}
} else if (valueType.contains("BigDecimal")) {
if (param == null) {
if (defaultVal != null) {
paramArray[order] = new BigDecimal((String) defaultVal);
} else {
paramArray[order] = null;
}
} else if ("".equals(param)) {
emptyNumeircParam = true;
} else {
// big decimal can contain ","
paramArray[order] = new BigDecimal(param.replaceAll(",", ""));
}
} else {
// enum object type
try {
String enumClazz = replaceLast(valueType, ".", "$");
Class<?> enumClazz1 = Class.forName(enumClazz);
if (enumClazz1.isEnum()) {
Object defaultEnum = null;
Object[] vals = enumClazz1.getEnumConstants();
for (int i = 0; i < vals.length; i++) {
if (vals[i].toString().equals(defaultVal)) {
defaultEnum = vals[i];
}
// in case no value was passed in the request
if (param == null && defaultEnum != null) {
paramArray[order] = defaultEnum;
break;
} else // make sure enum value is valid by converting the string to an enum
if (vals[i].toString().equals(param)) {
paramArray[order] = vals[i];
break;
}
if (i == vals.length - 1) {
// if enum passed is not valid, replace with default value
paramArray[order] = defaultEnum;
}
}
}
} catch (Exception ee) {
log.error(ee.getMessage(), ee);
endRequestWithError(rc, 400, true, ee.getMessage(), validRequest);
}
}
if (emptyNumeircParam) {
endRequestWithError(rc, 400, true, valueName + " does not have a default value in the RAML and has been passed empty", validRequest);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
endRequestWithError(rc, 400, true, e.getMessage(), validRequest);
}
}
}
});
}
use of org.folio.rest.jaxrs.model.Error in project raml-module-builder by folio-org.
the class RestVerticle method isValidRequest.
/**
* return whether the request is valid [0] and a cleaned up version of the object [1]
* cleaned up meaning,
* @param errorResp
* @param paramArray
* @param rc
* @param validRequest
* @param entityClazz
*/
private Object[] isValidRequest(RoutingContext rc, Object content, Errors errorResp, boolean[] validRequest, List<String> singleField, Class<?> entityClazz) {
Set<? extends ConstraintViolation<?>> validationErrors = validationFactory.getValidator().validate(content);
boolean ret = true;
if (validationErrors.size() > 0) {
for (ConstraintViolation<?> cv : validationErrors) {
if ("must be null".equals(cv.getMessage())) {
/**
* read only fields are marked with a 'must be null' annotation @null
* so the client should not pass them in, if they were passed in, remove them here
* so that they do not reach the implementing function
*/
try {
if (!(content instanceof JsonObject)) {
content = JsonObject.mapFrom(content);
}
((JsonObject) content).remove(cv.getPropertyPath().toString());
continue;
} catch (Exception e) {
log.warn("Failed to remove " + cv.getPropertyPath().toString() + " field from body when calling " + rc.request().absoluteURI(), e);
}
}
Error error = new Error();
Parameter p = new Parameter();
String field = cv.getPropertyPath().toString();
p.setKey(field);
Object val = cv.getInvalidValue();
if (val == null) {
p.setValue("null");
} else {
p.setValue(val.toString());
}
error.getParameters().add(p);
error.setMessage(cv.getMessage());
error.setCode("-1");
error.setType(RTFConsts.VALIDATION_FIELD_ERROR);
// or there are validation errors and this is not a per field validation request
if ((singleField != null && singleField.contains(field)) || singleField.isEmpty()) {
errorResp.getErrors().add(error);
ret = false;
}
// sb.append("\n" + cv.getPropertyPath() + " " + cv.getMessage() + ",");
}
if (content instanceof JsonObject) {
// we have sanitized the passed in object by removing read-only fields
try {
content = MAPPER.readValue(((JsonObject) content).encode(), entityClazz);
} catch (IOException e) {
log.error("Failed to serialize body content after removing read-only fields when calling " + rc.request().absoluteURI(), e);
}
}
}
return new Object[] { Boolean.valueOf(ret), content };
}
Aggregations