JSONUtils.java [src/csip/utils] Revision:   Date:
/*
 * $Id$
 *
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * a Model-as-a-Service framework, API and application suite.
 *
 * 2012-2022, Olaf David and others, OMSLab, Colorado State University.
 *
 * OMSLab licenses this file to you under the MIT license.
 * See the LICENSE file in the project root for more information.
 */
package csip.utils;

import csip.Config;
import csip.ModelDataService;
import static csip.ModelDataService.KEY_PARAMETER;
import static csip.ModelDataService.KEY_RESULT;
import csip.api.server.ServiceException;
import java.io.File;
import java.util.*;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.sql.SQLException;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import org.apache.commons.io.FileUtils;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;

/**
 *
 * @author od
 */
public class JSONUtils {

  public static int[] toIntArray(JSONArray arr) throws JSONException {
    int[] a = new int[arr.length()];
    for (int i = 0; i < a.length; i++) {
      a[i] = arr.getInt(i);
    }
    return a;
  }


  public static boolean[] toBooleanArray(JSONArray arr) throws JSONException {
    boolean[] a = new boolean[arr.length()];
    for (int i = 0; i < a.length; i++) {
      a[i] = arr.getBoolean(i);
    }
    return a;
  }


  public static long[] toLongArray(JSONArray arr) throws JSONException {
    long[] a = new long[arr.length()];
    for (int i = 0; i < a.length; i++) {
      a[i] = arr.getLong(i);
    }
    return a;
  }


  public static double[] toDoubleArray(JSONArray arr) throws JSONException {
    double[] a = new double[arr.length()];
    for (int i = 0; i < a.length; i++) {
      a[i] = arr.getDouble(i);
    }
    return a;
  }


  public static String[] toStringArray(JSONArray arr) throws JSONException {
    String[] a = new String[arr.length()];
    for (int i = 0; i < a.length; i++) {
      a[i] = arr.getString(i);
    }
    return a;
  }


  public static String[][] to2DStringArray(JSONArray arr) throws JSONException {
    String[][] a = new String[arr.length()][];
    for (int i = 0; i < a.length; i++) {
      JSONArray a2 = arr.getJSONArray(i);
      a[i] = toStringArray(a2);
    }
    return a;
  }


  public static JSONArray toArray(Collection<?> val) {
    return new JSONArray(val);
  }


  public static JSONArray toArray(double[] val) {
    return new JSONArray(DoubleStream.of(val).boxed().collect(Collectors.toList()));
  }


  public static JSONArray toArray(boolean[] val) {
    return new JSONArray(IntStream.range(0, val.length).mapToObj(idx -> val[idx]).collect(Collectors.toList()));
  }


  public static JSONArray toArray(long[] val) {
    return new JSONArray(LongStream.range(0l, (long) val.length).mapToObj(idx -> val[(int) idx]).collect(Collectors.toList()));
  }


  public static JSONArray toArray(int[] val) {
    return new JSONArray(IntStream.of(val).boxed().collect(Collectors.toList()));
  }


  public static JSONArray toArray(String[] val) {
    return new JSONArray(Arrays.asList(val));
  }


  public static void mergeInto(JSONObject src, JSONObject dest) throws JSONException {
    Iterator<?> i = src.keys();
    while (i.hasNext()) {
      String key = i.next().toString();
      dest.put(key, src.get(key));
    }
  }


  public static JSONObject clone(JSONObject o) throws JSONException {
    return new JSONObject(o.toString());
  }


  public static String[] getNames(JSONObject o) {
    Iterator<?> i = o.keys();
    ArrayList<String> l = new ArrayList<>();
    while (i.hasNext()) {
      l.add(i.next().toString());
    }
    return l.toArray(new String[l.size()]);
  }


  public static boolean getBooleanParam(Map<String, JSONObject> param, String key, boolean def) {
    try {
      return (param.get(key) != null) ? param.get(key).getBoolean(ModelDataService.VALUE) : def;
    } catch (JSONException ex) {
      return def;
    }
  }


