Skip to content

Commit

Permalink
Tentatively complete "FrontAction" (first of 2-action whisk sequence)
Browse files Browse the repository at this point in the history
Issue #67
  • Loading branch information
joshuaauerbachwatson committed Sep 21, 2017
1 parent 8710d8e commit 32a52a0
Showing 1 changed file with 214 additions and 0 deletions.
214 changes: 214 additions & 0 deletions javaService/src/org/qcert/javasvc/FrontAction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/**
* Copyright (C) 2017 Joshua Auerbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.qcert.javasvc;

import java.io.IOException;
import java.io.StringWriter;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonWriter;

/**
* Code that may be deployed as a whisk action in a two-action sequence where the following action is made from QcertJS.js.
* Incorporates both the Java service and the logic in the demo that decides when to invoke it for a given QcertCompilerConfig.
* The same code can also be invoked in a simulation under NanoHTTPd (designed to replace the current JavaService).
*/
public class FrontAction extends Dispatcher {
/**
* The entry point of the action.
* @param args a JSON object conforming to the QcertCompilerConfig type.
* @return the exact argument if no Java services have been performed or else a modified JSON object of the same type but modified
* by the actions of the applicable JavaServices
*/
public static JsonObject main(JsonObject args) {
String verb = null;
boolean sourceCAMP = false;
// TODO add support for CSV conversion of input data, if any
String source = getAsString(args, "source");
if (source == null || source.length() == 0)
return argsWithError(args, "Source not specified");
String query = getAsString(args, "query");
if (query == null || query.length() == 0)
return argsWithError(args, "Query not specified");
String schema = getAsString(args, "schema");
// Schema can legally be missing in many cases. If it is present, though, it might need conversion from SQL to JSON
schema = maybeConvertSchema(schema);
// Input is only present when evaluation is called for. If present, it might need conversion from CSV form to our standard form */
String input = getAsString(args, "input");
input = maybeConvertInput(input, schema);
switch (source) {
case "sql": {
verb = "parseSQL";
sourceCAMP = false;
break;
}
case "sqlpp": {
verb = "parseSQLPP";
sourceCAMP = false;
break;
}
case "tech_rule": {
if (schema == null || schema.length() == 0)
return argsWithError(args, "A schema is required for technical rules");
verb = "techRule2CAMP";
sourceCAMP = true;
query = combineInputAndSchema(query, schema);
if (query == null)
return argsWithError(args, "Invalid schema");
break;
}
case "designer_rule": {
verb = "serialRule2CAMP";
sourceCAMP = true;
break;
}
default:
return args;
}
String result = dispatch(verb, query);
args.addProperty("query", result);
args.addProperty("sourcesexp", true);
if (sourceCAMP) {
args.addProperty("source", "camp_rule");
fixPath(args.get("path"));
}
if (input != null)
args.addProperty("input", input);
return args;
}

/** Convenient wrapper for adding an error element to the args */
private static JsonObject argsWithError(JsonObject args, String error) {
args.addProperty("error", error);
return args;
}

/** Combines a (String) query with a (JSON-as-String) schema to form a new JSON object and Stringifies the result.
* Any error results in a null return. */
private static String combineInputAndSchema(String query, String schema) {
try {
JsonElement parsed = new JsonParser().parse(schema);
JsonObject ans = new JsonObject();
ans.add("schema", parsed);
ans.addProperty("query", query);
return stringify(ans);
} catch (Throwable t) {
return null;
}
}

/** If an initial jrule-to-camp step was performed by this action, and there is a path, adjust the path to account for the step
* performed. If the path is invalid, we leave it alone (the error will be handled downstream).
* @param path the candidate path (null if absent)
*/
private static void fixPath(JsonElement path) {
if (path == null || !path.isJsonArray())
return;
JsonArray patharray = path.getAsJsonArray();
if (patharray.size() > 0) {
JsonElement first = patharray.get(0);
if (first.isJsonPrimitive() && first.getAsJsonPrimitive().isString()) {
String maybe = first.getAsString();
if (maybe.equals("camp_rule"))
patharray.remove(0);
}
}


}

/** Convenient wrapper for getting a String, defensively, avoiding any exceptions for unexpected data form.
* Returns null if either the member doesn't exist or the member isn't a String
*/
private static String getAsString(JsonObject obj, String member) {
JsonElement ans = obj.get(member);
if (ans == null)
return null;
if (ans.isJsonPrimitive() && ans.getAsJsonPrimitive().isString())
return ans.getAsString();
return null;
}

/** Determine if a String contains a SQL schema. Not intended to be foolproof but just to discriminate the two supported schema
notations (SQL and JSON) when the input is at least mostly valid. */
private static boolean isSQLSchema(String schemaText) {
/* A SQL schema should have the word "create" in it but SQL is case insensitive. For present purposes, we can
* just lowercase the entire string. */
schemaText = schemaText.toLowerCase();
int create = schemaText.indexOf("create");
if (create < 0)
return false;
int brace = schemaText.indexOf('{');
if (brace >= 0 && brace < create)
/* Word create is coincidentally appearing inside what is probably a JSON schema */
return false;
/* Looking more like SQL. Drop any blanks that follow 'create' */
schemaText = schemaText.substring(create + 6).trim();
/* The next word must be 'table' (case insensitive) */
int table = schemaText.indexOf("table");
return table == 0;
}

/**
* Determine if there is input in CSV form; if so, convert it to standard form
* @param input the contents of the input member or null if there is no such member
* @return
*/
private static String maybeConvertInput(String input, String schema) {
if (input == null)
return null;
JsonParser parser = new JsonParser();
JsonElement parsedInput = parser.parse(input);
if (parsedInput.isJsonObject() && parsedInput.getAsJsonObject().has("delimiter") && parsedInput.getAsJsonObject().has("data")) {
parsedInput.getAsJsonObject().add("schema", parser.parse(schema));
try {
return dispatch("csv2JSON", stringify(parsedInput));
} catch (Exception e) {}
}
return input;
}

/**
* Determine if there is a schema in SQL form; if so, convert it to JSON form
* @param schema the schema to convert or null if there is no schema
* @return the appropriately modified form of the argument (often just the argument itself)
*/
private static String maybeConvertSchema(String schema) {
if (schema != null && isSQLSchema(schema))
schema = dispatch("sqlSchema2JSON", schema);
return schema;
}

/**
* Encapsulate the preferred logic to stringify JSON using GSON (toString will do something close, but this is better)
* @param element the JSON element to stringify
* @return the String version
* @throws IOException if anything goes wrong
*/
private static String stringify(JsonElement element) throws IOException {
StringWriter wtr = new StringWriter();
JsonWriter json = new JsonWriter(wtr);
json.setLenient(true);
json.setIndent(" ");
Streams.write(element, json);
json.close();
return wtr.toString();
}
}

0 comments on commit 32a52a0

Please sign in to comment.