DCAE-D be initial commit
[sdc/dcae-d/dt-be-main.git] / dcaedt_validator / checker / src / main / java / org / onap / sdc / dcae / checker / JSP.java
1 package org.onap.sdc.dcae.checker;
2
3 import java.io.IOException;
4 import java.io.File;
5
6 import java.net.URI;
7
8 import java.util.Set;
9 import java.util.Map;
10 import java.util.List;
11 import java.util.Arrays;
12 import java.util.Iterator;
13 import java.util.Collection;
14 import java.util.Collections;
15 import java.util.stream.Collectors;
16 import java.util.function.Consumer;
17 import java.util.function.BiFunction;
18
19 import javax.script.Compilable;
20 import javax.script.CompiledScript;
21 import javax.script.Bindings;
22 import javax.script.ScriptContext;
23 import javax.script.SimpleScriptContext;
24 import javax.script.ScriptEngine;
25 import javax.script.ScriptEngineManager;
26 import javax.script.ScriptException;
27
28 import jdk.nashorn.api.scripting.JSObject;
29 import jdk.nashorn.api.scripting.AbstractJSObject;
30
31 import org.apache.commons.jxpath.JXPathContext;
32 import org.onap.sdc.common.onaplog.OnapLoggerDebug;
33 import org.onap.sdc.common.onaplog.OnapLoggerError;
34 import org.onap.sdc.common.onaplog.Enums.LogLevel;
35
36
37 /**
38  * Java Script Processor
39  * Each script is represented by a Target and the JSP processor maintains a collection of Targets, i.e. scripts.
40  * A collection of targets can be used with only one JSP processor at a time (as the processor stores processor specific * compiled versions within the target). 
41  */
42 public class JSP implements Processor<JSP> {
43
44   private ScriptEngine engine;
45         private Collection<? extends Target> targets;
46
47   public JSP(String[] theScripts) {
48                 this(Arrays.stream(theScripts)
49                                                                 .map(s -> new Target(s, new File(s).toURI()))
50                                                                 .collect(Collectors.toList()));
51         }
52   
53         public JSP(File[] theScripts) {
54                 this(Arrays.stream(theScripts)
55                                                                 .map(s -> new Target(s.getName(), s.toURI()))
56                                                                 .collect(Collectors.toList()));
57         }
58         
59         public JSP(URI[] theScripts) {
60                 this(Arrays.stream(theScripts)
61                                                                 .map(s -> new Target(s.toString(), s))
62                                                                 .collect(Collectors.toList()));
63         }
64
65         /**
66    * The given collection is allowed to change while used by the JSP engine but access to it needs to be synchronized.
67    * The engine uses the target field of each Target to store a compiled version of each script. An external reset of
68    * this field (maybe in order to indicate some change in the Target) will caue a re-compilation of the Target.
69    */
70         public JSP(Collection<? extends Target> theTargets) {
71                 this.targets = theTargets;
72     ScriptEngineManager engineManager = new ScriptEngineManager();
73     this.engine = engineManager.getEngineByName("nashorn");
74         }
75
76         public Collection<? extends Target> targets() {
77                 return this.targets;
78         }
79
80         /* pre-compiles all known targets
81    */
82         protected void compile() throws ProcessorException {
83                 synchronized (this.targets) {
84                         for (Target t: this.targets)
85                                 compile(t);
86                 }
87         }
88
89         protected CompiledScript compile(Target theTarget) throws ProcessorException {
90
91                 CompiledScript cs = null;
92
93                 synchronized(theTarget) {               
94                         try {
95                                 cs = (CompiledScript)theTarget.getTarget();
96                         }
97                         catch(ClassCastException ccx) {
98                                 throw new ProcessorException(theTarget, "Unexpected target content");
99                         }
100
101                         if (cs == null) {
102                                 try {
103                                         cs = ((Compilable)this.engine).compile(theTarget.open());
104                                         theTarget.setTarget(cs);
105                                 }
106                 catch (IOException iox) {
107                                         throw new ProcessorException(theTarget, "Failed to read script", iox);
108                                 }
109                                 catch (ScriptException sx) {
110                                         throw new ProcessorException(theTarget, "Failed to compile script", sx);
111                                 }
112                         }
113                 }
114
115                 return cs;
116         }
117
118         public ContextBuilder process(Catalog theCatalog) {
119                 return new ContextBuilder(
120                                                                                         this.engine.createBindings())
121                                                                                         //new DelegateBindings(this.engine.getBindings(ScriptContext.ENGINE_SCOPE)))
122                                                                 .with("catalog", new JSCatalog(theCatalog));
123         }
124
125         /**
126    */
127         public class ContextBuilder implements ProcessBuilder<JSP> { 
128
129                 private ScriptContext           context;
130
131                 protected ContextBuilder(Bindings theBindings) {
132                         this.context = new SimpleScriptContext();
133                         this.context.setBindings(theBindings, Process.PROCESS_SCOPE /*ScriptContext.ENGINE_SCOPE*/);
134                 }
135
136                 public ContextBuilder withPreprocessing(BiFunction<Target, ScriptContext, Boolean> thePreprocessing) {
137                         this.context.setAttribute("preprocessor", thePreprocessing, Process.PROCESS_SCOPE);
138                         return this;
139                 }
140
141                 public ContextBuilder withPostprocessing(BiFunction<Target, ScriptContext, Boolean> thePostprocessing) {
142                         this.context.setAttribute("postprocessor", thePostprocessing, Process.PROCESS_SCOPE);
143                         return this;
144                 }
145
146                 public ContextBuilder with(String theName, Object theValue) {
147                         this.context.getBindings(Process.PROCESS_SCOPE).put(theName, theValue);
148                         return this;
149                 }
150                 
151                 public ContextBuilder withOpt(String theName, Object theValue) {
152                         if (theValue != null)
153                                 this.context.getBindings(Process.PROCESS_SCOPE).put(theName, theValue);
154                         return this;
155                 }
156
157                 public JSProcess process() {
158                         return new JSProcess(this.context);
159                 }
160
161         }
162
163   /**
164    */
165   public class JSProcess implements Process<JSP> {
166
167                 private Report                                                                                  report = new Report();
168                 private Iterator<? extends Target>      scripts;
169                 private JScriptInfo                                                                     scriptInfo = new JScriptInfo();
170                 private Target                                                                                  script; //script currently being evaluated
171                 private boolean                                                                                 stopped = false;
172                 private ScriptContext                                                           context;
173
174                 private JSProcess(ScriptContext theContext) {
175
176                         this.context = theContext;
177                         this.context.getBindings(Process.PROCESS_SCOPE)
178                                                                                         .put("stop", new Consumer<String>() {
179                                                                                                                                                         public void accept(String theMsg) {
180                                                                                                                                                                 JSProcess.this.stopped = true;
181                                                                                                                                                                 //log the message??
182                                                                                                                                                         }
183                                                                                                                                                 });
184                         this.context.getBindings(Process.PROCESS_SCOPE)
185                                                                                         .put("report", new Consumer<String>() {
186                                                                                                                                                         public void accept(String theMsg) {
187                                                                                                                                                                 JSProcess.this.report.add(new ProcessorException(script, theMsg));
188                                                                                                                                                         }
189                                                                                                                                                 });
190                         this.context.getBindings(Process.PROCESS_SCOPE)
191                                                                                         .put("reportOnce", new Consumer<String>() {
192                                                                                                                                                         public void accept(String theMsg) {
193                                                                                                                                                                 JSProcess.this.report.addOnce(new ProcessorException(script, theMsg));
194                                                                                                                                                         }
195                                                                                                                                                 });
196                         this.scripts = JSP.this.targets.iterator();
197                 }
198
199                 protected String infoName(Target theTarget) {
200                         String name = theTarget.getName();
201                         return name.substring(0, name.indexOf(".")) + "_info";
202                 }
203
204                 public JSP processor() {
205                         return JSP.this;
206                 }
207
208                 public boolean hasNext() {
209                         return !this.stopped && this.scripts.hasNext();
210                 }
211
212                 protected Target next() {
213                         if (hasNext())
214                                 return this.script = this.scripts.next();
215                         else
216                                 throw new RuntimeException("Process is completed");
217                 }
218
219                 protected boolean runProcessor(String theName) throws ProcessorException {
220                         BiFunction<Target, ScriptContext, Boolean> proc = (BiFunction<Target, ScriptContext, Boolean>)
221                                                                                                                                                                                                                                                 this.context.getAttribute(theName, Process.PROCESS_SCOPE);
222                         if (proc != null) {
223                                 try {
224                                         return proc.apply(this.script, this.context).booleanValue();
225                                 }
226                                 catch (Exception x) {
227                                         throw new ProcessorException(this.script, theName + "failed", x);
228                                 }
229                         }
230
231                         return true;
232                 }
233
234                 public Process runNext() throws ProcessorException {
235                         Target target = next();
236                         synchronized(target) {
237                                 String name = infoName(target);
238                                 try {
239                                         if (runProcessor("preprocessor")) {
240                                                 compile(target).eval(this.context);
241                                                 runProcessor("postprocessor");
242                                         }
243                                 }
244                                 catch (ScriptException sx) {
245                                         throw new ProcessorException(target, "Failed to execute validation script", sx);
246                                 }
247                         }
248                         
249                         return this;
250                 }
251                 
252                 public Process runNextSilently() {
253                         try {
254                                 return runNext();
255                         }
256                         catch (ProcessorException px) {
257                                 this.report.add(px);
258                         }
259                         return this;
260                 }
261
262                 public Report run() {
263                         while (hasNext())
264                                 runNextSilently();
265                         return this.report;
266                 }
267
268                 public void stop() {
269                         this.stopped = true;
270                 }
271
272                 public Report report() {
273                         return this.report;
274                 }
275         }
276
277         private static class JScriptInfo implements TargetInfo {
278         
279                 private JSObject info;
280
281                 protected JScriptInfo() {
282                 }
283
284                 protected JScriptInfo setInfo(JSObject theInfo) {
285                         this.info = theInfo;
286                         return this;
287                 }
288
289                 public Set<String>      entryNames() {
290                         return this.info == null ? Collections.EMPTY_SET : this.info.keySet();
291                 }
292
293                 public boolean  hasEntry(String theName) {
294                         return this.info == null ? false : this.info.hasMember(theName);
295                 }
296
297                 public Object   getEntry(String theName) {
298                         return this.info == null ? null : 
299                                                                  this.info.hasMember(theName) ? this.info.getMember(theName) : null;
300                 }
301         }
302
303
304   /* Exposes the catalog information in a more Java Script friendly manner.
305    */
306   public static class JSCatalog {
307
308                 private Catalog catalog;
309
310                 private JSCatalog(Catalog theCatalog) {
311                         this.catalog = theCatalog;
312                 }
313                 
314                 /** */
315     public JSTarget[] targets() {
316                         return 
317                                 this.catalog.targets()
318                                         .stream()
319                                         .map(t -> { return new JSTarget(t); })
320                                         .toArray(size -> new JSTarget[size]); //or toArray(JSNode[]::new)
321     }
322     
323                 public JSTarget[] topTargets() {
324                         return 
325                                 this.catalog.topTargets()
326                                         .stream()
327                                         .map(t -> { return new JSTarget(t); })
328                                         .toArray(size -> new JSTarget[size]); //or toArray(JSNode[]::new)
329     }
330
331                 /** */
332     public String[] types(String theConstruct) {
333                         Set<String> names = 
334                                                         this.catalog.getConstructTypes(Enum.valueOf(Construct.class,theConstruct)).keySet();
335       return names.toArray(new String[names.size()]);
336     }
337
338                 /** */
339                 public boolean isDerivedFrom(String theConstruct, String theType, String theSuperType) {
340                         return this.catalog.isDerivedFrom(Enum.valueOf(Construct.class,theConstruct), theType, theSuperType);
341                 }
342
343                 /** */
344                 public JSObject facetDefinition(String theConstruct, String theType, String theFacet, String theName) {
345                         return new JSElement(theName,
346                                                                                                          this.catalog.getFacetDefinition(
347                                                                                                                         Enum.valueOf(Construct.class, theConstruct), theType, 
348                                                                                                                         Enum.valueOf(Facet.class, theFacet), theName));
349                 }
350
351
352                 /** */
353 /*
354     public JSElement[] targetNodes(Target theTarget) {
355                         return 
356                                 this.catalog.getTargetTemplates(theTarget, Construct.Node)
357                                         .entrySet()
358                                                 .stream()
359                                                 .map(e -> { return new JSElement(e.getKey(),e.getValue()); })
360                                                 .toArray(size -> new JSElement[size]); //or toArray(JSNode[]::new)
361                 }
362 */
363
364                 public class JSTarget {
365
366                         private Target                          tgt;
367                 private JXPathContext jxPath;
368
369                         private JSTarget(Target theTarget) {
370                                 this.tgt = theTarget;
371                                 this.jxPath = JXPathContext.newContext(this.tgt.getTarget());
372                                 this.jxPath.setLenient(true);
373                         }
374
375                         public String getName() { return this.tgt.getName(); }
376
377                         public JSElement resolve(String thePath) {
378                                 Object res = jxPath.getValue(thePath);
379                                 if (res instanceof Map) {
380                                         return new JSElement(thePath, (Map)res);
381                                 }
382                                 //??
383                                 return null;
384                         }
385
386                         public JSElement[] getInputs() {
387                                 
388                                 Map<String,Map> inputs = (Map<String,Map>)jxPath.getValue("/topology_template/inputs");
389                                 return (inputs == null) ? 
390                                                                                 new JSElement[0]
391                                                                         : inputs.entrySet()
392                                                                                         .stream()
393                                                                                         .map(e -> { return new JSElement(e.getKey(),e.getValue()); })
394                                                                                         .toArray(size -> new JSElement[size]);
395                         }
396
397 //                      public JSElement[] getOutputs() {
398 //                      }
399
400       public JSElement getMetadata() {
401                                 return new JSElement("metadata", (Map)jxPath.getValue("/metadata"));
402                         }
403
404                         public JSElement[] getNodes() {
405                                 return 
406                                         JSCatalog.this.catalog.getTargetTemplates(this.tgt, Construct.Node)
407                                                 .entrySet()
408                                                         .stream()
409                                                         .map(e -> { return new JSElement(e.getKey(),e.getValue()); })
410                                                         .toArray(size -> new JSElement[size]); //or toArray(JSElement[]::new)
411                         }
412
413 //                      public JSElement[] getPolicies() {
414 //                      }
415
416                 }
417
418
419                 /*
420                  */
421         public class JSElement extends AbstractJSObject {
422         
423
424                         private String  name;
425           private Map                   def;
426
427                         private JSElement(String theName, Object theDef) {
428                                 this.name = theName;
429           this.def = theDef == null ? Collections.emptyMap() 
430                                                                                                                                         : (theDef instanceof Map) ? (Map)theDef
431                                                                                                                                                                                                                                                 : Collections.singletonMap("value",theDef);
432                         }
433
434                         public String getName() { return this.name; }
435
436                         public boolean hasMember(String theMember) {
437                                 return this.def.containsKey(theMember); 
438                         }
439
440                         public Object getMember(final String theMember) {
441                                 Object val = this.def.get(theMember);
442                                 if (val != null) {
443                                         if (val instanceof Map) {
444                                                 return new JSElement(theMember, val);
445                                         /*
446                                                 return ((Map<String,?>)obj).entrySet()
447                                                                                         .stream()
448                                                                                         .map((Map.Entry<String,?> e) -> { return new JSElement(e.getKey(),e.getValue()); })
449                                                                                         .toArray(size -> new JSElement[size]);
450                                         */
451                                         }
452                                         
453                                         if (val instanceof List) {
454                                                 //a property value can be a list of: primitive types or maps (for a user defined type)
455                                                 //requirements are exposed as a list of maps
456                                                 List lval = (List)val;
457                                                 if (lval.get(0) instanceof Map) {
458                                                         return lval
459                                                                                         .stream()
460                                                                                         .map((e) -> new JSElement(theMember, e))
461                                                                                         .toArray(size -> new JSElement[size]);
462                                                         
463                                                 /*
464                                                         return val
465                                                                                         .stream()
466                                                                                         .map((e) -> {
467                                                                                                                                                 Map.Entry<String,?> re = ((Map<String,?>)e).entrySet().iterator().next();
468                                                                                                                                                 return new JSElement(re.getKey(), re.getValue());
469                                                                                                                                         })
470                                                                                         .toArray(size -> new JSElement[size]);
471                                                 */
472                                                 }
473                                         }
474                                         
475                                         return val;
476                                 }
477                                 else {
478                                         if ("name".equals(theMember))
479                                                 return this.name;
480                                         if ("toString".equals(theMember))
481                                                 return _toString;
482                                         if ("hasOwnProperty".equals(theMember))
483                                                 return _hasOwnProperty;
484                                         return super.getMember(theMember);
485                                 }
486                         }               
487                         /* TODO: we do not expose 'name' in here */
488                         public Set<String> keySet() {
489                                 return this.def.keySet(); 
490                         }
491
492                 }
493
494
495         static final JSObject _toString = 
496                                                 new TracerJSObject("_toString") {
497                                                         public Object call(Object thiz, Object... args) {
498                                                                 return ((JSElement)thiz).def.toString();
499                                                         }
500
501                                                         public boolean isFunction() { return true; }
502                                                 };
503
504         static final JSObject _hasOwnProperty = 
505                                                 new TracerJSObject("_hasOwnProperty") {
506                                                         public Object call(Object thiz, Object... args) {
507                                                                 return ((JSElement)thiz).def.containsKey(args[0]);
508                                                         }
509
510                                                         public boolean isFunction() { return true; }
511                                                 };      
512
513         }//JSCatalog
514
515
516
517   private static class TracerJSObject extends AbstractJSObject {
518
519           private static OnapLoggerError errLogger = OnapLoggerError.getInstance();
520           private static OnapLoggerDebug debugLogger = OnapLoggerDebug.getInstance();
521
522                 private String mark;
523         
524                 TracerJSObject(String theMark) {
525                         this.mark = theMark;
526                 }
527
528                 public Object call(Object thiz, Object... args) {
529                         debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:call", this.mark);
530                         return super.call(thiz, args);
531                 }
532
533                 public Object newObject(Object... args) {
534             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:newObject", this.mark);
535                         return super.newObject(args);
536                 }
537
538                 public Object eval(String s) {
539             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:eval", this.mark);
540                         return super.eval(s);
541                 }
542
543                 public Object getMember(String name) {
544             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:getMember", this.mark);
545                         return super.getMember(name);
546                 }
547
548                 public Object getSlot(int index) {
549             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:getSlot", this.mark);
550                         return super.getSlot(index);
551                 }
552
553                 public boolean hasMember(String name) {
554             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:hasMember", this.mark);
555                         return super.hasMember(name);
556                 }
557
558                 public boolean hasSlot(int slot) {
559             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:hasSlot", this.mark);
560                         return super.hasSlot(slot);
561                 }
562
563                 public void removeMember(String name) {
564             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:removeMember", this.mark);
565                         super.removeMember(name);
566                 }
567
568                 public void setMember(String name, Object value) {
569             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:setMember", this.mark);
570                         super.setMember(name,value);
571                 }
572
573                 public void setSlot(int index, Object value) {
574             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:setSlot", this.mark);
575                         super.setSlot(index,value);
576                 }
577
578                 public Set<String> keySet() {
579             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:JSObject:keySet", this.mark);
580                         return super.keySet();
581                 }
582
583                 public Collection<Object> values() {
584             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:values", this.mark);
585                         return super.values();
586                 }
587
588                 public boolean isInstance(Object instance) {
589             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:isInstance", this.mark);
590                         return super.isInstance(instance);
591                 }
592
593                 public boolean isInstanceOf(Object clazz) {
594             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:isInstanceOf", this.mark);
595                         return super.isInstance(clazz);
596                 }
597
598                 public String getClassName() {
599             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:getClassName", this.mark);
600                         return super.getClassName();
601                 }
602
603                 public boolean isFunction() {
604             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:isFunction", this.mark);
605                         return super.isFunction();
606     }
607
608                 public boolean isStrictFunction() {
609             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:isStrictFunction", this.mark);
610                         return super.isStrictFunction();
611                 }
612
613                 public boolean isArray() {
614             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:isArray", this.mark);
615                         return super.isArray();
616                 }
617
618                 public Object getDefaultValue(Class<?> hint) {
619             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:getDefaultValue({})", this.mark, hint);
620                         return super.getDefaultValue(hint);
621                 }
622         }
623
624 }