  public static int getIntParam(Map<String, JSONObject> param, String key, int def) {
    try {
      return (param.get(key) != null) ? param.get(key).getInt(ModelDataService.VALUE) : def;
    } catch (JSONException ex) {
      return def;
    }
  }


  public static double getDoubleParam(Map<String, JSONObject> param, String key, double def) {
    try {
      return (param.get(key) != null) ? param.get(key).getDouble(ModelDataService.VALUE) : def;
    } catch (JSONException ex) {
      return def;
    }
  }


  public static long getJSONLong(JSONObject o, String key, long def) {
    try {
      return o.getLong(key);
    } catch (JSONException ex) {
      return def;
    }
  }


  public static int getJSONInt(JSONObject o, String key, int def) {
    try {
      return o.getInt(key);
    } catch (JSONException ex) {
      return def;
    }
  }


  public static String getJSONString(JSONObject o, String key, String def) {
    try {
      return o.getString(key);
    } catch (JSONException ex) {
      return def;
    }
  }


  public static boolean getJSONBoolean(JSONObject o, String key, boolean def) {
    try {
      return o.getBoolean(key);
    } catch (JSONException ex) {
      return def;
    }
  }


  public static Object getObjectParam(Map<String, JSONObject> param, String key, Object def) {
    try {
      return (param.get(key) != null) ? param.get(key).get(ModelDataService.VALUE) : def;
    } catch (JSONException ex) {
      return def;
    }
  }


  public static String getStringParam(Map<String, JSONObject> param, String key, String def) {
    try {
      return (param.get(key) != null) ? param.get(key).getString(ModelDataService.VALUE) : def;
    } catch (JSONException ex) {
      return def;
    }
  }


  public static JSONArray getJSONArrayParam(Map<String, JSONObject> param, String key) {
    try {
      return (param.get(key) != null) ? param.get(key).getJSONArray(ModelDataService.VALUE) : null;
    } catch (JSONException ex) {
      return null;
    }
  }


  public static JSONObject getJSONObjectParam(Map<String, JSONObject> param, String key) {
    try {
      return (param.get(key) != null) ? param.get(key).getJSONObject(ModelDataService.VALUE) : null;
    } catch (JSONException ex) {
      return null;
    }
  }


  public static JSONObject getJSONObjectGeometry(Map<String, JSONObject> param, String key) {
    try {
      return (param.get(key) != null) ? param.get(key).getJSONObject(ModelDataService.GEOMETRY) : null;
    } catch (JSONException ex) {
      return null;
    }
  }


  public static void checkDouble(String msg, double val, double min, double max) {
    if (Double.isNaN(val))
      throw new IllegalArgumentException(msg + " no valid input " + val);

    if (val < min || val > max)
      throw new IllegalArgumentException(msg + " illegal value: " + val);

  }


  public static List<String> getRequestedResults(JSONObject metainfo) throws ServiceException {
    List<String> l = new ArrayList<>();
    try {
      JSONArray j = metainfo.getJSONArray(ModelDataService.KEY_REQUEST_RESULTS);
      for (int i = 0; i < j.length(); i++) {
        l.add(j.getString(i));
      }
    } catch (JSONException ex) {
      throw new ServiceException("No results requested.");
    }
    return l;
  }


  public static void checkValidResultRequest(JSONObject metainfo, Collection<String> results) throws ServiceException {
    try {
      JSONArray j = metainfo.getJSONArray(ModelDataService.KEY_REQUEST_RESULTS);
      for (int i = 0; i < j.length(); i++) {
        String name = j.getString(i);
        if (!results.contains(name)) {
          throw new ServiceException("unknown result request :" + name);
        }
      }
    } catch (JSONException ex) {
      throw new ServiceException("No results requested. Add one or more of : " + results.toString());
    }
  }


  public static void checkKeyExists(Map<String, JSONObject> params, String key) throws ServiceException {
    JSONObject j = params.get(key);
    if (j == null)
      throw new ServiceException("Key not found :" + key);

  }


