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

org.hcjf.io.net.http.RestContext Maven / Gradle / Ivy

package org.hcjf.io.net.http;

import com.google.gson.*;
import org.hcjf.encoding.MimeType;
import org.hcjf.errors.HCJFRuntimeException;
import org.hcjf.layers.Layers;
import org.hcjf.layers.crud.CreateLayerInterface;
import org.hcjf.layers.crud.DeleteLayerInterface;
import org.hcjf.layers.crud.ReadLayerInterface;
import org.hcjf.layers.crud.UpdateLayerInterface;
import org.hcjf.layers.query.*;
import org.hcjf.properties.SystemProperties;
import org.hcjf.utils.JsonUtils;
import org.hcjf.utils.Strings;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.*;
import java.util.regex.Pattern;

/**
 * This class provides an abstraction to implements a simple rest end point that
 * join any rest rule with some layer implementation.
 * @author javaito
 */
public class RestContext extends Context {

    private static class Fields {
        private static final String VALUE_FIELD = "value";
        private static final String PARAMS_FIELD = "params";
        private static final String ID_URL_FIELD = "id";
        private static final String REQUEST_CONFIG = "__request_config";
        private static final String DATE_FORMAT_CONFIG = "dateFormat";
        private static class Throwable {
            private static final String MESSAGE = "message";
            private static final String EXCEPTION = "exception";
            private static final String BODY = "body";
            private static final String TAGS = "tags";
        }
    }

    private static final String REGEX_TEMPLATE = "\\/%s(\\/(?[A-Za-z0-9\\-\\_]{0,})){0,}";
    private static final String DEFAULT_QUERY_PARAMETER = "q";

    private final List idRegexList;

    public RestContext(String baseContext) {
        this(baseContext, List.of(SystemProperties.getPattern(SystemProperties.HCJF_UUID_REGEX)));
    }

    public RestContext(String baseContext, List idRegexList) {
        super(String.format(REGEX_TEMPLATE, Strings.trim(baseContext, Strings.SLASH)));
        this.idRegexList = idRegexList;
    }

