com.balajeetm.mystique.util.gson.lever.JsonQuery.java Source code

Java tutorial

Introduction

Here is the source code for com.balajeetm.mystique.util.gson.lever.JsonQuery.java

Source

/*
 * Copyright (c) Balajee TM 2016.
 * All rights reserved.
 * License -  @see <a href="http://www.apache.org/licenses/LICENSE-2.0"></a>
 */

/*
 * Created on 12 Oct, 2016 by balajeetm
 * http://www.balajeetm.com
 */
package com.balajeetm.mystique.util.gson.lever;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;

import org.apache.commons.lang3.StringUtils;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;

import reactor.core.Exceptions;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

/**
 * The Class JsonQuery.
 *
 * @author balajeetm
 */
public class JsonQuery {

    /** The json lever. */
    private JsonLever jsonLever;

    /** The query types. */
    private Map<String, BiFunction<JsonElement, JsonObject, Boolean>> queryTypes;

    /**
     * Gets the single instance of JsonQuery.
     *
     * @return single instance of JsonQuery
     */
    public static JsonQuery getInstance() {
        return Creator.INSTANCE;
    }

    // Efficient Thread safe Lazy Initialization
    // works only if the singleton constructor is non parameterized
    /** The Class Creator. */
    private static class Creator {

        /** The instance. */
        private static JsonQuery INSTANCE = new JsonQuery();
    }

    /** Instantiates a new json query. */
    private JsonQuery() {
        jsonLever = JsonLever.getInstance();
        queryTypes = new HashMap<>();
        queryTypes.put("=", this::equalsFilter);
        queryTypes.put("!=", this::notEqualsFilter);
        queryTypes.put("in", this::inFilter);
        queryTypes.put("notin", this::notinFilter);
    }

    /**
     * Query.
     *
     * @param query the query
     * @return the json element
     */
    public JsonElement query(JsonObject query) {

        JsonElement result = null;
        try {
            Mono<JsonArray> reduce = queryAsync(query).reduce(new JsonArray(), (resultarray, json) -> {
                resultarray.add(json);
                return resultarray;
            });
            result = reduce.block();
        } catch (RuntimeException e) {
            result = new JsonPrimitive(e.getMessage());
        }

        return result;
    }

    public Flux<JsonElement> queryAsync(JsonObject query) {
        return Flux.just(query).map(q -> validate(q)).flatMap(s -> {
            Flux<JsonElement> obs = null;
            if ("valid".equals(s)) {
                JsonElement from = query.get("from");
                obs = jsonLever.isArray(from) ? queryAsync(from.getAsJsonArray(), query)
                        : queryAsync(from.getAsJsonObject(), query);
            } else {
                Exceptions.propagate(new Throwable(s));
            }
            return obs;
        });
    }

    /**
     * Query async.
     *
     * @param array the array
     * @param query the query
     * @return the observable
     */
    private Flux<JsonElement> queryAsync(JsonArray array, JsonObject query) {
        return Flux.fromIterable(array).publishOn(Schedulers.elastic())
                .filter(json -> filter(json, query.get("where"))).compose(obs -> {
                    Long limit = jsonLever.asLong(query.get("limit"), 0L);
                    return limit > 0 ? obs.take(limit.intValue()) : obs;
                }).publishOn(Schedulers.elastic())
                .compose(obs -> StringUtils.equals(jsonLever.getString(query, "select"), "count")
                        ? obs.count().map(count -> new JsonPrimitive(count))
                        : obs.map(json -> select(json, query.get("select"))));
    }

    /**
     * Query async.
     *
     * @param obj the obj
     * @param query the query
     * @return the observable
     */
    private Flux<JsonElement> queryAsync(JsonElement obj, JsonObject query) {
        return Flux.just(obj).publishOn(Schedulers.elastic()).filter(json -> filter(json, query.get("where")))
                .compose(obs -> {
                    Long limit = jsonLever.asLong(query.get("limit"), 0L);
                    return limit > 0 ? obs.take(limit.intValue()) : obs;
                }).publishOn(Schedulers.elastic())
                .compose(obs -> StringUtils.equals(jsonLever.getString(query, "select"), "count")
                        ? obs.count().map(count -> new JsonPrimitive(count))
                        : obs.map(json -> select(json, query.get("select"))));
    }