  public static boolean checkKeyExistsB(Map<String, JSONObject> params, String key) {
    JSONObject j = params.get(key);
    return j != null;
  }


  public static boolean checkArrayExists(JSONObject obj, String arrayKey) {
    try {
      obj.getJSONArray(arrayKey);
      return true;
    } catch (JSONException e) {
      return false;
    }
  }


  public static void checkFileExists(JSONObject param, File dir) throws JSONException, ServiceException {
    String file = param.getString(ModelDataService.VALUE);
    if (file == null)
      throw new ServiceException("Invalid File :" + param.getString(ModelDataService.VALUE));

    if (!new File(dir, file).exists())
      throw new ServiceException("File not found :" + param.getString(ModelDataService.VALUE));

  }


  public static Map<String, JSONObject> getParameter(JSONObject request) throws JSONException {
    JSONArray param = request.getJSONArray(KEY_PARAMETER);
    Map<String, JSONObject> pm = JSONUtils.preprocess(param);
    return pm;
  }


  public static Map<String, JSONObject> getResults(JSONObject request) throws JSONException {
    JSONArray param = request.getJSONArray(KEY_RESULT);
    Map<String, JSONObject> pm = JSONUtils.preprocess(param);
    return pm;
  }


  public static String getErrorStatus(JSONObject request) throws Exception {
    JSONObject metainfo = request.getJSONObject(ModelDataService.KEY_METAINFO);
    if (getStatus(request).equals(ModelDataService.FINISHED) && hasResult(request)) {
      return null;
    } else if (getStatus(request).equals(ModelDataService.SUBMITTED)) {
      return null;
    } else if (getStatus(request).equals(ModelDataService.RUNNING)) {
      return null;
    } else if (getStatus(request).equals(ModelDataService.FAILED) && metainfo.has("Error")) {
      return metainfo.getString("Error");
    }
    return "Error";
  }


  public static int getPayloadVersion(Object params) {
    if (params instanceof JSONArray)
      return 1;

    if (params instanceof JSONObject)
      return 2;

    throw new RuntimeException("Invalid payload structure");
  }


  public static Map<String, JSONObject> preprocess(Object params) throws JSONException {
    Map<String, JSONObject> p = new HashMap<>();
    switch (getPayloadVersion(params)) {
      case 1: // payload version 1
        JSONArray pa = (JSONArray) params;
        for (int i = 0; i < pa.length(); i++) {
          JSONObject o = pa.getJSONObject(i);
          p.put(o.getString(ModelDataService.KEY_NAME), o);
        }
        break;
      case 2: // payload version 2
        JSONObject po = (JSONObject) params;
        Iterator i = po.keys();
        while (i.hasNext()) {
          String key = i.next().toString();
          p.put(key, po.getJSONObject(key));
        }
    }
    return p;
  }


  public static JSONObject toV2(JSONArray results) throws JSONException {
    JSONObject r = new JSONObject();
    for (int i = 0; i < results.length(); i++) {
      JSONObject o = results.getJSONObject(i);
      r.put(o.getString(ModelDataService.KEY_NAME), o);
      o.remove(ModelDataService.KEY_NAME);
    }
    return r;
  }


  static JSONArray toJsonArray(Map<String, JSONObject> params) throws JSONException {
    JSONArray arr = new JSONArray();
    SortedSet<String> keys = new TreeSet<>(params.keySet());
    for (String key : keys) {
      JSONObject value = params.get(key);
      arr.put(value);
    }
    return arr;
  }


  public static JSONObject newRequest(JSONArray parameter, JSONObject metainfo) {
    JSONObject req = new JSONObject();
    try {
      metainfo.put(ModelDataService.KEY_TSTAMP, Dates.nowISO());
      req.put(ModelDataService.KEY_METAINFO, metainfo);
      req.put(ModelDataService.KEY_PARAMETER, parameter);
    } catch (JSONException ex) {
      ex.printStackTrace(System.err);
    }
    return req;
  }