    /**
     * This method receive the http request and delegate each request for the different method depends of the
     * http method and the request body.
     * @param request All the request information.
     * @return Returns the response object.
     */
    @Override
    public HttpResponse onContext(HttpRequest request) {
        HttpMethod method = request.getMethod();
        JsonParser jsonParser = new JsonParser();
        Gson gson = new GsonBuilder().setDateFormat(SystemProperties.get(SystemProperties.HCJF_DEFAULT_DATE_FORMAT)).create();
        JsonElement jsonElement;
        Collection headers = new ArrayList<>();

        String lastPart = request.getPathParts().get(request.getPathParts().size() -1);
        Object id = null;
        for(Pattern idRegex : idRegexList) {
            if(idRegex.matcher(lastPart).matches()) {
                id = Strings.deductInstance(lastPart);
                break;
            }
        }
        String resourceName = Strings.join(request.getPathParts().stream().skip(1).limit(request.getPathParts().size() -
                (id == null ? 0 : 2)), Strings.CLASS_SEPARATOR);

        if(method.equals(HttpMethod.GET)) {
            if(id == null) {
                if (request.hasParameter(DEFAULT_QUERY_PARAMETER)) {
                    Queryable queryable = Query.compile(request.getParameter(DEFAULT_QUERY_PARAMETER));
                    Collection queryResult = Query.evaluate(queryable);
                    if(queryResult instanceof ResultSet) {
                        ResultSet resultSet = (ResultSet) queryResult;
                        headers.add(new HttpHeader(HttpHeader.X_HCJF_QUERY_TOTAL_TIME, resultSet.getTotalTime().toString()));
                        headers.add(new HttpHeader(HttpHeader.X_HCJF_QUERY_TIME_COMPILING, resultSet.getTimeCompilingQuery().toString()));
                        headers.add(new HttpHeader(HttpHeader.X_HCJF_QUERY_TIME_COLLECTING_DATA, resultSet.getTimeCollectingData().toString()));
                        headers.add(new HttpHeader(HttpHeader.X_HCJF_QUERY_TIME_EVALUATING_CONDITIONS, resultSet.getTimeEvaluatingConditions().toString()));
                        headers.add(new HttpHeader(HttpHeader.X_HCJF_QUERY_AVERAGE_TIME_EVALUATING_CONDITIONS, resultSet.getAverageTimeFormattingDataByRow().toString()));
                        headers.add(new HttpHeader(HttpHeader.X_HCJF_QUERY_TIME_FORMATTING_DATA, resultSet.getTimeFormattingData().toString()));
                        headers.add(new HttpHeader(HttpHeader.X_HCJF_QUERY_AVERAGE_TIME_FORMATTING_DATA, resultSet.getAverageTimeFormattingDataByRow().toString()));
                        headers.add(new HttpHeader(HttpHeader.X_HCJF_QUERY_PRESENT_FIELDS, Strings.join(resultSet.getPresentFields(), Strings.ARGUMENT_SEPARATOR)));
                    }
                    jsonElement = gson.toJsonTree(queryResult);
                } else {
                    ReadLayerInterface readLayerInterface = Layers.get(ReadLayerInterface.class, resourceName);
                    jsonElement = gson.toJsonTree(readLayerInterface.read());
                }
            } else {
                ReadLayerInterface readLayerInterface = Layers.get(ReadLayerInterface.class, resourceName);
                jsonElement = gson.toJsonTree(readLayerInterface.read(id));
            }
        } else if(method.equals(HttpMethod.POST)) {
            RequestModel requestModel = new RequestModel((JsonObject) jsonParser.parse(new String(request.getBody())));
            if(requestModel.getBody() == null) {
                // If the method is post and the body object is null then the request attempt to create a parameterized
                // query instance or a group of queryable instances.
                if(requestModel.getQueryable() != null) {
                    jsonElement = gson.toJsonTree(Query.evaluate(requestModel.getQueryable()));
                } else if(requestModel.getQueryables() != null){
                    JsonObject queriesResult = new JsonObject();
                    for(String key : requestModel.getQueryables().keySet()) {
                        try {
                            queriesResult.add(key, gson.toJsonTree(Query.evaluate(requestModel.getQueryables().get(key))));
                        } catch (Throwable throwable){
                            queriesResult.add(key, createJsonFromThrowable(throwable));
                        }
                    }
                    jsonElement = queriesResult;
                } else {
                    throw new HCJFRuntimeException("Unsupported POST method configuration");
                }
            } else {
                // This method call by default to create layer interface implementation.
                CreateLayerInterface createLayerInterface = Layers.get(CreateLayerInterface.class, resourceName);
                jsonElement = gson.toJsonTree(createLayerInterface.create(requestModel.getBody()));
            }
        } else if(method.equals(HttpMethod.PUT)) {
            // This method call to update layer interface implementation.
            UpdateLayerInterface updateLayerInterface = Layers.get(UpdateLayerInterface.class, resourceName);
            RequestModel requestModel = new RequestModel((JsonObject) jsonParser.parse(new String(request.getBody())));
            if(id != null) {
                requestModel.getBody().put(Fields.ID_URL_FIELD, id);
                jsonElement = gson.toJsonTree(updateLayerInterface.update(requestModel.getBody()));
            } else {
                jsonElement = gson.toJsonTree(updateLayerInterface.update(requestModel.queryable, requestModel.getBody()));
            }
        } else if(method.equals(HttpMethod.DELETE)) {
            // This method call to delete layer interface implementation.
            DeleteLayerInterface deleteLayerInterface = Layers.get(DeleteLayerInterface.class, resourceName);
            if(id != null) {
                jsonElement = gson.toJsonTree(deleteLayerInterface.delete(id));
            } else {
                RequestModel requestModel = new RequestModel((JsonObject) jsonParser.parse(new String(request.getBody())));
                jsonElement = gson.toJsonTree(deleteLayerInterface.delete(requestModel.getQueryable()));
            }
        } else {
            throw new HCJFRuntimeException("Unsupported http method: %s", method.toString());
        }

        HttpResponse response = new HttpResponse();
        response.addHeader(new HttpHeader(HttpHeader.CONTENT_TYPE, MimeType.APPLICATION_JSON.toString()));
        byte[] body = jsonElement.toString().getBytes();
        response.addHeader(new HttpHeader(HttpHeader.CONTENT_LENGTH, Integer.toString(body.length)));
        for(HttpHeader header : headers) {
            response.addHeader(header);
        }
        response.setBody(body);

        return response;
    }

    /**
     * This method generate error response all the times that the request generates an throwable instance.
     * @param request All the request information.
     * @param throwable Throwable object, could be null.
     * @return Returns http response instance.
     */
    @Override
    protected HttpResponse onError(HttpRequest request, Throwable throwable) {
        HttpResponse response = new HttpResponse();
        response.setResponseCode(HttpResponseCode.BAD_REQUEST);
        response.addHeader(new HttpHeader(HttpHeader.CONTENT_TYPE, MimeType.APPLICATION_JSON.toString()));
        byte[] body = createJsonFromThrowable(throwable).toString().getBytes();
        response.addHeader(new HttpHeader(HttpHeader.CONTENT_LENGTH, Integer.toString(body.length)));
        response.setBody(body);

        return response;
    }

