esdl_script.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925
  1. /*##############################################################################
  2. HPCC SYSTEMS software Copyright (C) 2020 HPCC Systems®.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ############################################################################## */
  13. #include "espcontext.hpp"
  14. #include "esdl_script.hpp"
  15. #include "wsexcept.hpp"
  16. interface IEsdlTransformOperation : public IInterface
  17. {
  18. virtual const char *queryMergedTarget() = 0;
  19. virtual bool process(IEspContext * context, IPropertyTree *content, IXpathContext * xpathContext) = 0;
  20. virtual void toDBGLog() = 0;
  21. };
  22. IEsdlTransformOperation *createEsdlTransformOperation(IPropertyTree *element, const StringBuffer &prefix);
  23. inline void esdlOperationError(int code, const char *op, const char *msg, const char *traceName, bool exception)
  24. {
  25. StringBuffer s("ESDL Script: ");
  26. if (!isEmptyString(traceName))
  27. s.append(" '").append(traceName).append("' ");
  28. if (!isEmptyString(op))
  29. s.append(" ").append(op).append(" ");
  30. s.append(msg);
  31. if(exception)
  32. throw MakeStringException(code, "%s", s.str());
  33. IERRLOG("%s", s.str());
  34. }
  35. inline void esdlOperationError(int code, const char *op, const char *msg, bool exception)
  36. {
  37. esdlOperationError(code, op, msg, "", exception);
  38. }
  39. static inline const char *checkSkipOpPrefix(const char *op, const StringBuffer &prefix)
  40. {
  41. if (prefix.length())
  42. {
  43. if (!hasPrefix(op, prefix, true))
  44. {
  45. DBGLOG(1,"Unrecognized script operation: %s", op);
  46. return nullptr;
  47. }
  48. return (op + prefix.length());
  49. }
  50. return op;
  51. }
  52. static inline StringBuffer &makeOperationTagName(StringBuffer &s, const StringBuffer &prefix, const char *op)
  53. {
  54. return s.append(prefix).append(op);
  55. }
  56. class CEsdlTransformOperationBase : public CInterfaceOf<IEsdlTransformOperation>
  57. {
  58. protected:
  59. StringAttr m_mergedTarget;
  60. StringAttr m_tagname;
  61. bool m_ignoreCodingErrors = false; //ideally used only for debugging
  62. public:
  63. CEsdlTransformOperationBase(IPropertyTree *tree, const StringBuffer &prefix)
  64. {
  65. m_tagname.set(tree->queryName());
  66. if (tree->hasProp("@_crtTarget"))
  67. m_mergedTarget.set(tree->queryProp("@_crtTarget"));
  68. m_ignoreCodingErrors = tree->getPropBool("@optional", false);
  69. }
  70. virtual const char *queryMergedTarget() override
  71. {
  72. return m_mergedTarget;
  73. }
  74. };
  75. class CEsdlTransformOperationVariable : public CEsdlTransformOperationBase
  76. {
  77. protected:
  78. StringAttr m_name;
  79. Owned<ICompiledXpath> m_select;
  80. public:
  81. CEsdlTransformOperationVariable(IPropertyTree *tree, const StringBuffer &prefix) : CEsdlTransformOperationBase(tree, prefix)
  82. {
  83. m_name.set(tree->queryProp("@name"));
  84. if (m_name.isEmpty())
  85. esdlOperationError(ESDL_SCRIPT_MissingOperationAttr, m_tagname, "without name", m_name, !m_ignoreCodingErrors);
  86. const char *select = tree->queryProp("@select");
  87. if (!isEmptyString(select))
  88. m_select.setown(compileXpath(select));
  89. }
  90. virtual ~CEsdlTransformOperationVariable()
  91. {
  92. }
  93. virtual bool process(IEspContext * context, IPropertyTree *content, IXpathContext * xpathContext) override
  94. {
  95. if (m_select)
  96. return xpathContext->addCompiledVariable(m_name, m_select);
  97. return xpathContext->addVariable(m_name, "");
  98. }
  99. virtual void toDBGLog() override
  100. {
  101. #if defined(_DEBUG)
  102. DBGLOG(">%s> %s with select(%s)", m_name.str(), m_tagname.str(), m_select.get() ? m_select->getXpath() : "");
  103. #endif
  104. }
  105. };
  106. class CEsdlTransformOperationParameter : public CEsdlTransformOperationVariable
  107. {
  108. public:
  109. CEsdlTransformOperationParameter(IPropertyTree *tree, const StringBuffer &prefix) : CEsdlTransformOperationVariable(tree, prefix)
  110. {
  111. }
  112. virtual ~CEsdlTransformOperationParameter()
  113. {
  114. }
  115. virtual bool process(IEspContext * context, IPropertyTree *content, IXpathContext * xpathContext) override
  116. {
  117. if (m_select)
  118. return xpathContext->declareCompiledParameter(m_name, m_select);
  119. return xpathContext->declareParameter(m_name, "");
  120. }
  121. };
  122. class CEsdlTransformOperationSetValue : public CEsdlTransformOperationBase
  123. {
  124. protected:
  125. Owned<ICompiledXpath> m_select;
  126. Owned<ICompiledXpath> m_xpath_target;
  127. StringAttr m_target;
  128. StringAttr m_traceName;
  129. public:
  130. CEsdlTransformOperationSetValue(IPropertyTree *tree, const StringBuffer &prefix) : CEsdlTransformOperationBase(tree, prefix)
  131. {
  132. m_traceName.set(tree->queryProp("@name"));
  133. if (isEmptyString(tree->queryProp("@target")) && isEmptyString(tree->queryProp("@xpath_target")))
  134. esdlOperationError(ESDL_SCRIPT_MissingOperationAttr, m_tagname.str(), "without target", m_traceName.str(), !m_ignoreCodingErrors);
  135. const char *select = tree->queryProp("@select");
  136. if (isEmptyString(select))
  137. select = tree->queryProp("@value");
  138. if (isEmptyString(select))
  139. esdlOperationError(ESDL_SCRIPT_MissingOperationAttr, m_tagname, "without select", m_traceName, !m_ignoreCodingErrors); //don't mention value, it's deprecated
  140. m_select.setown(compileXpath(select));
  141. if (tree->hasProp("@xpath_target"))
  142. m_xpath_target.setown(compileXpath(tree->queryProp("@xpath_target")));
  143. else if (tree->hasProp("@target"))
  144. m_target.set(tree->queryProp("@target"));
  145. }
  146. virtual void toDBGLog() override
  147. {
  148. #if defined(_DEBUG)
  149. DBGLOG(">%s> %s(%s, select('%s'))", m_traceName.str(), m_tagname.str(), m_target.str(), m_select->getXpath());
  150. #endif
  151. }
  152. virtual ~CEsdlTransformOperationSetValue(){}
  153. virtual bool process(IEspContext * context, IPropertyTree *content, IXpathContext * xpathContext) override
  154. {
  155. if ((!m_xpath_target && m_target.isEmpty()) || !m_select)
  156. return false; //only here if "optional" backward compatible support for now (optional syntax errors aren't actually helpful
  157. try
  158. {
  159. StringBuffer value;
  160. xpathContext->evaluateAsString(m_select, value);
  161. return doSet(xpathContext, content, value);
  162. }
  163. catch (IException* e)
  164. {
  165. int code = e->errorCode();
  166. StringBuffer msg;
  167. e->errorMessage(msg);
  168. e->Release();
  169. esdlOperationError(code, m_tagname, msg, m_traceName, !m_ignoreCodingErrors);
  170. }
  171. catch (...)
  172. {
  173. esdlOperationError(ESDL_SCRIPT_Error, m_tagname, "unknown exception processing", m_traceName, !m_ignoreCodingErrors);
  174. }
  175. return false;
  176. }
  177. const char *getTargetPath(IXpathContext * xpathContext, StringBuffer &s)
  178. {
  179. if (m_xpath_target)
  180. {
  181. xpathContext->evaluateAsString(m_xpath_target, s);
  182. return s;
  183. }
  184. return m_target.str();
  185. }
  186. virtual bool doSet(IXpathContext * xpathContext, IPropertyTree *tree, const char *value)
  187. {
  188. StringBuffer xpath;
  189. const char *target = getTargetPath(xpathContext, xpath);
  190. ensurePTree(tree, target);
  191. tree->setProp(target, value);
  192. return true;
  193. }
  194. };
  195. class CEsdlTransformOperationAppendValue : public CEsdlTransformOperationSetValue
  196. {
  197. public:
  198. CEsdlTransformOperationAppendValue(IPropertyTree *tree, const StringBuffer &prefix) : CEsdlTransformOperationSetValue(tree, prefix){}
  199. virtual ~CEsdlTransformOperationAppendValue(){}
  200. virtual bool doSet(IXpathContext * xpathContext, IPropertyTree *tree, const char *value)
  201. {
  202. StringBuffer xpath;
  203. const char *target = getTargetPath(xpathContext, xpath);
  204. ensurePTree(tree, target);
  205. tree->appendProp(target, value);
  206. return true;
  207. }
  208. };
  209. class CEsdlTransformOperationAddValue : public CEsdlTransformOperationSetValue
  210. {
  211. public:
  212. CEsdlTransformOperationAddValue(IPropertyTree *tree, const StringBuffer &prefix) : CEsdlTransformOperationSetValue(tree, prefix){}
  213. virtual ~CEsdlTransformOperationAddValue(){}
  214. virtual bool doSet(IXpathContext * xpathContext, IPropertyTree *tree, const char *value)
  215. {
  216. StringBuffer xpath;
  217. const char *target = getTargetPath(xpathContext, xpath);
  218. if (tree->getCount(target)==0)
  219. {
  220. ensurePTree(tree, target);
  221. tree->setProp(target, value);
  222. }
  223. else
  224. tree->addProp(target, value);
  225. return true;
  226. }
  227. };
  228. class CEsdlTransformOperationFail : public CEsdlTransformOperationBase
  229. {
  230. protected:
  231. StringAttr m_traceName;
  232. Owned<ICompiledXpath> m_message;
  233. Owned<ICompiledXpath> m_code;
  234. public:
  235. CEsdlTransformOperationFail(IPropertyTree *tree, const StringBuffer &prefix) : CEsdlTransformOperationBase(tree, prefix)
  236. {
  237. m_traceName.set(tree->queryProp("@name"));
  238. if (isEmptyString(tree->queryProp("@code")))
  239. esdlOperationError(ESDL_SCRIPT_MissingOperationAttr, m_tagname, "without code", m_traceName.str(), true);
  240. if (isEmptyString(tree->queryProp("@message")))
  241. esdlOperationError(ESDL_SCRIPT_MissingOperationAttr, m_tagname, "without message", m_traceName.str(), true);
  242. m_code.setown(compileXpath(tree->queryProp("@code")));
  243. m_message.setown(compileXpath(tree->queryProp("@message")));
  244. }
  245. virtual ~CEsdlTransformOperationFail()
  246. {
  247. }
  248. virtual bool process(IEspContext * context, IPropertyTree *content, IXpathContext * xpathContext) override
  249. {
  250. int code = m_code.get() ? (int) xpathContext->evaluateAsNumber(m_code) : ESDL_SCRIPT_Error;
  251. StringBuffer msg;
  252. if (m_message.get())
  253. xpathContext->evaluateAsString(m_message, msg);
  254. throw makeStringException(code, msg.str());
  255. return true; //avoid compilation error
  256. }
  257. virtual void toDBGLog() override
  258. {
  259. #if defined(_DEBUG)
  260. DBGLOG(">%s> %s with message(%s)", m_traceName.str(), m_tagname.str(), m_message.get() ? m_message->getXpath() : "");
  261. #endif
  262. }
  263. };
  264. class CEsdlTransformOperationAssert : public CEsdlTransformOperationFail
  265. {
  266. private:
  267. Owned<ICompiledXpath> m_test; //assert is like a conditional fail
  268. public:
  269. CEsdlTransformOperationAssert(IPropertyTree *tree, const StringBuffer &prefix) : CEsdlTransformOperationFail(tree, prefix)
  270. {
  271. if (isEmptyString(tree->queryProp("@test")))
  272. esdlOperationError(ESDL_SCRIPT_MissingOperationAttr, m_tagname, "without test", m_traceName.str(), true);
  273. m_test.setown(compileXpath(tree->queryProp("@test")));
  274. }
  275. virtual ~CEsdlTransformOperationAssert()
  276. {
  277. }
  278. virtual bool process(IEspContext * context, IPropertyTree *content, IXpathContext * xpathContext) override
  279. {
  280. if (m_test && xpathContext->evaluateAsBoolean(m_test))
  281. return false;
  282. return CEsdlTransformOperationFail::process(context, content, xpathContext);
  283. }
  284. virtual void toDBGLog() override
  285. {
  286. #if defined(_DEBUG)
  287. const char *testXpath = m_test.get() ? m_test->getXpath() : "SYNTAX ERROR IN test";
  288. DBGLOG(">%s> %s if '%s' with message(%s)", m_traceName.str(), m_tagname.str(), testXpath, m_message.get() ? m_message->getXpath() : "");
  289. #endif
  290. }
  291. };
  292. class CEsdlTransformOperationWithChildren : public CEsdlTransformOperationBase
  293. {
  294. protected:
  295. IArrayOf<IEsdlTransformOperation> m_children;
  296. public:
  297. CEsdlTransformOperationWithChildren(IPropertyTree *tree, const StringBuffer &prefix, bool withVariables) : CEsdlTransformOperationBase(tree, prefix)
  298. {
  299. if (tree)
  300. loadChildren(tree, prefix, withVariables);
  301. }
  302. virtual ~CEsdlTransformOperationWithChildren(){}
  303. virtual bool processChildren(IEspContext * context, IPropertyTree *content, IXpathContext * xpathContext)
  304. {
  305. bool ret = false;
  306. ForEachItemIn(i, m_children)
  307. {
  308. if (m_children.item(i).process(context, content, xpathContext))
  309. ret = true;
  310. }
  311. return ret;
  312. }
  313. virtual void toDBGLog () override
  314. {
  315. #if defined(_DEBUG)
  316. ForEachItemIn(i, m_children)
  317. m_children.item(i).toDBGLog();
  318. #endif
  319. }
  320. protected:
  321. virtual void loadChildren(IPropertyTree * tree, const StringBuffer &prefix, bool withVariables)
  322. {
  323. if (withVariables)
  324. {
  325. StringBuffer xpath;
  326. Owned<IPropertyTreeIterator> parameters = tree->getElements(makeOperationTagName(xpath, prefix, "param"));
  327. ForEach(*parameters)
  328. m_children.append(*new CEsdlTransformOperationParameter(&parameters->query(), prefix));
  329. Owned<IPropertyTreeIterator> variables = tree->getElements(makeOperationTagName(xpath.clear(), prefix, "variable"));
  330. ForEach(*variables)
  331. m_children.append(*new CEsdlTransformOperationVariable(&variables->query(), prefix));
  332. }
  333. Owned<IPropertyTreeIterator> children = tree->getElements("*");
  334. ForEach(*children)
  335. {
  336. Owned<IEsdlTransformOperation> operation = createEsdlTransformOperation(&children->query(), prefix);
  337. if (operation)
  338. m_children.append(*operation.getClear());
  339. }
  340. }
  341. };
  342. class CEsdlTransformOperationForEach : public CEsdlTransformOperationWithChildren
  343. {
  344. protected:
  345. Owned<ICompiledXpath> m_select;
  346. public:
  347. CEsdlTransformOperationForEach(IPropertyTree *tree, const StringBuffer &prefix) : CEsdlTransformOperationWithChildren(tree, prefix, true)
  348. {
  349. if (tree)
  350. {
  351. if (isEmptyString(tree->queryProp("@select")))
  352. esdlOperationError(ESDL_SCRIPT_MissingOperationAttr, m_tagname, "without select", !m_ignoreCodingErrors);
  353. m_select.setown(compileXpath(tree->queryProp("@select")));
  354. }
  355. }
  356. virtual ~CEsdlTransformOperationForEach(){}
  357. bool process(IEspContext * context, IPropertyTree *content, IXpathContext * xpathContext) override
  358. {
  359. Owned<IXpathContextIterator> contexts = evaluate(xpathContext);
  360. if (!contexts)
  361. return false;
  362. if (!contexts->first())
  363. return false;
  364. CXpathContextScope scope(xpathContext, "for-each"); //new variables are scoped
  365. ForEach(*contexts)
  366. processChildren(context, content, &contexts->query());
  367. return true;
  368. }
  369. virtual void toDBGLog () override
  370. {
  371. #if defined(_DEBUG)
  372. DBGLOG (">>>>%s %s ", m_tagname.str(), m_select ? m_select->getXpath() : "");
  373. CEsdlTransformOperationWithChildren::toDBGLog();
  374. DBGLOG ("<<<<%s<<<<<", m_tagname.str());
  375. #endif
  376. }
  377. private:
  378. IXpathContextIterator *evaluate(IXpathContext * xpathContext)
  379. {
  380. IXpathContextIterator *xpathset = nullptr;
  381. try
  382. {
  383. xpathset = xpathContext->evaluateAsNodeSet(m_select);
  384. }
  385. catch (IException* e)
  386. {
  387. int code = e->errorCode();
  388. StringBuffer msg;
  389. e->errorMessage(msg);
  390. e->Release();
  391. esdlOperationError(code, m_tagname, msg, !m_ignoreCodingErrors);
  392. }
  393. catch (...)
  394. {
  395. VStringBuffer msg("unknown exception evaluating select '%s'", m_select.get() ? m_select->getXpath() : "undefined!");
  396. esdlOperationError(ESDL_SCRIPT_Error, m_tagname, msg, !m_ignoreCodingErrors);
  397. }
  398. return xpathset;
  399. }
  400. };
  401. class CEsdlTransformOperationConditional : public CEsdlTransformOperationWithChildren
  402. {
  403. private:
  404. Owned<ICompiledXpath> m_test;
  405. char m_op = 'i'; //'i'=if, 'w'=when, 'o'=otherwise
  406. public:
  407. CEsdlTransformOperationConditional(IPropertyTree * tree, const StringBuffer &prefix) : CEsdlTransformOperationWithChildren(tree, prefix, true)
  408. {
  409. if (tree)
  410. {
  411. const char *op = checkSkipOpPrefix(tree->queryName(), prefix);
  412. if (isEmptyString(op))
  413. esdlOperationError(ESDL_SCRIPT_UnknownOperation, m_tagname, "unrecognized conditional", !m_ignoreCodingErrors);
  414. //m_ignoreCodingErrors means op may still be null
  415. if (!op || streq(op, "if"))
  416. m_op = 'i';
  417. else if (streq(op, "when"))
  418. m_op = 'w';
  419. else if (streq(op, "otherwise"))
  420. m_op = 'o';
  421. if (m_op!='o')
  422. {
  423. if (isEmptyString(tree->queryProp("@test")))
  424. esdlOperationError(ESDL_SCRIPT_MissingOperationAttr, m_tagname, "without test", !m_ignoreCodingErrors);
  425. m_test.setown(compileXpath(tree->queryProp("@test")));
  426. }
  427. }
  428. }
  429. virtual ~CEsdlTransformOperationConditional(){}
  430. bool process(IEspContext * context, IPropertyTree *content, IXpathContext * xpathContext) override
  431. {
  432. if (!evaluate(xpathContext))
  433. return false;
  434. CXpathContextScope scope(xpathContext, m_tagname); //child variables are scoped
  435. processChildren(context, content, xpathContext);
  436. return true; //just means that evaluation succeeded and attempted to process children
  437. }
  438. virtual void toDBGLog () override
  439. {
  440. #if defined(_DEBUG)
  441. DBGLOG (">>>>%s %s ", m_tagname.str(), m_test ? m_test->getXpath() : "");
  442. CEsdlTransformOperationWithChildren::toDBGLog();
  443. DBGLOG ("<<<<%s<<<<<", m_tagname.str());
  444. #endif
  445. }
  446. private:
  447. bool evaluate(IXpathContext * xpathContext)
  448. {
  449. if (m_op=='o') //'o'/"otherwise" is unconditional
  450. return true;
  451. bool match = false;
  452. try
  453. {
  454. match = xpathContext->evaluateAsBoolean(m_test);
  455. }
  456. catch (IException* e)
  457. {
  458. int code = e->errorCode();
  459. StringBuffer msg;
  460. e->errorMessage(msg);
  461. e->Release();
  462. esdlOperationError(code, m_tagname, msg, !m_ignoreCodingErrors);
  463. }
  464. catch (...)
  465. {
  466. VStringBuffer msg("unknown exception evaluating test '%s'", m_test.get() ? m_test->getXpath() : "undefined!");
  467. esdlOperationError(ESDL_SCRIPT_Error, m_tagname, msg, !m_ignoreCodingErrors);
  468. }
  469. return match;
  470. }
  471. };
  472. class CEsdlTransformOperationChoose : public CEsdlTransformOperationWithChildren
  473. {
  474. public:
  475. CEsdlTransformOperationChoose(IPropertyTree * tree, const StringBuffer &prefix) : CEsdlTransformOperationWithChildren(tree, prefix, false)
  476. {
  477. if (tree)
  478. {
  479. loadWhens(tree, prefix);
  480. loadOtherwise(tree, prefix);
  481. }
  482. }
  483. virtual ~CEsdlTransformOperationChoose(){}
  484. bool process(IEspContext * context, IPropertyTree *content, IXpathContext * xpathContext) override
  485. {
  486. return processChildren(context, content, xpathContext);
  487. }
  488. virtual bool processChildren(IEspContext * context, IPropertyTree *content, IXpathContext * xpathContext) override
  489. {
  490. ForEachItemIn(i, m_children)
  491. {
  492. if (m_children.item(i).process(context, content, xpathContext))
  493. return true;
  494. }
  495. return false;
  496. }
  497. virtual void toDBGLog () override
  498. {
  499. #if defined(_DEBUG)
  500. DBGLOG (">>>>>>>>>>> %s >>>>>>>>>>", m_tagname.str());
  501. CEsdlTransformOperationWithChildren::toDBGLog();
  502. DBGLOG (">>>>>>>>>>> %s >>>>>>>>>>", m_tagname.str());
  503. #endif
  504. }
  505. protected:
  506. void loadWhens(IPropertyTree * tree, const StringBuffer &prefix)
  507. {
  508. StringBuffer xpath;
  509. Owned<IPropertyTreeIterator> children = tree->getElements(makeOperationTagName(xpath, prefix, "when"));
  510. ForEach(*children)
  511. m_children.append(*new CEsdlTransformOperationConditional(&children->query(), prefix));
  512. }
  513. void loadOtherwise(IPropertyTree * tree, const StringBuffer &prefix)
  514. {
  515. StringBuffer xpath;
  516. IPropertyTree * otherwise = tree->queryPropTree(makeOperationTagName(xpath, prefix, "otherwise"));
  517. if (!otherwise)
  518. return;
  519. m_children.append(*new CEsdlTransformOperationConditional(otherwise, prefix));
  520. }
  521. virtual void loadChildren(IPropertyTree * tree, const StringBuffer &prefix, bool withVariables) override
  522. {
  523. loadWhens(tree, prefix);
  524. loadOtherwise(tree, prefix);
  525. }
  526. };
  527. IEsdlTransformOperation *createEsdlTransformOperation(IPropertyTree *element, const StringBuffer &prefix)
  528. {
  529. const char *op = checkSkipOpPrefix(element->queryName(), prefix);
  530. if (isEmptyString(op))
  531. return nullptr;
  532. if (streq(op, "choose"))
  533. return new CEsdlTransformOperationChoose(element, prefix);
  534. if (streq(op, "for-each"))
  535. return new CEsdlTransformOperationForEach(element, prefix);
  536. if (streq(op, "if"))
  537. return new CEsdlTransformOperationConditional(element, prefix);
  538. if (streq(op, "set-value") || streq(op, "SetValue"))
  539. return new CEsdlTransformOperationSetValue(element, prefix);
  540. if (streq(op, "append-to-value") || streq(op, "AppendValue"))
  541. return new CEsdlTransformOperationAppendValue(element, prefix);
  542. if (streq(op, "add-value"))
  543. return new CEsdlTransformOperationAddValue(element, prefix);
  544. if (streq(op, "fail"))
  545. return new CEsdlTransformOperationFail(element, prefix);
  546. if (streq(op, "assert"))
  547. return new CEsdlTransformOperationAssert(element, prefix);
  548. return nullptr;
  549. }
  550. static IPropertyTree *getTargetPTree(IPropertyTree *tree, IXpathContext *xpathContext, const char *target)
  551. {
  552. StringBuffer xpath(target);
  553. if (xpath.length())
  554. {
  555. //we can use real xpath processing in the future, for now simple substitution is fine
  556. StringBuffer variable;
  557. xpath.replaceString("{$query}", xpathContext->getVariable("query", variable));
  558. xpath.replaceString("{$method}", xpathContext->getVariable("method", variable.clear()));
  559. xpath.replaceString("{$service}", xpathContext->getVariable("service", variable.clear()));
  560. xpath.replaceString("{$request}", xpathContext->getVariable("request", variable.clear()));
  561. IPropertyTree *child = tree->queryPropTree(xpath.str()); //get pointer to the write-able area
  562. if (!child)
  563. throw MakeStringException(ESDL_SCRIPT_Error, "EsdlCustomTransform error getting target xpath %s", xpath.str());
  564. return child;
  565. }
  566. return tree;
  567. }
  568. static IPropertyTree *getOperationTargetPTree(MapStringToMyClass<IPropertyTree> &treeMap, IPropertyTree *currentTree, IEsdlTransformOperation &operation, IPropertyTree *tree, IXpathContext *xpathContext, const char *target)
  569. {
  570. const char *mergedTarget = operation.queryMergedTarget();
  571. if (isEmptyString(mergedTarget) || streq(mergedTarget, target))
  572. return currentTree;
  573. IPropertyTree *opTree = treeMap.getValue(mergedTarget);
  574. if (opTree)
  575. return opTree;
  576. opTree = getTargetPTree(tree, xpathContext, mergedTarget);
  577. if (opTree)
  578. treeMap.setValue(mergedTarget, LINK(opTree));
  579. return opTree;
  580. }
  581. class CEsdlCustomTransform : public CInterfaceOf<IEsdlCustomTransform>
  582. {
  583. private:
  584. IArrayOf<IEsdlTransformOperation> m_variables; //keep separate and only at top level for now
  585. IArrayOf<IEsdlTransformOperation> m_operations;
  586. StringAttr m_name;
  587. StringAttr m_target;
  588. StringBuffer m_prefix;
  589. public:
  590. CEsdlCustomTransform(){}
  591. void verifyPrefixDeclared(IPropertyTree &tree, const char *prefix)
  592. {
  593. StringBuffer attpath("@xmlns");
  594. if (!isEmptyString(prefix))
  595. attpath.append(':').append(prefix);
  596. const char *uri = tree.queryProp(attpath.str());
  597. if (!uri || !streq(uri, "urn:hpcc:esdl:script"))
  598. throw MakeStringException(ESDL_SCRIPT_Error, "Undeclared script xmlns prefix %s", isEmptyString(prefix) ? "<default>" : prefix);
  599. }
  600. CEsdlCustomTransform(IPropertyTree &tree, const char *ns_prefix) : m_prefix(ns_prefix)
  601. {
  602. if (m_prefix.length())
  603. m_prefix.append(':');
  604. else
  605. {
  606. const char *tag = tree.queryName();
  607. if (!tag)
  608. m_prefix.set("xsdl:");
  609. else
  610. {
  611. const char *colon = strchr(tag, ':');
  612. if (!colon)
  613. verifyPrefixDeclared(tree, nullptr);
  614. else
  615. {
  616. if (colon == tag)
  617. throw MakeStringException(ESDL_SCRIPT_Error, "Tag shouldn't start with colon %s", tag);
  618. m_prefix.append(colon-tag, tag);
  619. if (!streq(m_prefix, "xsdl"))
  620. verifyPrefixDeclared(tree, m_prefix);
  621. //add back the colon for easy comparison
  622. m_prefix.append(':');
  623. }
  624. }
  625. }
  626. m_name.set(tree.queryProp("@name"));
  627. m_target.set(tree.queryProp("@target"));
  628. DBGLOG("Compiling custom ESDL Transform: '%s'", m_name.str());
  629. StringBuffer xpath;
  630. Owned<IPropertyTreeIterator> parameters = tree.getElements(makeOperationTagName(xpath, m_prefix, "param"));
  631. ForEach(*parameters)
  632. m_variables.append(*new CEsdlTransformOperationParameter(&parameters->query(), m_prefix));
  633. Owned<IPropertyTreeIterator> variables = tree.getElements(makeOperationTagName(xpath.clear(), m_prefix, "variable"));
  634. ForEach(*variables)
  635. m_variables.append(*new CEsdlTransformOperationVariable(&variables->query(), m_prefix));
  636. Owned<IPropertyTreeIterator> children = tree.getElements("*");
  637. ForEach(*children)
  638. {
  639. Owned<IEsdlTransformOperation> operation = createEsdlTransformOperation(&children->query(), m_prefix);
  640. if (operation)
  641. m_operations.append(*operation.getClear());
  642. }
  643. }
  644. virtual void appendEsdlURIPrefixes(StringArray &prefixes) override
  645. {
  646. if (m_prefix.length())
  647. {
  648. StringAttr copy(m_prefix.str(), m_prefix.length()-1); //remove the colon
  649. prefixes.appendUniq(copy.str());
  650. }
  651. else
  652. prefixes.appendUniq("");
  653. }
  654. virtual void toDBGLog() override
  655. {
  656. #if defined(_DEBUG)
  657. DBGLOG(">>>>>>>>>>>>>>>>transform: '%s'>>>>>>>>>>", m_name.str());
  658. ForEachItemIn(i, m_operations)
  659. m_operations.item(i).toDBGLog();
  660. DBGLOG("<<<<<<<<<<<<<<<<transform<<<<<<<<<<<<");
  661. #endif
  662. }
  663. virtual ~CEsdlCustomTransform(){}
  664. void processTransformImpl(IEspContext * context, IPropertyTree *theroot, IXpathContext *xpathContext, const char *target)
  665. {
  666. CXpathContextScope scope(xpathContext, "transform");
  667. if (m_target.length())
  668. target = m_target.str();
  669. MapStringToMyClass<IPropertyTree> treeMap; //cache trees because when there are merged targets they are likely to repeat
  670. IPropertyTree *txTree = getTargetPTree(theroot, xpathContext, target);
  671. treeMap.setValue(target, LINK(txTree));
  672. ForEachItemIn(v, m_variables)
  673. m_variables.item(v).process(context, txTree, xpathContext);
  674. ForEachItemIn(i, m_operations)
  675. {
  676. IPropertyTree *opTree = getOperationTargetPTree(treeMap, txTree, m_operations.item(i), theroot, xpathContext, target);
  677. m_operations.item(i).process(context, opTree, xpathContext);
  678. }
  679. }
  680. void processTransform(IEspContext * context, IPropertyTree *tgtcfg, IEsdlDefService &srvdef, IEsdlDefMethod &mthdef, StringBuffer & content, IPropertyTree * bindingCfg) override
  681. {
  682. processServiceAndMethodTransforms({static_cast<IEsdlCustomTransform*>(this)}, context, tgtcfg, srvdef, mthdef, content, bindingCfg);
  683. }
  684. void processTransform(IEspContext * context, IPropertyTree *tgtcfg, const char *service, const char *method, const char* reqtype, StringBuffer & content, IPropertyTree * bindingCfg) override
  685. {
  686. processServiceAndMethodTransforms({static_cast<IEsdlCustomTransform*>(this)}, context, tgtcfg, service, method, reqtype, content, bindingCfg);
  687. }
  688. };
  689. void processServiceAndMethodTransforms(std::initializer_list<IEsdlCustomTransform *> const &transforms, IEspContext * context, IPropertyTree *tgtcfg, const char *service, const char *method, const char* reqtype, StringBuffer & content, IPropertyTree * bindingCfg)
  690. {
  691. LogLevel level = LogMin;
  692. if (!transforms.size())
  693. return;
  694. if (tgtcfg)
  695. level = (unsigned) tgtcfg->getPropInt("@traceLevel", level);
  696. if (content.length()!=0)
  697. {
  698. if (level >= LogMax)
  699. {
  700. DBGLOG("ORIGINAL content: %s", content.str());
  701. StringBuffer marshalled;
  702. if (bindingCfg)
  703. toXML(bindingCfg, marshalled.clear());
  704. DBGLOG("BINDING CONFIG: %s", marshalled.str());
  705. if (tgtcfg)
  706. toXML(tgtcfg, marshalled.clear());
  707. DBGLOG("TARGET CONFIG: %s", marshalled.str());
  708. }
  709. bool strictParams = bindingCfg ? bindingCfg->getPropBool("@strictParams", false) : false;
  710. Owned<IXpathContext> xpathContext = getXpathContext(content.str(), strictParams);
  711. StringArray prefixes;
  712. for ( IEsdlCustomTransform * const & item : transforms)
  713. {
  714. if (item)
  715. item->appendEsdlURIPrefixes(prefixes);
  716. }
  717. registerEsdlXPathExtensions(xpathContext, context, prefixes);
  718. VStringBuffer ver("%g", context->getClientVersion());
  719. if(!xpathContext->addVariable("clientversion", ver.str()))
  720. OERRLOG("Could not set ESDL Script variable: clientversion:'%s'", ver.str());
  721. //in case transform wants to make use of these values:
  722. //make them few well known values variables rather than inputs so they are automatically available
  723. xpathContext->addVariable("query", tgtcfg->queryProp("@queryname"));
  724. xpathContext->addVariable("method", method);
  725. xpathContext->addVariable("service", service);
  726. xpathContext->addVariable("request", reqtype);
  727. ISecUser *user = context->queryUser();
  728. if (user)
  729. {
  730. static const std::map<SecUserStatus, const char*> statusLabels =
  731. {
  732. #define STATUS_LABEL_NODE(s) { s, #s }
  733. STATUS_LABEL_NODE(SecUserStatus_Inhouse),
  734. STATUS_LABEL_NODE(SecUserStatus_Active),
  735. STATUS_LABEL_NODE(SecUserStatus_Exempt),
  736. STATUS_LABEL_NODE(SecUserStatus_FreeTrial),
  737. STATUS_LABEL_NODE(SecUserStatus_csdemo),
  738. STATUS_LABEL_NODE(SecUserStatus_Rollover),
  739. STATUS_LABEL_NODE(SecUserStatus_Suspended),
  740. STATUS_LABEL_NODE(SecUserStatus_Terminated),
  741. STATUS_LABEL_NODE(SecUserStatus_TrialExpired),
  742. STATUS_LABEL_NODE(SecUserStatus_Status_Hold),
  743. STATUS_LABEL_NODE(SecUserStatus_Unknown),
  744. #undef STATUS_LABEL_NODE
  745. };
  746. Owned<IPropertyIterator> userPropIt = user->getPropertyIterator();
  747. ForEach(*userPropIt)
  748. {
  749. const char *name = userPropIt->getPropKey();
  750. if (name && *name)
  751. xpathContext->addInputValue(name, user->getProperty(name));
  752. }
  753. auto it = statusLabels.find(user->getStatus());
  754. xpathContext->addInputValue("espUserName", user->getName());
  755. xpathContext->addInputValue("espUserRealm", user->getRealm() ? user->getRealm() : "");
  756. xpathContext->addInputValue("espUserPeer", user->getPeer() ? user->getPeer() : "");
  757. xpathContext->addInputValue("espUserStatus", VStringBuffer("%d", int(user->getStatus())));
  758. if (it != statusLabels.end())
  759. xpathContext->addInputValue("espUserStatusString", it->second);
  760. else
  761. throw MakeStringException(ESDL_SCRIPT_Error, "encountered unexpected secure user status (%d) while processing transform", int(user->getStatus()));
  762. }
  763. else
  764. {
  765. // enable transforms to distinguish secure versus insecure requests
  766. xpathContext->addInputValue("espUserName", "");
  767. xpathContext->addInputValue("espUserRealm", "");
  768. xpathContext->addInputValue("espUserPeer", "");
  769. xpathContext->addInputValue("espUserStatus", "");
  770. xpathContext->addInputValue("espUserStatusString", "");
  771. }
  772. //external parameters need <es:param> statements to make them accessible (in strict mode)
  773. Owned<IPropertyTreeIterator> configParams;
  774. if (bindingCfg)
  775. configParams.setown(bindingCfg->getElements("Transform/Param"));
  776. if (configParams)
  777. {
  778. ForEach(*configParams)
  779. {
  780. IPropertyTree & currentParam = configParams->query();
  781. if (currentParam.hasProp("@select"))
  782. xpathContext->addInputXpath(currentParam.queryProp("@name"), currentParam.queryProp("@select"));
  783. else
  784. xpathContext->addInputValue(currentParam.queryProp("@name"), currentParam.queryProp("@value"));
  785. }
  786. }
  787. if (!strictParams)
  788. xpathContext->declareRemainingInputs();
  789. Owned<IPropertyTree> theroot = createPTreeFromXMLString(content.str());
  790. StringBuffer defaultTarget;
  791. //This default gives us backward compatibility with only being able to write to the actual request
  792. const char *tgtQueryName = tgtcfg->queryProp("@queryname");
  793. defaultTarget.setf("soap:Body/%s/%s", tgtQueryName ? tgtQueryName : method, reqtype);
  794. for ( auto&& item : transforms)
  795. {
  796. if (item)
  797. {
  798. CEsdlCustomTransform *transform = static_cast<CEsdlCustomTransform*>(item);
  799. transform->processTransformImpl(context, theroot, xpathContext, defaultTarget);
  800. }
  801. }
  802. toXML(theroot, content.clear());
  803. if (level >= LogMax)
  804. DBGLOG(1,"MODIFIED content: %s", content.str());
  805. }
  806. }
  807. void processServiceAndMethodTransforms(std::initializer_list<IEsdlCustomTransform *> const &transforms, IEspContext * context, IPropertyTree *tgtcfg, IEsdlDefService &srvdef, IEsdlDefMethod &mthdef, StringBuffer & content, IPropertyTree * bindingCfg)
  808. {
  809. processServiceAndMethodTransforms(transforms, context, tgtcfg, srvdef.queryName(), mthdef.queryMethodName(), mthdef.queryRequestType(), content, bindingCfg);
  810. }
  811. IEsdlCustomTransform *createEsdlCustomTransform(IPropertyTree &tree, const char *ns_prefix)
  812. {
  813. return new CEsdlCustomTransform(tree, ns_prefix);
  814. }