  public static void putRequestedMetaInfo(JSONObject req, JSONObject res, String key, Object val) throws JSONException {
    if (req.getJSONObject(ModelDataService.KEY_METAINFO).has(key))
      res.getJSONObject(ModelDataService.KEY_METAINFO).put(key, val);

  }


  public static void putRequestedMetaInfo(JSONObject metainfo, String key, Object val) throws JSONException {
    if (metainfo.has(key))
      metainfo.put(key, val);

  }


  public static JSONObject newResponse() {
    JSONArray param = new JSONArray();
    JSONArray result = new JSONArray();
    JSONObject metainfo = new JSONObject();
    return newResponse(param, result, metainfo);
  }


  public static JSONObject newRequest() {
    JSONArray param = new JSONArray();
    JSONObject metainfo = new JSONObject();
    return newResponse(param, null, metainfo);
  }


  public static JSONObject newRequest(Map<String, JSONObject> params) throws JSONException {
    JSONObject metainfo = new JSONObject();
    return newResponse(toJsonArray(params), null, metainfo);
  }


  public static JSONObject newRequest(Map<String, JSONObject> params, JSONObject metainfo) throws JSONException {
    return newResponse(toJsonArray(params), null, metainfo);
  }


  public static JSONObject newRequest(JSONObject metainfo) {
    JSONArray param = new JSONArray();
    return newResponse(param, null, metainfo);
  }


  public static JSONObject getMetaInfo(JSONObject object) throws ServiceException {
    try {
      return object.getJSONObject(ModelDataService.KEY_METAINFO);
    } catch (JSONException ex) {
      throw new ServiceException(ex);
    }
  }


  public static JSONObject newResponse(JSONObject request, Object result, JSONObject metainfo) throws ServiceException {
    try {
      request.put(ModelDataService.KEY_METAINFO, metainfo);
      if (result != null)
        request.put(ModelDataService.KEY_RESULT, result);

    } catch (JSONException ex) {
      throw new ServiceException(ex);
    }
    return request;
  }


  public static JSONObject newResponse(JSONArray parameter, JSONArray result, JSONObject metainfo) {
    JSONObject req = new JSONObject();
    try {
      req.put(ModelDataService.KEY_METAINFO, metainfo);
      if (parameter != null)
        req.put(ModelDataService.KEY_PARAMETER, parameter);

      if (result != null)
        req.put(ModelDataService.KEY_RESULT, result);

    } catch (JSONException ex) {
      throw new RuntimeException(ex);
    }
    return req;
  }

  static final String GENERIC_SQL_EXCEPTION = "SQL failed with an exception, check logs for details.";


  public static String getErrorMessage(Throwable E) {
    // Controling the propagation of SQL messages such as failed sql queries 
    // via the return payload.
    String message = E.getMessage();
    if (message == null)
      message = E.getClass().toString();

    if (Config.getBoolean("csip.response.sqlfilter")) {
      if (E instanceof SQLException)
        return GENERIC_SQL_EXCEPTION;

      // check if a SQLException is somewhere in the stacktrace.
      Throwable cause = E.getCause();
      while (cause != null) {
        if (cause instanceof SQLException // only filter if SQLException message is in E
            /*
             * && (message.contains(cause.getMessage()))
             */)
          return GENERIC_SQL_EXCEPTION;

        cause = cause.getCause();
      }
    }
    return message;
  }


  public static String getStringStackTrace(Throwable e) {
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    e.printStackTrace(pw);
    pw.close();
    return sw.toString();
  }


  public static JSONArray getJSONStackTrace(Throwable e) {
    String s = getStringStackTrace(e);
    JSONArray arr = new JSONArray();
    for (String st : s.split("\n")) {
      arr.put(st.replace('\t', ' '));
    }
    return arr;
  }