    /**
     * Select.
     *
     * @param json the json
     * @param selectElement the select element
     * @return the json element
     */
    private JsonElement select(JsonElement json, JsonElement selectElement) {
        JsonElement result = null;
        String selectStr = jsonLever.asString(selectElement);
        if (null != selectStr) {
            if (equalsAny(selectStr, "*"))
                result = json;
        } else {
            if (jsonLever.isArray(selectElement)) {
                result = new JsonArray();
                for (JsonElement ele : jsonLever.asJsonArray(selectElement)) {
                    jsonLever.asJsonArray(result).add(jsonLever.get(json, ele));
                }
                if (jsonLever.asJsonArray(result).size() == 1) {
                    result = jsonLever.getFirst(jsonLever.asJsonArray(result));
                }
            }
        }
        return result;
    }

    /**
     * Filter.
     *
     * @param json the json
     * @param whereElement the where element
     * @return the boolean
     */
    private Boolean filter(JsonElement json, JsonElement whereElement) {
        Boolean filter = Boolean.TRUE;
        JsonArray where = jsonLever.asJsonArray(whereElement, new JsonArray());
        for (JsonElement condition : where) {
            if (jsonLever.isString(condition)) {
                String operator = StringUtils.lowerCase(jsonLever.asString(condition));
                if (equalsAny(operator, "&", "&&", "and")) {
                    if (filter) {
                        continue;
                    } else {
                        return filter;
                    }
                }
                if (equalsAny(operator, "|", "||", "or")) {
                    if (!filter) {
                        continue;
                    } else {
                        return filter;
                    }
                }
            } else if (jsonLever.isObject(condition)) {
                String type = jsonLever.getString(condition, "type");
                type = null != type && queryTypes.containsKey(type) ? type : "=";
                filter = queryTypes.get(type).apply(json, condition.getAsJsonObject());
            }
        }
        return filter;
    }

    /**
     * Equals filter.
     *
     * @param json the json
     * @param condition the condition
     * @return the boolean
     */
    private Boolean equalsFilter(JsonElement json, JsonObject condition) {
        return jsonLever.asJsonElement(condition.get("value"), JsonNull.INSTANCE)
                .equals(jsonLever.get(json, jsonLever.asJsonArray(condition.get("field"), new JsonArray())));
    }

    /**
     * Not equals filter.
     *
     * @param json the json
     * @param condition the condition
     * @return the boolean
     */
    private Boolean notEqualsFilter(JsonElement json, JsonObject condition) {
        return !equalsFilter(json, condition);
    }

    /**
     * In filter.
     *
     * @param json the json
     * @param condition the condition
     * @return the boolean
     */
    private Boolean inFilter(JsonElement json, JsonObject condition) {
        return jsonLever.asJsonArray(condition.get("value"), new JsonArray())
                .contains(jsonLever.get(json, jsonLever.asJsonArray(condition.get("field"), new JsonArray())));
    }

    /**
     * Notin filter.
     *
     * @param json the json
     * @param condition the condition
     * @return the boolean
     */
    private Boolean notinFilter(JsonElement json, JsonObject condition) {
        return !inFilter(json, condition);
    }

    private Boolean equalsAll(String str, String... matches) {
        Boolean equals = Boolean.TRUE;
        for (String string : matches) {
            equals = equals && StringUtils.equals(str, string);
            if (!equals) {
                break;
            }
        }
        return equals;
    }

    private Boolean equalsAny(String str, String... matches) {
        Boolean equals = Boolean.FALSE;
        for (String string : matches) {
            equals = equals || StringUtils.equals(str, string);
            if (equals) {
                break;
            }
        }
        return equals;
    }

    /**
     * Validate.
     *
     * @param query the query
     * @return the string
     */
    private String validate(JsonObject query) {
        if (jsonLever.isNull(jsonLever.get(query, "select"))) {
            return "select field of the query cannot be null";
        }
        JsonElement from = query.get("from");
        if (!(jsonLever.isObject(from) || jsonLever.isArray(from))) {
            return "from field must be a valid json object or array";
        }
        JsonElement where = jsonLever.get(query, "where");
        if (jsonLever.isNotNull(where) && !jsonLever.isArray(where)) {
            return "where field must be a valid json array";
        }
        JsonElement limit = jsonLever.get(query, "limit");
        if (jsonLever.isNotNull(limit) && !jsonLever.isNumber(limit)) {
            return "limit must be a valid number";
        }
        return "valid";
    }
}