Search in sources :

Example 1 with Stream

use of org.folio.rest.annotations.Stream 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);
    }
}
Also used : ValidationHelper(org.folio.rest.tools.utils.ValidationHelper) UnrecognizedPropertyException(com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException) Date(java.util.Date) HttpServer(io.vertx.core.http.HttpServer) MultiMap(io.vertx.core.MultiMap) MessagingException(javax.mail.MessagingException) Router(io.vertx.ext.web.Router) RoutingContext(io.vertx.ext.web.RoutingContext) BodyHandler(io.vertx.ext.web.handler.BodyHandler) StringUtils(org.apache.commons.lang3.StringUtils) VertxUtils(org.folio.rest.tools.utils.VertxUtils) Context(io.vertx.core.Context) PomReader(org.folio.rest.tools.PomReader) BigDecimal(java.math.BigDecimal) MetricsService(io.vertx.ext.dropwizard.MetricsService) Matcher(java.util.regex.Matcher) EventBus(io.vertx.core.eventbus.EventBus) ByteArrayInputStream(java.io.ByteArrayInputStream) LogUtil(org.folio.rest.tools.utils.LogUtil) ResponseImpl(org.folio.rest.tools.utils.ResponseImpl) Map(java.util.Map) AnnotationGrabber(org.folio.rest.tools.AnnotationGrabber) RTFConsts(org.folio.rest.tools.RTFConsts) JsonObject(io.vertx.core.json.JsonObject) Metadata(org.folio.rest.jaxrs.model.Metadata) KieSession(org.kie.api.runtime.KieSession) Logger(io.vertx.core.logging.Logger) Method(java.lang.reflect.Method) ConstraintViolation(javax.validation.ConstraintViolation) Errors(org.folio.rest.jaxrs.model.Errors) JwtUtils(org.folio.rest.tools.utils.JwtUtils) MimeMultipart(javax.mail.internet.MimeMultipart) BinaryOutStream(org.folio.rest.tools.utils.BinaryOutStream) JsonUtils(org.folio.rest.tools.utils.JsonUtils) Set(java.util.Set) UUID(java.util.UUID) Future(io.vertx.core.Future) PostgresClient(org.folio.rest.persist.PostgresClient) Messages(org.folio.rest.tools.messages.Messages) OutStream(org.folio.rest.tools.utils.OutStream) List(java.util.List) PojoEventBusCodec(org.folio.rest.tools.codecs.PojoEventBusCodec) InterfaceToImpl(org.folio.rest.tools.utils.InterfaceToImpl) Response(javax.ws.rs.core.Response) Buffer(io.vertx.core.buffer.Buffer) HttpServerResponse(io.vertx.core.http.HttpServerResponse) AbstractVerticle(io.vertx.core.AbstractVerticle) Annotation(java.lang.annotation.Annotation) Entry(java.util.Map.Entry) ByteStreams(com.google.common.io.ByteStreams) Parameter(org.folio.rest.jaxrs.model.Parameter) Pattern(java.util.regex.Pattern) Joiner(com.google.common.base.Joiner) HttpServerRequest(io.vertx.core.http.HttpServerRequest) MessageConsts(org.folio.rest.tools.messages.MessageConsts) MimeBodyPart(javax.mail.internet.MimeBodyPart) HashMap(java.util.HashMap) ValidatorFactory(javax.validation.ValidatorFactory) CaseInsensitiveMap(org.apache.commons.collections4.map.CaseInsensitiveMap) LoggerFactory(io.vertx.core.logging.LoggerFactory) ArrayList(java.util.ArrayList) Validation(javax.validation.Validation) AsyncResult(io.vertx.core.AsyncResult) MDC(org.apache.log4j.MDC) Properties(java.util.Properties) HttpServerFileUpload(io.vertx.core.http.HttpServerFileUpload) Iterator(java.util.Iterator) StaticHandler(io.vertx.ext.web.handler.StaticHandler) ObjectMapper(com.fasterxml.jackson.databind.ObjectMapper) Vertx(io.vertx.core.Vertx) IOException(java.io.IOException) FactHandle(org.kie.api.runtime.rule.FactHandle) Consumer(java.util.function.Consumer) Rules(org.folio.rulez.Rules) Stream(org.folio.rest.annotations.Stream) Error(org.folio.rest.jaxrs.model.Error) HttpClientMock2(org.folio.rest.tools.client.test.HttpClientMock2) JsonArray(io.vertx.core.json.JsonArray) InternetHeaders(javax.mail.internet.InternetHeaders) AsyncResponseResult(org.folio.rest.tools.utils.AsyncResponseResult) StringReader(java.io.StringReader) HttpMethod(io.vertx.core.http.HttpMethod) ObjectMapperTool(org.folio.rest.tools.utils.ObjectMapperTool) TenantAttributes(org.folio.rest.jaxrs.model.TenantAttributes) ClientGenerator(org.folio.rest.tools.ClientGenerator) HttpServerOptions(io.vertx.core.http.HttpServerOptions) Handler(io.vertx.core.Handler) InputStream(java.io.InputStream) Matcher(java.util.regex.Matcher) HttpServerRequest(io.vertx.core.http.HttpServerRequest) JsonObject(io.vertx.core.json.JsonObject) Method(java.lang.reflect.Method) HttpMethod(io.vertx.core.http.HttpMethod) UnrecognizedPropertyException(com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException) MessagingException(javax.mail.MessagingException) IOException(java.io.IOException) CaseInsensitiveMap(org.apache.commons.collections4.map.CaseInsensitiveMap) JsonArray(io.vertx.core.json.JsonArray) Entry(java.util.Map.Entry) JsonObject(io.vertx.core.json.JsonObject)

Aggregations

ObjectMapper (com.fasterxml.jackson.databind.ObjectMapper)1 UnrecognizedPropertyException (com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException)1 Joiner (com.google.common.base.Joiner)1 ByteStreams (com.google.common.io.ByteStreams)1 AbstractVerticle (io.vertx.core.AbstractVerticle)1 AsyncResult (io.vertx.core.AsyncResult)1 Context (io.vertx.core.Context)1 Future (io.vertx.core.Future)1 Handler (io.vertx.core.Handler)1 MultiMap (io.vertx.core.MultiMap)1 Vertx (io.vertx.core.Vertx)1 Buffer (io.vertx.core.buffer.Buffer)1 EventBus (io.vertx.core.eventbus.EventBus)1 HttpMethod (io.vertx.core.http.HttpMethod)1 HttpServer (io.vertx.core.http.HttpServer)1 HttpServerFileUpload (io.vertx.core.http.HttpServerFileUpload)1 HttpServerOptions (io.vertx.core.http.HttpServerOptions)1 HttpServerRequest (io.vertx.core.http.HttpServerRequest)1 HttpServerResponse (io.vertx.core.http.HttpServerResponse)1 JsonArray (io.vertx.core.json.JsonArray)1