1 package org.onap.sdc.dcae.checker;
3 import java.io.IOException;
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;
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;
28 import jdk.nashorn.api.scripting.JSObject;
29 import jdk.nashorn.api.scripting.AbstractJSObject;
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;
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).
42 public class JSP implements Processor<JSP> {
44 private ScriptEngine engine;
45 private Collection<? extends Target> targets;
47 public JSP(String[] theScripts) {
48 this(Arrays.stream(theScripts)
49 .map(s -> new Target(s, new File(s).toURI()))
50 .collect(Collectors.toList()));
53 public JSP(File[] theScripts) {
54 this(Arrays.stream(theScripts)
55 .map(s -> new Target(s.getName(), s.toURI()))
56 .collect(Collectors.toList()));
59 public JSP(URI[] theScripts) {
60 this(Arrays.stream(theScripts)
61 .map(s -> new Target(s.toString(), s))
62 .collect(Collectors.toList()));
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.
70 public JSP(Collection<? extends Target> theTargets) {
71 this.targets = theTargets;
72 ScriptEngineManager engineManager = new ScriptEngineManager();
73 this.engine = engineManager.getEngineByName("nashorn");
76 public Collection<? extends Target> targets() {
80 /* pre-compiles all known targets
82 protected void compile() throws ProcessorException {
83 synchronized (this.targets) {
84 for (Target t: this.targets)
89 protected CompiledScript compile(Target theTarget) throws ProcessorException {
91 CompiledScript cs = null;
93 synchronized(theTarget) {
95 cs = (CompiledScript)theTarget.getTarget();
97 catch(ClassCastException ccx) {
98 throw new ProcessorException(theTarget, "Unexpected target content");
103 cs = ((Compilable)this.engine).compile(theTarget.open());
104 theTarget.setTarget(cs);
106 catch (IOException iox) {
107 throw new ProcessorException(theTarget, "Failed to read script", iox);
109 catch (ScriptException sx) {
110 throw new ProcessorException(theTarget, "Failed to compile script", sx);
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));
127 public class ContextBuilder implements ProcessBuilder<JSP> {
129 private ScriptContext context;
131 protected ContextBuilder(Bindings theBindings) {
132 this.context = new SimpleScriptContext();
133 this.context.setBindings(theBindings, Process.PROCESS_SCOPE /*ScriptContext.ENGINE_SCOPE*/);
136 public ContextBuilder withPreprocessing(BiFunction<Target, ScriptContext, Boolean> thePreprocessing) {
137 this.context.setAttribute("preprocessor", thePreprocessing, Process.PROCESS_SCOPE);
141 public ContextBuilder withPostprocessing(BiFunction<Target, ScriptContext, Boolean> thePostprocessing) {
142 this.context.setAttribute("postprocessor", thePostprocessing, Process.PROCESS_SCOPE);
146 public ContextBuilder with(String theName, Object theValue) {
147 this.context.getBindings(Process.PROCESS_SCOPE).put(theName, theValue);
151 public ContextBuilder withOpt(String theName, Object theValue) {
152 if (theValue != null)
153 this.context.getBindings(Process.PROCESS_SCOPE).put(theName, theValue);
157 public JSProcess process() {
158 return new JSProcess(this.context);
165 public class JSProcess implements Process<JSP> {
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;
174 private JSProcess(ScriptContext theContext) {
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;
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));
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));
196 this.scripts = JSP.this.targets.iterator();
199 protected String infoName(Target theTarget) {
200 String name = theTarget.getName();
201 return name.substring(0, name.indexOf(".")) + "_info";
204 public JSP processor() {
208 public boolean hasNext() {
209 return !this.stopped && this.scripts.hasNext();
212 protected Target next() {
214 return this.script = this.scripts.next();
216 throw new RuntimeException("Process is completed");
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);
224 return proc.apply(this.script, this.context).booleanValue();
226 catch (Exception x) {
227 throw new ProcessorException(this.script, theName + "failed", x);
234 public Process runNext() throws ProcessorException {
235 Target target = next();
236 synchronized(target) {
237 String name = infoName(target);
239 if (runProcessor("preprocessor")) {
240 compile(target).eval(this.context);
241 runProcessor("postprocessor");
244 catch (ScriptException sx) {
245 throw new ProcessorException(target, "Failed to execute validation script", sx);
252 public Process runNextSilently() {
256 catch (ProcessorException px) {
262 public Report run() {
272 public Report report() {
277 private static class JScriptInfo implements TargetInfo {
279 private JSObject info;
281 protected JScriptInfo() {
284 protected JScriptInfo setInfo(JSObject theInfo) {
289 public Set<String> entryNames() {
290 return this.info == null ? Collections.EMPTY_SET : this.info.keySet();
293 public boolean hasEntry(String theName) {
294 return this.info == null ? false : this.info.hasMember(theName);
297 public Object getEntry(String theName) {
298 return this.info == null ? null :
299 this.info.hasMember(theName) ? this.info.getMember(theName) : null;
304 /* Exposes the catalog information in a more Java Script friendly manner.
306 public static class JSCatalog {
308 private Catalog catalog;
310 private JSCatalog(Catalog theCatalog) {
311 this.catalog = theCatalog;
315 public JSTarget[] targets() {
317 this.catalog.targets()
319 .map(t -> { return new JSTarget(t); })
320 .toArray(size -> new JSTarget[size]); //or toArray(JSNode[]::new)
323 public JSTarget[] topTargets() {
325 this.catalog.topTargets()
327 .map(t -> { return new JSTarget(t); })
328 .toArray(size -> new JSTarget[size]); //or toArray(JSNode[]::new)
332 public String[] types(String theConstruct) {
334 this.catalog.getConstructTypes(Enum.valueOf(Construct.class,theConstruct)).keySet();
335 return names.toArray(new String[names.size()]);
339 public boolean isDerivedFrom(String theConstruct, String theType, String theSuperType) {
340 return this.catalog.isDerivedFrom(Enum.valueOf(Construct.class,theConstruct), theType, theSuperType);
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));
354 public JSElement[] targetNodes(Target theTarget) {
356 this.catalog.getTargetTemplates(theTarget, Construct.Node)
359 .map(e -> { return new JSElement(e.getKey(),e.getValue()); })
360 .toArray(size -> new JSElement[size]); //or toArray(JSNode[]::new)
364 public class JSTarget {
367 private JXPathContext jxPath;
369 private JSTarget(Target theTarget) {
370 this.tgt = theTarget;
371 this.jxPath = JXPathContext.newContext(this.tgt.getTarget());
372 this.jxPath.setLenient(true);
375 public String getName() { return this.tgt.getName(); }
377 public JSElement resolve(String thePath) {
378 Object res = jxPath.getValue(thePath);
379 if (res instanceof Map) {
380 return new JSElement(thePath, (Map)res);
386 public JSElement[] getInputs() {
388 Map<String,Map> inputs = (Map<String,Map>)jxPath.getValue("/topology_template/inputs");
389 return (inputs == null) ?
393 .map(e -> { return new JSElement(e.getKey(),e.getValue()); })
394 .toArray(size -> new JSElement[size]);
397 // public JSElement[] getOutputs() {
400 public JSElement getMetadata() {
401 return new JSElement("metadata", (Map)jxPath.getValue("/metadata"));
404 public JSElement[] getNodes() {
406 JSCatalog.this.catalog.getTargetTemplates(this.tgt, Construct.Node)
409 .map(e -> { return new JSElement(e.getKey(),e.getValue()); })
410 .toArray(size -> new JSElement[size]); //or toArray(JSElement[]::new)
413 // public JSElement[] getPolicies() {
421 public class JSElement extends AbstractJSObject {
427 private JSElement(String theName, Object theDef) {
429 this.def = theDef == null ? Collections.emptyMap()
430 : (theDef instanceof Map) ? (Map)theDef
431 : Collections.singletonMap("value",theDef);
434 public String getName() { return this.name; }
436 public boolean hasMember(String theMember) {
437 return this.def.containsKey(theMember);
440 public Object getMember(final String theMember) {
441 Object val = this.def.get(theMember);
443 if (val instanceof Map) {
444 return new JSElement(theMember, val);
446 return ((Map<String,?>)obj).entrySet()
448 .map((Map.Entry<String,?> e) -> { return new JSElement(e.getKey(),e.getValue()); })
449 .toArray(size -> new JSElement[size]);
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) {
460 .map((e) -> new JSElement(theMember, e))
461 .toArray(size -> new JSElement[size]);
467 Map.Entry<String,?> re = ((Map<String,?>)e).entrySet().iterator().next();
468 return new JSElement(re.getKey(), re.getValue());
470 .toArray(size -> new JSElement[size]);
478 if ("name".equals(theMember))
480 if ("toString".equals(theMember))
482 if ("hasOwnProperty".equals(theMember))
483 return _hasOwnProperty;
484 return super.getMember(theMember);
487 /* TODO: we do not expose 'name' in here */
488 public Set<String> keySet() {
489 return this.def.keySet();
495 static final JSObject _toString =
496 new TracerJSObject("_toString") {
497 public Object call(Object thiz, Object... args) {
498 return ((JSElement)thiz).def.toString();
501 public boolean isFunction() { return true; }
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]);
510 public boolean isFunction() { return true; }
517 private static class TracerJSObject extends AbstractJSObject {
519 private static OnapLoggerError errLogger = OnapLoggerError.getInstance();
520 private static OnapLoggerDebug debugLogger = OnapLoggerDebug.getInstance();
524 TracerJSObject(String theMark) {
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);
533 public Object newObject(Object... args) {
534 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:newObject", this.mark);
535 return super.newObject(args);
538 public Object eval(String s) {
539 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:eval", this.mark);
540 return super.eval(s);
543 public Object getMember(String name) {
544 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:getMember", this.mark);
545 return super.getMember(name);
548 public Object getSlot(int index) {
549 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:getSlot", this.mark);
550 return super.getSlot(index);
553 public boolean hasMember(String name) {
554 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:hasMember", this.mark);
555 return super.hasMember(name);
558 public boolean hasSlot(int slot) {
559 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:hasSlot", this.mark);
560 return super.hasSlot(slot);
563 public void removeMember(String name) {
564 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:removeMember", this.mark);
565 super.removeMember(name);
568 public void setMember(String name, Object value) {
569 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:setMember", this.mark);
570 super.setMember(name,value);
573 public void setSlot(int index, Object value) {
574 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:setSlot", this.mark);
575 super.setSlot(index,value);
578 public Set<String> keySet() {
579 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:JSObject:keySet", this.mark);
580 return super.keySet();
583 public Collection<Object> values() {
584 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:values", this.mark);
585 return super.values();
588 public boolean isInstance(Object instance) {
589 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:isInstance", this.mark);
590 return super.isInstance(instance);
593 public boolean isInstanceOf(Object clazz) {
594 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:isInstanceOf", this.mark);
595 return super.isInstance(clazz);
598 public String getClassName() {
599 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:getClassName", this.mark);
600 return super.getClassName();
603 public boolean isFunction() {
604 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:isFunction", this.mark);
605 return super.isFunction();
608 public boolean isStrictFunction() {
609 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:isStrictFunction", this.mark);
610 return super.isStrictFunction();
613 public boolean isArray() {
614 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:isArray", this.mark);
615 return super.isArray();
618 public Object getDefaultValue(Class<?> hint) {
619 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "{}:getDefaultValue({})", this.mark, hint);
620 return super.getDefaultValue(hint);