  public static JSONObject newDeniedError(JSONObject meta, Object parameter, SecurityException err) {
    JSONObject resp = new JSONObject();
    try {
      meta.put(ModelDataService.ERROR, getErrorMessage(err));
      resp.put(ModelDataService.KEY_METAINFO, meta);
      if (parameter != null)
        resp.put(ModelDataService.KEY_PARAMETER, parameter);

    } catch (JSONException ex) {
      ex.printStackTrace(System.err);
    }
    return resp;
  }


  public static JSONObject newError(JSONObject meta, Object parameter, Throwable err) {
    JSONObject resp = new JSONObject();
    try {
      meta.put(ModelDataService.ERROR, getErrorMessage(err));
      if (Config.getBoolean("csip.response.stacktrace")) {
        JSONArray trace = getJSONStackTrace(err);
        if (trace != null)
          meta.put("stacktrace", trace);

      }
      resp.put(ModelDataService.KEY_METAINFO, meta);
      if (parameter != null)
        resp.put(ModelDataService.KEY_PARAMETER, parameter);

    } catch (JSONException ex) {
      ex.printStackTrace(System.err);
    }
    return resp;
  }


  public static String toString(JSONObject o) {
    int ind = Config.getInt(Config.CSIP_RESPONSE_JSONINDENT, 2);
    if (ind < 1) {
      return o.toString();
    } else {
      try {
        return o.toString(ind);
      } catch (JSONException ex) {
        return o.toString();
      }
    }
  }


  public static JSONObject data(String name, Object value) throws JSONException {
    return data(name, value, null);
  }


  public static JSONObject data(String name, JSONObject value) throws JSONException {
    if (name == null)
      throw new NullPointerException("name");

    JSONObject param = new JSONObject();
    param.put(ModelDataService.KEY_NAME, name);
    param.put(ModelDataService.VALUE, value == null ? JSONObject.NULL : value);
    return param;
  }


  public static JSONObject data(String name, Object value, String unit) throws JSONException {
    if (name == null)
      throw new NullPointerException("name");

    JSONObject param = new JSONObject();
    param.put(ModelDataService.KEY_NAME, name);
    if (value == null) {
      param.put(ModelDataService.VALUE, JSONObject.NULL);
    } else if (value instanceof Collection) {
      param.put(ModelDataService.VALUE, (Collection) value);
    } else if (value instanceof Map) {
      param.put(ModelDataService.VALUE, (Map) value);
    } else {
      param.put(ModelDataService.VALUE, value);
    }
    if (unit != null)
      param.put(ModelDataService.KEY_UNIT, unit);

    return param;
  }


  public static JSONObject dataDesc(String name, Object value, String desc) throws JSONException {
    if (name == null)
      throw new NullPointerException("name");

    JSONObject param = new JSONObject();
    param.put(ModelDataService.KEY_NAME, name);
    param.put(ModelDataService.VALUE, value == null ? JSONObject.NULL : value);
    if (desc != null)
      param.put(ModelDataService.KEY_DESC, desc);

    return param;
  }


  public static JSONObject data(String name, Object value, String descr, String unit) {
    return dataUnitDesc(name, value, unit, descr);
  }


  public static JSONObject dataUnitDesc(String name, Object value, String unit, String desc) {
    if (name == null)
      throw new NullPointerException("name");

    JSONObject param = new JSONObject();
    try {
      param.put(ModelDataService.KEY_NAME, name);
      param.put(ModelDataService.VALUE, value == null ? JSONObject.NULL : value);
      if (unit != null)
        param.put(ModelDataService.KEY_UNIT, unit);

      if (desc != null)
        param.put(ModelDataService.KEY_DESC, desc);

      return param;
    } catch (JSONException ex) {
      throw new RuntimeException(ex);
    }
  }


  public static JSONObject error(String text) {
    JSONObject o = new JSONObject();
    try {
      o.put(ModelDataService.ERROR, text);
    } catch (JSONException ex) {
      ex.printStackTrace(System.err);
    }
    return o;
  }