    /**
     * This method create a json object from a throwable.
     * @param throwable throwable instance.
     * @return Json object.
     */
    private JsonObject createJsonFromThrowable(Throwable throwable) {
        JsonObject jsonObject = new JsonObject();
        if(throwable.getMessage() != null) {
            jsonObject.addProperty(Fields.Throwable.MESSAGE, throwable.getMessage());
        }
        Map tags = getTags(throwable, new HashMap<>());
        if(!tags.isEmpty()) {
            JsonObject jsonTags = new JsonObject();
            for(String tag : tags.keySet()) {
                jsonTags.addProperty(tag, tags.get(tag));
            }
            jsonObject.add(Fields.Throwable.TAGS, jsonTags);
        }
        jsonObject.addProperty(Fields.Throwable.EXCEPTION, throwable.getClass().getName());
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        PrintStream printStream = new PrintStream(byteArrayOutputStream);
        throwable.printStackTrace(printStream);
        jsonObject.addProperty(Fields.Throwable.BODY, byteArrayOutputStream.toString());
        return jsonObject;
    }

    /**
     * Find all the tags into the throwable instances and his causes.
     * @param throwable Throwable instance.
     * @param tag Map to store the tags founded.
     * @return Return the same map sending as parameter.
     */
    private Map getTags(Throwable throwable, Map tag) {
        if(throwable.getMessage() != null) {
            tag.putAll(Strings.getTagsFromMessage(throwable.getMessage()));
        }
        if(throwable.getCause() != null) {
            tag = getTags(throwable.getCause(), tag);
        }
        return tag;
    }

    /**
     * This inner class contains the necessary methods to parse the request body in order to call
     * the specific layer implementation.
     */
    private static class RequestModel {

        private Map body;
        private Queryable queryable;
        private Map queryables;
        private Map requestConfig;

        public RequestModel(JsonObject jsonObject) {
            if(!jsonObject.has(SystemProperties.get(SystemProperties.Net.Rest.BODY_FIELD)) &&
                    !jsonObject.has(SystemProperties.get(SystemProperties.Net.Rest.QUERY_FIELD)) &&
                    !jsonObject.has(SystemProperties.get(SystemProperties.Net.Rest.QUERIES_FIELD))) {
                body = JsonUtils.createBody(jsonObject);
            } else {
                if (jsonObject.has(SystemProperties.get(SystemProperties.Net.Rest.BODY_FIELD))) {
                    body = JsonUtils.createBody(jsonObject.getAsJsonObject(SystemProperties.get(SystemProperties.Net.Rest.BODY_FIELD)));
                }

                if (jsonObject.has(SystemProperties.get(SystemProperties.Net.Rest.QUERY_FIELD))) {
                    queryable = createQuery(jsonObject.get(SystemProperties.get(SystemProperties.Net.Rest.QUERY_FIELD)));
                }

                if (jsonObject.has(SystemProperties.get(SystemProperties.Net.Rest.QUERIES_FIELD))) {
                    queryables = new HashMap<>();
                    JsonObject queryablesObject = jsonObject.getAsJsonObject(SystemProperties.get(SystemProperties.Net.Rest.QUERIES_FIELD));
                    for (String key : queryablesObject.keySet()) {
                        queryables.put(key, createQuery(queryablesObject.get(key)));
                    }
                }
            }
        }

        /**
         * Creates the queryable instance from a json element.
         * @param element Json element instance.
         * @return Returns the queriable instance.
         */
        private Queryable createQuery(JsonElement element) {
            Queryable result;
            if(element instanceof JsonObject) {
                JsonObject queryJsonObject = element.getAsJsonObject();
                ParameterizedQuery parameterizedQuery = Query.compile(queryJsonObject.get(Fields.VALUE_FIELD).getAsString()).getParameterizedQuery();
                if(queryJsonObject.has(Fields.PARAMS_FIELD)) {
                    for (Object parameter : JsonUtils.createList(queryJsonObject.get(Fields.PARAMS_FIELD).getAsJsonArray())) {
                        parameterizedQuery.add(parameter);
                    }
                }
                result = parameterizedQuery;
            } else {
                result = Query.compile(element.getAsString());
            }
            return result;
        }

        /**
         * Returns the body of the request.
         * @return Body of the request.
         */
        public Map getBody() {
            return body;
        }

        /**
         * Returns the queryable instance of the request.
         * @return Queriable instace.
         */
        public Queryable getQueryable() {
            return queryable;
        }

        /**
         * Returns a map of the queryable instances of the request.
         * @return Queryable instances of the request.
         */
        public Map getQueryables() {
            return queryables;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy