ccc5ea667ed3b9d65ea787930840324c5ad15b16
[so.git] / bpmn / MSOCoreBPMN / src / main / java / org / onap / so / bpmn / core / json / JsonUtils.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright (C) 2017 Huawei Technologies Co., Ltd. All rights reserved.
7  * ================================================================================
8  * Modifications Copyright (c) 2019 Samsung
9  * ================================================================================
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  *      http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  * ============LICENSE_END=========================================================
22  */
23
24 package org.onap.so.bpmn.core.json;
25
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.StringTokenizer;
33
34 import org.camunda.bpm.engine.delegate.DelegateExecution;
35 import org.camunda.bpm.engine.runtime.Execution;
36 import org.json.JSONArray;
37 import org.json.JSONException;
38 import org.json.JSONObject;
39 import org.json.XML;
40 import org.onap.so.bpmn.core.xml.XmlTool;
41 import org.onap.so.exceptions.ValidationException;
42
43 import com.fasterxml.jackson.databind.JsonNode;
44 import com.github.fge.jackson.JsonLoader;
45 import com.github.fge.jsonschema.core.exceptions.ProcessingException;
46 import com.github.fge.jsonschema.core.report.ProcessingReport;
47 import com.github.fge.jsonschema.main.JsonSchemaFactory;
48 import com.github.fge.jsonschema.main.JsonValidator;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 /**
53  * Utility class for JSON processing
54  *
55  * @version 1.0
56  *
57  * Note: It was observed, that depending on the JSON implementation, an org.json.JSONException or a
58  *       java.util.NoSuchElementException will be thrown in the event of the key value being "not found"
59  *       in a JSON document. A general check has been added to the applicable catch blocks for this
60  *       this type of behavior to reduce the amount of logging. As a key value not being found is
61  *       expect behavior, it makes no sense to log the stack trace associated with this type of failure.
62  */
63 public class JsonUtils {
64
65         private static Logger logger = LoggerFactory.getLogger(JsonUtils.class);
66         private static int MSOJsonIndentFactor = 3;
67
68         /**
69          * Uses the JSONObject static method to convert a XML doc to JSON.
70          *
71          * @param  xml          String containing the XML doc
72          * @param  pretty       flag to determine if the output should be formatted
73          * @return String containing the JSON translation
74          */
75         public static String xml2json(String xml, Boolean pretty) {
76                 try {
77                         // name spaces cause problems, so just remove them
78                         JSONObject jsonObj = XML.toJSONObject(XmlTool.removeNamespaces(xml));
79                         if (!pretty) {
80                                 return jsonObj.toString();
81                         } else {
82                                 // add an indent to make it 'pretty'
83                                 return jsonObj.toString(MSOJsonIndentFactor);
84                         }
85                 } catch (Exception e){
86                                 logger.debug("xml2json(): unable to parse xml and convert to json. Exception was: {}", e.toString(), e);
87                                 return null;
88                 }
89         }
90
91         /**
92          * Invokes xml2json(String, Boolean) defaulting to 'pretty' output.
93          *
94          * @param  xml  String containing the XML doc
95          * @return String containing the JSON translation
96          */
97         public static String xml2json(String xml) {
98                 return xml2json(xml, true);
99         }
100
101         /**
102          * Uses the JSONObject static method to convert a JSON doc to XML.
103          * Note: this method may not generate valid XML if the JSONObject
104          * contains JSONArrays which are used to represent XML attributes
105          * in the JSON doc.
106          *
107          * @param  jsonStr      String containing the JSON doc
108          * @param  pretty       flag to determine if the output should be formatted
109          * @return String containing the XML translation
110          */
111         public static String json2xml(String jsonStr, Boolean pretty) {
112
113                 try {
114                         JSONObject jsonObj = new JSONObject(jsonStr);
115                         if (pretty) {
116 //                              use the local class method which properly handles certain JSONArray content
117                                 return XmlTool.normalize(toXMLString(jsonObj, null));
118                         } else {
119 //                              use the local class method which properly handles certain JSONArray content
120                                 return toXMLString(jsonObj, null);
121                         }
122                 } catch (Exception e){
123                                 logger.debug("json2xml(): unable to parse json and convert to xml. Exception was: {}", e.toString(), e);
124                                 return null;
125                 }
126         }
127
128         /**
129          * Uses a modified version of the org.json.XML toString() algorithm
130          * to convert a JSONObject to an XML Doc. The intent of this is to
131          * correctly generate XML from JSON including TAGs for JSONArrays
132          *
133          * @param  obj  org.json.JSON object to be converted to XML
134          * @param  tagName      optional XML tagname supplied primarily during recursive calls
135          * @return String containing the XML translation
136          */
137         public static String toXMLString(Object obj, String tagName) throws JSONException {
138                 StringBuilder strBuf = new StringBuilder();
139                 int i;
140                 JSONArray jsonArr;
141                 JSONObject jsonObj;
142                 String key;
143                 Iterator<String> keys;
144                 int len;
145                 String str;
146                 Object curObj;
147                 if (obj instanceof JSONObject) {
148                         // append "<tagName>" to the XML output
149                         if (tagName != null) {
150                                 strBuf.append("<");
151                                 strBuf.append(tagName);
152                                 strBuf.append(">");
153                         }
154                         // iterate thru the keys.
155                         jsonObj = (JSONObject) obj;
156                         keys = jsonObj.keys();
157                         while (keys.hasNext()) {
158                                 key = keys.next();
159                                 curObj = jsonObj.opt(key);
160                                 if (curObj == null) {
161                                         curObj = "";
162                                 }
163                                 if (curObj instanceof String) {
164                                         str = (String) curObj;
165                                 } else {
166                                         str = null;
167                                 }
168                                 // append the content to the XML output
169                                 if ("content".equals(key)) {
170                                         if (curObj instanceof JSONArray) {
171                                                 jsonArr = (JSONArray) curObj;
172                                                 len = jsonArr.length();
173                                                 for (i = 0; i < len; i += 1) {
174                                                         if (i > 0) {
175                                                                 strBuf.append('\n');
176                                                         }
177                                                         strBuf.append(XML.escape(jsonArr.get(i).toString()));
178                                                 }
179                                         } else {
180                                                 strBuf.append(XML.escape(curObj.toString()));
181                                         }
182                                 // append an array of similar keys to the XML output
183                                 } else if (curObj instanceof JSONArray) {
184                                         jsonArr = (JSONArray) curObj;
185                                         len = jsonArr.length();
186                                         for (i = 0; i < len; i += 1) {
187                                                 curObj = jsonArr.get(i);
188                                                 if (curObj instanceof JSONArray) {
189 //                                                      The XML tags for the nested array should be generated below when this method
190 //                                                      is called recursively and the JSONArray object is passed
191 //                                                      strBuf.append("<");
192 //                                                      strBuf.append(key);
193 //                                                      strBuf.append(">");
194                                                         strBuf.append(toXMLString(curObj, null));
195 //                                                      strBuf.append("</");
196 //                                                      strBuf.append(key);
197 //                                                      strBuf.append(">");
198                                                 } else {
199                                                         // append the opening tag for the array (before 1st element)
200                                                         if (i == 0) {
201                                                                 strBuf.append("<");
202                                                                 strBuf.append(key);
203                                                                 strBuf.append(">");
204                                                         }
205                                                         // append the opening tag for the array
206                                                         strBuf.append(toXMLString(curObj, null));
207                                                         // append the closing tag for the array (after last element)
208                                                         if (i == (len - 1)) {
209                                                                 strBuf.append("</");
210                                                                 strBuf.append(key);
211                                                                 strBuf.append(">");
212                                                         }
213                                                 }
214                                         }
215                                 } else if (curObj.equals("")) {
216                                         // append a closing tag "<key>" to the XML output
217                                         strBuf.append("<");
218                                         strBuf.append(key);
219                                         strBuf.append("/>");
220                                 } else {
221                                         strBuf.append(toXMLString(curObj, key));
222                                 }
223                         }
224                         if (tagName != null) {
225                                 // append the closing tag "</tagName>" to the XML output
226                                 strBuf.append("</");
227                                 strBuf.append(tagName);
228                                 strBuf.append(">");
229                         }
230                         return strBuf.toString();
231                 // XML does not have good support for arrays. If an array appears in a place
232                 // where XML is lacking, synthesize an < array > element.
233                 } else if (obj instanceof JSONArray) {
234                         jsonArr = (JSONArray) obj;
235                         len = jsonArr.length();
236                         for (i = 0; i < len; ++i) {
237                                 curObj = jsonArr.opt(i);
238                                 strBuf.append(toXMLString(curObj, (tagName == null) ? "array"
239                                                 : tagName));
240                         }
241                         return strBuf.toString();
242                 } else {
243                         str = (obj == null) ? "null" : XML.escape(obj.toString());
244                         return (tagName == null) ? "\"" + str + "\""
245                                         : (str.length() == 0) ? "<" + tagName + "/>" : "<"
246                                                         + tagName + ">" + str + "</" + tagName + ">";
247                 }
248         }
249
250         /**
251          * Invokes json2xml(String, Boolean) defaulting to 'pretty' output.
252          *
253          * @param  jsonStr      String containing the XML doc
254          * @return String containing the JSON translation
255          */
256         public static String json2xml(String jsonStr) {
257                 return json2xml(jsonStr, true);
258         }
259
260         /**
261          * Formats the JSON String using the value of MSOJsonIndentFactor.
262          *
263          * @param  jsonStr      String containing the JSON doc
264          * @return String containing the formatted JSON doc
265          */
266         public static String prettyJson(String jsonStr) {
267                 try {
268                         JSONObject jsonObj = new JSONObject(jsonStr);
269                         return jsonObj.toString(MSOJsonIndentFactor);
270                 } catch (Exception e){
271                         logger.debug("prettyJson(): unable to parse/format json input. Exception was: {}", e.toString(), e);
272                         return null;
273                 }
274         }
275
276         /**
277          * Returns an Iterator over the JSON keys in the specified JSON doc.
278          *
279          * @param  jsonStr      String containing the JSON doc
280          * @return Iterator over the JSON keys
281          * @throws JSONException if the doc cannot be parsed
282          */
283         public static Iterator <String> getJsonIterator(String jsonStr) throws JSONException {
284                 return new JSONObject(jsonStr).keys();
285         }
286
287         /**
288          * Returns the name of the "root" property in the specified JSON doc. The
289          * "root" property is the single top-level property in the JSON doc. An
290          * exception is thrown if the doc is empty or if it contains more than one
291          * top-level property.
292          *
293          * @param  jsonStr      String containing the JSON doc
294          * @return the name of the "root" property
295          * @throws JSONException if the doc cannot be parsed, or if it is empty, or if
296          *         it contains more than one top-level property
297          */
298         public static String getJsonRootProperty(String jsonStr) throws JSONException {
299                 Iterator<String> iter = getJsonIterator(jsonStr);
300
301                 if (!iter.hasNext()) {
302                         throw new JSONException("Empty JSON object");
303                 }
304
305                 String rootPropertyName = iter.next();
306
307                 if (iter.hasNext()) {
308                         throw new JSONException("JSON object has more than one root property");
309                 }
310
311                 return rootPropertyName;
312         }
313
314         /**
315          * Invokes the getJsonRawValue() method and returns the String equivalent of
316          * the object returned.
317          *
318          * TBD: May need separate methods for boolean, float, and integer fields if the
319          * String representation is not sufficient to meet client needs.
320          *
321          * @param  jsonStr      String containing the JSON doc
322          * @param  keys         full key path to the target value in the format of "key1.key2.key3..."
323          * @return String field value associated with keys
324          */
325         public static String getJsonValue(String jsonStr, String keys) {
326                 try {
327                                 Object rawValue = getJsonRawValue(jsonStr, keys);
328                                 if (rawValue == null) {
329                                         return null;
330                                 } else {
331                                         if (rawValue instanceof String) {
332                                                 logger.debug("getJsonValue(): the raw value is a String Object={}", rawValue);
333                                                 return (String) rawValue;
334                                         } else {
335                                                 logger.debug("getJsonValue(): the raw value is NOT a String Object={}", rawValue.toString());
336                                                 return rawValue.toString();
337                                         }
338                                 }
339                 } catch (Exception e) {
340                         logger.debug("getJsonValue(): unable to parse json to retrieve value for field={}. Exception was: {}", keys,
341                                 e.toString(), e);
342                 }
343                 return null;
344         }
345
346         /**
347          * Invokes the getJsonRawValue() method with the wrap flag set to true
348          * and returns the String equivalent of the json node object returned.
349          *
350          * @param  jsonStr      String containing the JSON doc
351          * @param  keys         full key path to the target value in the format of "key1.key2.key3..."
352          * @return String field value associated with keys
353          */
354         public static String getJsonNodeValue(String jsonStr, String keys) {
355                 try {
356                                 Object rawValue = getJsonRawValue(jsonStr, keys, true);
357                                 if (rawValue == null) {
358                                         return null;
359                                 } else {
360                                         if (rawValue instanceof String) {
361                                                 logger.debug("getJsonNodeValue(): the raw value is a String Object={}", rawValue);
362                                                 return (String) rawValue;
363                                         } else {
364                                                 logger.debug("getJsonNodeValue(): the raw value is NOT a String Object={}", rawValue.toString());
365                                                 return rawValue.toString();
366                                         }
367                                 }
368                 } catch (Exception e) {
369                         logger.debug("getJsonNodeValue(): unable to parse json to retrieve node for field={}. Exception was: {}", keys,
370                                 e.toString(), e);
371                 }
372                 return null;
373         }
374
375         /**
376          * Invokes the getJsonRawValue() method and returns the String equivalent of
377          * the object returned.
378          *
379          * TBD: May need separate methods for boolean, float, and integer fields if the
380          * String representation is not sufficient to meet client needs.
381          *
382          * @param  jsonStr      String containing the JSON doc
383          * @param  keys         full key path to the target value in the format of "key1.key2.key3..."
384          * @return String field value associated with keys
385          */
386         public static int getJsonIntValue(String jsonStr, String keys) {
387                 try {
388                                 Object rawValue = getJsonRawValue(jsonStr, keys);
389                                 if (rawValue == null) {
390                                         return 0;
391                                 } else {
392                                         if (rawValue instanceof Integer) {
393                                                 logger.debug("getJsonIntValue(): the raw value is an Integer Object={}", ((String) rawValue).toString());
394                                                 return (Integer) rawValue;
395                                         } else {
396                                                 logger.debug("getJsonIntValue(): the raw value is NOT an Integer Object={}", rawValue.toString());
397                                                 return 0;
398                                         }
399                                 }
400                 } catch (Exception e) {
401                         logger.debug("getJsonIntValue(): unable to parse json to retrieve value for field={}. Exception was: {}", keys,
402                                 e.toString(), e);
403                 }
404                 return 0;
405         }
406
407         /**
408          * Invokes the getJsonRawValue() method and returns the boolean equivalent of
409          * the object returned.
410          *
411          * @param  jsonStr      String containing the JSON doc
412          * @param  keys         full key path to the target value in the format of "key1.key2.key3..."
413          * @return boolean field value associated with keys - default is false
414          */
415         public static boolean getJsonBooleanValue(String jsonStr, String keys) {
416                 try {
417                                 Object rawValue = getJsonRawValue(jsonStr, keys);
418                                 if (rawValue == null) {
419                                         return false;
420                                 } else {
421                                         if (rawValue instanceof Boolean) {
422                                                 logger.debug("getJsonBooleanValue(): the raw value is a Boolean Object={}", rawValue);
423                                                 return (Boolean) rawValue;
424                                         } else {
425                                                 logger.debug("getJsonBooleanValue(): the raw value is NOT an Boolean Object={}", rawValue.toString());
426                                                 return false;
427                                         }
428                                 }
429                 } catch (Exception e) {
430                         logger.debug("getJsonBooleanValue(): unable to parse json to retrieve value for field={}. Exception was: {}", keys,
431                                         e.toString(), e);
432                 }
433                 return false;
434         }
435
436         /**
437          * Invokes the getJsonParamValue() method to obtain the JSONArray associated with
438          * the specified keys. The JSONArray is then walked to retrieve the first array
439          * value associated with the specified field name (index=0).
440          *
441          * @param  jsonStr      String containing the JSON doc
442          * @param  keys         full key path to the target value in the format of "key1.key2.key3..."
443          * @param  name         field name for the param to be retrieved
444          * @return String param value associated with field name
445          */
446         public static String getJsonParamValue(String jsonStr, String keys, String name) {
447                 return getJsonParamValue(jsonStr, keys, name, 0);
448         }
449
450         /**
451          * Invokes the getJsonRawValue() method to obtain the JSONArray associated with
452          * the specified keys. The JSONArray is then walked to retrieve the nth array
453          * value associated with the specified field name and index.
454          *
455          * @param  jsonStr      String containing the JSON doc
456          * @param  keys         full key path to the target value in the format of "key1.key2.key3..."
457          * @param  name         field name for the param to be retrieved
458          * @param  index    the nth param associated with name starting at 0
459          * @return String param value associated with field name
460          */
461         public static String getJsonParamValue(String jsonStr, String keys, String name, int index) {
462                 try {
463                         Object rawValue = getJsonRawValue(jsonStr, keys);
464                         if (rawValue == null) {
465                                 return null;
466                         } else {
467                                 if (rawValue instanceof JSONArray) {
468                                         logger.debug("getJsonParamValue(): keys={} points to JSONArray: {}", keys, rawValue.toString());
469                                         int arrayLen = ((JSONArray) rawValue).length();
470                                         if (index < 0 || arrayLen < index+1) {
471                                                 logger.debug("getJsonParamValue(): index: {} is out of bounds for array size of {}", index, arrayLen);
472                                                 return null;
473                                         }
474                                         int foundCnt = 0;
475                                         for (int i = 0; i < arrayLen; i++) {
476                                                 logger.debug("getJsonParamValue(): index: {}, value: {}", i, ((JSONArray) rawValue).get(i).toString());
477                                                 if (((JSONArray) rawValue).get(i) instanceof JSONObject) {
478                                                         JSONObject jsonObj = (JSONObject)((JSONArray) rawValue).get(i);
479                                                         String parmValue = jsonObj.get(name).toString();
480                                                         if (parmValue != null) {
481                                                                 logger.debug("getJsonParamValue(): found value: {} for name: {} and index: {}", parmValue, name, i);
482                                                                 if (foundCnt == index) {
483                                                                         return parmValue;
484                                                                 } else {
485                                                                         foundCnt++;
486                                                                         continue;
487                                                                 }
488                                                         } else {
489                                                                 continue;
490                                                         }
491                                                 } else {
492                                                         logger.debug("getJsonParamValue(): the JSONArray element is NOT a JSONObject={}", rawValue.toString());
493                                                         return null;
494                                                 }
495                                         }
496                                         logger.debug("getJsonParamValue(): content value NOT found for name: {}", name);
497                                         return null;
498                                 } else {
499                                         logger.debug("getJsonParamValue(): the raw value is NOT a JSONArray Object={}", rawValue.toString());
500                                         return null;
501                                 }
502                         }
503                 } catch (Exception e) {
504                         if (e.getMessage().contains("not found")) {
505                                 logger.debug("getJsonParamValue(): failed to retrieve param value for keys:{}, name={} : {}", keys, name,
506                                         e.getMessage());
507                         } else {
508                                 logger.debug("getJsonParamValue(): unable to parse json to retrieve value for field={}. Exception was: {}", keys,
509                                                 e.toString(), e);
510                         }
511                 }
512                 return null;
513         }
514
515         /**
516          * Wrapper to generate the JSONObject to pass to the getJsonValueForKey(JSONObject, String)
517          * method so that recursion over the subobjects can be supported there
518          *
519          * @param  jsonStr      String containing the JSON doc
520          * @param  key          key to the target value
521          * @return String field value associated with key
522          */
523         public static String getJsonValueForKey(String jsonStr, String key) {
524
525                 try {
526                         JSONObject jsonObj = new JSONObject(jsonStr);
527                         return getJsonValueForKey(jsonObj, key);
528                 } catch (Exception e) {
529                         logger.debug("getJsonValueForKey(): unable to parse json to retrieve value for field={}. Exception was: {}", key,
530                                 e.toString(), e);
531                 }
532                 return null;
533         }
534
535         /**
536          * Walks the JSONObject (and sub-objects recursively), searching for the first value associated with the
537          * single key/field name specified. Returns the associated value if found or null if the key is not found
538          *
539          * @param  jsonObj      JSONObject representation of the the JSON doc
540          * @param  key          key to the target value
541          * @return String field value associated with key
542          */
543         public static String getJsonValueForKey(JSONObject jsonObj, String key) {
544
545                 String keyValue = null;
546                 try {
547                         if (jsonObj.has(key)) {
548                                 Object value = jsonObj.get(key);
549                                 logger.debug("getJsonValueForKey(): found value={}, for key={}", (String) value, key);
550                                 if (value == null) {
551                                         return null;
552                                 } else {
553                                         return ((String) value);
554                                 }
555                         } else {
556                                 Iterator <String> itr = jsonObj.keys();
557                                 while (itr.hasNext()) {
558                                         String nextKey = itr.next();
559                                         Object obj = jsonObj.get(nextKey);
560                                         if (obj instanceof JSONObject) {
561                                                 keyValue = getJsonValueForKey((JSONObject) obj, key);
562                                                 if (keyValue != null) {
563                                                         break;
564                                                 }
565                                         } else {
566                                                 logger.debug("getJsonValueForKey(): key={}, does not point to a JSONObject, next key", nextKey);
567                                         }
568                                 }
569                         }
570                 } catch (Exception e) {
571                         // JSONObject::get() throws a "not found" exception if one of the specified keys is not found
572                         if (e.getMessage().contains("not found")) {
573                                 logger.debug("getJsonValueForKey(): failed to retrieve param value for key={}: {}", key, e.getMessage());
574                         } else {
575                                 logger.debug("getJsonValueForKey(): unable to parse json to retrieve value for field={}. Exception was {}", key,
576                                         e.toString(), e);
577                         }
578                         keyValue = null;
579                 }
580                 return keyValue;
581         }
582
583         /**
584          * Walks the JSONObject (and sub-objects recursively), searching for the first value associated with the
585          * single key/field name specified. Returns the associated value if found or null if the key is not found
586          *
587          * @param  jsonObj      JSONObject representation of the the JSON doc
588          * @param  key          key to the target value
589          * @return String field value associated with key
590          */
591         public static Integer getJsonIntValueForKey(JSONObject jsonObj, String key) {
592                 Integer keyValue = null;
593                 try {
594                         if (jsonObj.has(key)) {
595                                 Integer value = (Integer) jsonObj.get(key);
596                                 logger.debug("getJsonIntValueForKey(): found value={}, for key={}", value, key);
597                                 return value;
598                         } else {
599                                 Iterator <String> itr = jsonObj.keys();
600                                 while (itr.hasNext()) {
601                                         String nextKey = itr.next();
602                                         Object obj = jsonObj.get(nextKey);
603                                         if (obj instanceof JSONObject) {
604                                                 keyValue = getJsonIntValueForKey((JSONObject) obj, key);
605                                                 if (keyValue != null) {
606                                                         break;
607                                                 }
608                                         } else {
609                                                 logger.debug("getJsonIntValueForKey(): key={}, does not point to a JSONObject, next key", nextKey);
610                                         }
611                                 }
612                         }
613                 } catch (Exception e) {
614                         // JSONObject::get() throws a "not found" exception if one of the specified keys is not found
615                         if (e.getMessage().contains("not found")) {
616                                 logger.debug("getJsonIntValueForKey(): failed to retrieve param value for key={}: {}", key, e.getMessage());
617                         } else {
618                                 logger.debug("getJsonIntValueForKey(): unable to parse json to retrieve value for field={}. Exception was: {}", key,
619                                                 e.toString(), e);
620                         }
621                         keyValue = null;
622                 }
623                 return keyValue;
624         }
625
626         /**
627          * Walks the JSONObject (and sub-objects recursively), searching for the first value associated with the
628          * single key/field name specified. Returns the associated value if found or null if the key is not found
629          *
630          * @param  jsonObj      JSONObject representation of the the JSON doc
631          * @param  key          key to the target value
632          * @return String field value associated with key
633          */
634         public static Boolean getJsonBooleanValueForKey(JSONObject jsonObj, String key) {
635                 Boolean keyValue = null;
636                 try {
637                         if (jsonObj.has(key)) {
638                                 Boolean value = (Boolean) jsonObj.get(key);
639                                 logger.debug("getJsonBooleanValueForKey(): found value={}, for key={}", value, key);
640                                 return value;
641                         } else {
642                                 Iterator <String> itr = jsonObj.keys();
643                                 while (itr.hasNext()) {
644                                         String nextKey = itr.next();
645                                         Object obj = jsonObj.get(nextKey);
646                                         if (obj instanceof JSONObject) {
647                                                 keyValue = getJsonBooleanValueForKey((JSONObject) obj, key);
648                                                 if (keyValue != null) {
649                                                         break;
650                                                 }
651                                         } else {
652                                                 logger.debug("getJsonBooleanValueForKey(): key={}, does not point to a JSONObject, next key", nextKey);
653                                         }
654                                 }
655                         }
656                 } catch (Exception e) {
657                         // JSONObject::get() throws a "not found" exception if one of the specified keys is not found
658                         if (e.getMessage().contains("not found")) {
659                                 logger.debug("getJsonBooleanValueForKey(): failed to retrieve param value for key={}: {}", key, e.getMessage());
660                         } else {
661                                 logger.debug("getJsonBooleanValueForKey(): unable to parse json to retrieve value for field={}. Exception was: {}",
662                                         key, e.toString(), e);
663                         }
664                         keyValue = null;
665                 }
666                 return keyValue;
667         }
668
669         /**
670          * Boolean method to determine if a key path is valid for the JSON doc. Invokes
671          * getJsonValue().
672          *
673          * @param  jsonStr      String containing the JSON doc
674          * @param  keys         full key path to the target value in the format of "key1.key2.key3..."
675          * @return Boolean true if keys points to value in the JSON doc
676          */
677         public static Boolean jsonValueExists(String jsonStr, String keys) {
678                 if (getJsonRawValue(jsonStr, keys) == null) {
679                         return false;
680                 } else {
681                         return true;
682                 }
683         }
684
685         /**
686          * Inserts the new key/value pair at the appropriate location in the JSON
687          * document after first determining if keyed field already exists. If
688          * it does exist, return the JSON unmodified, otherwise return the new JSON
689          * Note: this method currently only supports String value inserts.
690          *
691          * @param  jsonStr      String containing the JSON doc
692          * @param  keys         full key path to the value to be added in the format of "key1.key2.key3..."
693          * @return String containing the updated JSON doc
694          */
695         public static String addJsonValue(String jsonStr, String keys, String value) {
696
697                 // only attempt to insert the key/value pair if it does not exist
698                 if (!jsonValueExists(jsonStr, keys)) {
699                         return putJsonValue(jsonStr, keys, value);
700                 } else {
701                         logger.debug("addJsonValue(): JSON add failed, key={}/value={} already exists", keys, value);
702                         return jsonStr;
703                 }
704         }
705
706         /**
707          * Updates the value for the specified key in the JSON document
708          * after first determining if keyed field exists. If it does
709          * not exist, return the JSON unmodified, otherwise return the updated JSON.
710          * Note: this method currently only supports String value updates.
711          *
712          * @param  jsonStr      String containing the JSON doc
713          * @param  keys         full key path to the value to be updated in the format of "key1.key2.key3..."
714          * @return String containing the updated JSON doc
715          */
716         public static String updJsonValue(String jsonStr, String keys, String newValue) {
717                 // only attempt to modify the key/value pair if it exists
718                 if (jsonValueExists(jsonStr, keys)) {
719                         return putJsonValue(jsonStr, keys, newValue);
720                 } else {
721                         logger.debug("updJsonValue(): JSON update failed, no value exists for key={}", keys);
722                         return jsonStr;
723                 }
724         }
725
726         /**
727          * Deletes the value for the specified key in the JSON document
728          * after first determining if keyed field exists. If it does
729          * not exist, return the JSON unmodified, otherwise return the updated JSON
730          *
731          * @param  jsonStr      String containing the JSON doc
732          * @param  keys         full key path to the value to be deleted in the format of "key1.key2.key3..."
733          * @return String containing the updated JSON doc
734          */
735         public static String delJsonValue(String jsonStr, String keys) {
736
737                 // only attempt to remove the key/value pair if it exists
738                 if (jsonValueExists(jsonStr, keys)) {
739                         // passing a null value results in a delete
740                         return putJsonValue(jsonStr, keys, null);
741                 } else {
742                         logger.debug("delJsonValue(): JSON delete failed, no value exists for key={}", keys);
743                         return jsonStr;
744                 }
745         }
746
747         /**
748          * Walks the JSON doc using the full key path to retrieve the associated
749          * value. All but the last key points to the 'parent' object name(s) in order
750          * in the JSON hierarchy with the last key pointing to the target value.
751          * The value returned is a Java object.
752          *
753          * @param  jsonStr      String containing the JSON doc
754          * @param  keys         full key path to the target value in the format of "key1.key2.key3..."
755          * @return Object field value associated with keys
756          */
757         private static Object getJsonRawValue(String jsonStr, String keys) {
758                 return getJsonRawValue(jsonStr, keys, false);
759         }
760
761         /**
762          * Walks the JSON doc using the full key path to retrieve the associated
763          * value. All but the last key points to the 'parent' object name(s) in order
764          * in the JSON hierarchy with the last key pointing to the target value.
765          * The value returned is a Java object.
766          *
767          * @param  jsonStr      String containing the JSON doc
768          * @param  keys         full key path to the target value in the format of "key1.key2.key3..."
769          * @param  wrap         Boolean which determines if returned JSONObjects sould be "wrapped"
770          *                  Note: wrap does not apply to returned scalar values
771          * @return Object field value associated with keys
772          */
773         private static Object getJsonRawValue(String jsonStr, String keys, Boolean wrap) {
774
775                 String keyStr = "";
776                 try {
777                         JSONObject jsonObj = new JSONObject(jsonStr);
778                         StringTokenizer keyTokens = new StringTokenizer(keys, ".");
779                         while (keyTokens.hasMoreElements()) {
780                                 keyStr = keyTokens.nextToken();
781                                 Object keyValue = jsonObj.get(keyStr);
782                                 if (keyValue instanceof JSONObject) {
783                                         jsonObj = (JSONObject) keyValue;
784                                 } else {
785                                         if (keyTokens.hasMoreElements()) {
786                                                 logger.debug("getJsonRawValue(): value found prior to last key for key={}", keyStr);
787                                         }
788                                         return keyValue;
789                                 }
790                         }
791                         // return the json 'node' that the key points to
792                         // note: since this is a json object and not a scalar value,
793                         //       use the wrap flag to determine if the object should
794                         //       be wrapped with a root node value
795                         //       (the last key in the keys String)
796                         if (wrap) {
797                                 JSONObject wrappedJsonObj = new JSONObject();
798                                 wrappedJsonObj.put(keyStr, jsonObj);
799                                 return wrappedJsonObj.toString();
800                         } else {
801                                 return jsonObj.toString();
802                         }
803
804                 } catch (Exception e) {
805                         // JSONObject::get() throws a "not found" exception if one of the specified keys is not found
806                         if (e.getMessage().contains("not found")) {
807                                 logger.debug("getJsonRawValue(): failed to retrieve param value for key={}: {}", keyStr, e.getMessage());
808                         } else {
809                                 logger.debug("getJsonRawValue(): unable to parse json to retrieve value for field={}. Exception was: {}", keys,
810                                         e.toString(), e);
811                         }
812                 }
813                 return null;
814         }
815
816         /**
817          * Private method invoked by the public add, update, and delete methods.
818          *
819          * @param  jsonStr      String containing the JSON doc
820          * @param  keys         full key path to the value to be deleted in the format of "key1.key2.key3..."
821          * @return String containing the updated JSON doc
822          */
823         private static String putJsonValue(String jsonStr, String keys, String value) {
824
825                 String keyStr = "";
826                 try {
827                         JSONObject jsonObj = new JSONObject(jsonStr);
828                         JSONObject jsonObjOut = jsonObj;
829                         StringTokenizer keyTokens = new StringTokenizer(keys, ".");
830                         while (keyTokens.hasMoreElements()) {
831                                 keyStr = keyTokens.nextToken();
832                                 if (keyTokens.hasMoreElements()) {
833                                         Object keyValue = jsonObj.get(keyStr);
834                                         if (keyValue instanceof JSONObject) {
835                                                 jsonObj = (JSONObject) keyValue;
836                                         } else {
837                                                 logger.debug("putJsonValue(): key={} not the last key but points to non-json object: {}", keyStr, keyValue);
838                                                 return null;
839                                         }
840                                 } else { // at the last/new key value
841                                         jsonObj.put(keyStr, value);
842                                         return jsonObjOut.toString(3);
843                                 }
844                         }
845                         // should not hit this point if the key points to a valid key value
846                         return null;
847
848                 } catch (Exception e) {
849                         // JSONObject::get() throws a "not found" exception if one of the specified keys is not found
850                         if (e.getMessage().contains("not found")) {
851                                 logger.debug("putJsonValue(): failed to put param value for key={}: {}", keyStr, e.getMessage());
852                         } else {
853                                 logger.debug("putJsonValue(): unable to parse json to put value for key={}. Exception was: {}", keys, e.toString(),
854                                                 e);
855                         }
856                 }
857                 return null;
858         }
859
860         /**
861          * This json util method converts a json array of Key Value
862          * pair objects into a Java Map.
863          *
864          * @param execution
865          * @param entryArray - the getJsonValue of a json Array of key/value pairs
866          *
867          * @return Map - a Map containing the entries
868          */
869         public Map<String, String> jsonStringToMap(DelegateExecution execution, String entry) {
870                 logger.debug("Started Json String To Map Method");
871
872                 Map<String, String> map = new HashMap<>();
873
874                 //Populate Map
875                 JSONObject obj = new JSONObject(entry);
876
877                 /* Wildfly is pushing a version of org.json which does not
878                  * auto cast to string. Leaving it as an object prevents
879                  * a method not found exception at runtime.
880                  */
881                 final Iterator<String> keys = obj.keys();
882                 while (keys.hasNext()) {
883                         final String key = keys.next();
884                         map.put(key, obj.getString(key));
885                 }
886                 logger.debug("Outgoing Map is: {}", map);
887                 logger.debug("Completed Json String To Map Method");
888                 return map;
889         }
890
891         /**
892          * This json util method converts a json array of Key Value
893          * pair objects into a Java Map.
894          *
895          * @param execution
896          * @param entryArray - the getJsonValue of a json Array of key/value pairs
897          * @param keyNode - the name of the node that represents the key
898          * @param valueNode - the name of the node that represents the value
899          * @return Map - a Map containing the entries
900          *
901          */
902         public Map<String, String> entryArrayToMap(DelegateExecution execution, String entryArray, String keyNode, String valueNode) {
903                 logger.debug("Started Entry Array To Map Util Method");
904
905                 Map<String, String> map = new HashMap<>();
906                 //Populate Map
907                 String entryListJson = "{ \"wrapper\":" + entryArray + "}";
908                 JSONObject obj = new JSONObject(entryListJson);
909                 JSONArray arr = obj.getJSONArray("wrapper");
910                 for (int i = 0; i < arr.length(); i++){
911                         JSONObject jo = arr.getJSONObject(i);
912                         String key = jo.getString(keyNode);
913                         String value = jo.get(valueNode).toString();
914                         map.put(key, value);
915                 }
916                 logger.debug("Completed Entry Array To Map Util Method");
917                 return map;
918         }
919
920         /**
921          * This json util method converts a json array of Key Value pair objects into a Java Map.
922          *
923          * @param entryArray - the json Array of key/value pairs objects
924          * @param keyNode - the name of the node that represents the key
925          * @param valueNode - the name of the node that represents the value
926          * @return Map - a Map containing the entries
927          * @author cb645j
928          *
929          */
930         public Map<String, String> entryArrayToMap(String entryArray, String keyNode, String valueNode){
931                 logger.debug("Started Entry Array To Map Util Method");
932
933                 Map<String, String> map = new HashMap<>();
934                 String entryListJson = "{ \"wrapper\":" + entryArray + "}";
935                 JSONObject obj = new JSONObject(entryListJson); // TODO just put in json array
936                 JSONArray arr = obj.getJSONArray("wrapper");
937                 for(int i = 0; i < arr.length(); i++){
938                         JSONObject jo = arr.getJSONObject(i);
939                         String key = jo.getString(keyNode);
940                         String value = jo.get(valueNode).toString();
941                         map.put(key, value);
942                 }
943                 logger.debug("Completed Entry Array To Map Util Method");
944                 return map;
945         }
946
947         /**
948          * This json util method converts a json Array of Strings to a Java List. It takes each
949          * String in the json Array and puts it in a Java List<String>.
950          *
951          * @param execution
952          * @param jsonArray - string value of a json array
953          * @return List - a java list containing the strings
954          *
955          * @author cb645j
956          */
957         public List<String> StringArrayToList(Execution execution, String jsonArray){
958                 logger.debug("Started  String Array To List Util Method");
959
960                 List<String> list = new ArrayList<>();
961                 // Populate List
962                 // TODO
963                 String stringListJson = "{ \"strings\":" + jsonArray + "}";
964                 JSONObject obj = new JSONObject(stringListJson);
965                 JSONArray arr = obj.getJSONArray("strings");
966                 for(int i = 0; i < arr.length(); i++){
967                         String s = arr.get(i).toString();
968                         list.add(s);
969                 }
970                 logger.debug("Outgoing List is: {}", list);
971                 logger.debug("Completed String Array To List Util Method");
972                 return list;
973         }
974
975         /**
976          * This json util method converts a json Array of Strings to a Java List. It takes each
977          * String in the json Array and puts it in a Java List<String>.
978          *
979          * @param jsonArray - string value of a json array
980          * @return List - a java list containing the strings
981          *
982          * @author cb645j
983          */
984         public List<String> StringArrayToList(String jsonArray){
985                 logger.debug("Started Json Util String Array To List");
986                 List<String> list = new ArrayList<>();
987
988                 JSONArray arr = new JSONArray(jsonArray);
989                 for(int i = 0; i < arr.length(); i++){
990                         String s = arr.get(i).toString();
991                         list.add(s);
992                 }
993                 logger.debug("Completed Json Util String Array To List");
994                 return list;
995         }
996
997         /**
998          * This json util method converts a json Array of Strings to a Java List. It takes each
999          * String in the json Array and puts it in a Java List<String>.
1000          *
1001          * @param jsonArray - json array
1002          * @return List - a java list containing the strings
1003          *
1004          * @author cb645j
1005          */
1006         public List<String> StringArrayToList(JSONArray jsonArray){
1007                 logger.debug("Started Json Util String Array To List");
1008                 List<String> list = new ArrayList<>();
1009
1010                 for(int i = 0; i < jsonArray.length(); i++){
1011                         String s = jsonArray.get(i).toString();
1012                         list.add(s);
1013                 }
1014                 logger.debug("Completed Json Util String Array To List");
1015                 return list;
1016         }
1017
1018         /**
1019          *
1020          * Invokes the getJsonRawValue() method to determine if the json element/variable exist.
1021          * Returns true if the json element exist
1022          *
1023          * @param jsonStr - String containing the JSON doc
1024          * @param keys - full key path to the target value in the format of "key1.key2.key3..."
1025          * @return boolean field value associated with keys
1026          *
1027          */
1028         public static boolean jsonElementExist(String jsonStr, String keys){
1029
1030                 try{
1031                         Object rawValue = getJsonRawValue(jsonStr, keys);
1032
1033                         return !(rawValue == null);
1034
1035                 } catch(Exception e){
1036                         logger.debug("jsonElementExist(): unable to determine if json element exist. Exception is: {}", e.toString(), e);
1037                 }
1038                 return true;
1039         }
1040
1041         /**
1042          *
1043          * Validates the JSON document against a schema file.
1044          *
1045          * @param  jsonStr      String containing the JSON doc
1046          * @param  jsonSchemaPath full path to a valid JSON schema file
1047          *
1048          */
1049     public static String jsonSchemaValidation(String jsonStr, String jsonSchemaPath) throws ValidationException {
1050         try {
1051                 logger.debug("JSON document to be validated: {}", jsonStr);
1052                 JsonNode document = JsonLoader.fromString(jsonStr);
1053                 JsonNode schema = JsonLoader.fromPath(jsonSchemaPath);
1054
1055                 JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
1056                 JsonValidator validator = factory.getValidator();
1057
1058                 ProcessingReport report = validator.validate(schema, document);
1059                 logger.debug("JSON schema validation report: {}", report.toString());
1060                 return report.toString();
1061         } catch (IOException e) {
1062                 logger.debug("IOException performing JSON schema validation on document: {}", e.toString());
1063                 throw new ValidationException(e.getMessage());
1064         } catch (ProcessingException e) {
1065                 logger.debug("ProcessingException performing JSON schema validation on document: {}", e.toString());
1066                 throw new ValidationException(e.getMessage());
1067         }
1068     }
1069 }