  public static JSONObject ok(String text) {
    JSONObject o = new JSONObject();
    try {
      o.put(ModelDataService.OK, text);
    } catch (JSONException ex) {
      ex.printStackTrace(System.err);
    }
    return o;
  }


  public static String getValue(JSONObject o) {
    try {
      return o.getString(ModelDataService.VALUE);
    } catch (JSONException ex) {
      ex.printStackTrace(System.err);
    }
    return null;
  }


  public static String getValueByKey(JSONArray a, String key) {
    try {
      for (int i = 0; i < a.length(); i++) {
        JSONObject attr = a.getJSONObject(i);
        if (attr.getString(ModelDataService.KEY_NAME).equals(key))
          return attr.getString(ModelDataService.VALUE);

      }
      throw new JSONException("Key not found in array=" + key);
    } catch (JSONException ex) {
      ex.printStackTrace(System.err);
    }
    return null;
  }


  public static String getUnit(JSONObject o) {
    try {
      return o.getString(ModelDataService.UNIT);
    } catch (JSONException ex) {
      ex.printStackTrace(System.err);
    }
    return null;
  }


  public static boolean hasResult(JSONObject r) throws Exception {
    if (r.has("result")) {
      JSONArray response_result = r.getJSONArray("result");
      return response_result.length() > 0;
    }
    return false;
  }


  public static String getSID(JSONObject r) throws JSONException {
    JSONObject meta = r.getJSONObject("metainfo");
    if (meta.has("suid"))
      return meta.getString("suid");

    return "";
  }


  public static boolean isAsync(JSONObject r) throws JSONException {
    JSONObject meta = r.getJSONObject("metainfo");
    if (meta.has("mode")) {
      String mode = meta.getString("mode");
      return mode.toLowerCase().equals("async");
    }
    return false;
  }


  public static String getStatus(JSONObject r) throws JSONException {
    JSONObject meta = r.getJSONObject("metainfo");
    if (meta.has("status"))
      return meta.getString("status");

    return "unknown";
  }


  public boolean hasRange(JSONObject o) {
    return o.has(ModelDataService.RANGE);
  }


  public static double getRangeMin(JSONObject o) {
    try {
      return o.getJSONObject(ModelDataService.RANGE).getDouble(ModelDataService.MIN);
    } catch (JSONException ex) {
      ex.printStackTrace(System.err);
    }
    return Double.MIN_VALUE;
  }


  public static double getRangeMax(JSONObject o) throws JSONException {
    try {
      return o.getJSONObject(ModelDataService.RANGE).getDouble(ModelDataService.MAX);
    } catch (JSONException ex) {
      ex.printStackTrace(System.err);
    }
    return Double.MAX_VALUE;
  }


  public static String getDescription(JSONObject o) {
    try {
      return o.getString(ModelDataService.KEY_DESC);
    } catch (JSONException ex) {
      ex.printStackTrace(System.err);
    }
    return null;
  }


  public boolean hasIntent(JSONObject o) {
    return o.has(ModelDataService.INTENT);
  }


  public static boolean isIn(JSONObject o) {
    try {
      return o.getString(ModelDataService.INTENT).toLowerCase().contains(ModelDataService.IN);
    } catch (JSONException ex) {
      ex.printStackTrace(System.err);
    }
    return false;
  }


  public static boolean isOut(JSONObject o) {
    try {
      return o.getString(ModelDataService.INTENT).toLowerCase().contains(ModelDataService.OUT);
    } catch (JSONException ex) {
      ex.printStackTrace(System.err);
    }
    return false;
  }


  public static boolean isInOut(JSONObject o) {
    return isIn(o) && isOut(o);
  }


  public static JSONObject read(File file) throws Exception {
    String f = FileUtils.readFileToString(file, "UTF-8");
    JSONObject o = new JSONObject(f);
    return o;
  }


  public static void write(JSONObject o, File file) throws Exception {
    FileUtils.writeStringToFile(file, o.toString(2), "UTF-8");
  }

}