Advertisement
Guest User

Untitled

a guest
Oct 19th, 2019
128
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 23.90 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta name="description" content="[A simple workflow orchestration engine design.]">
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width">
  7. <title>JS Bin</title>
  8. </head>
  9. <body>
  10.  
  11. <script id="jsbin-javascript">
  12. let definition = {
  13. "name":"sampleWorkflowDefinition",
  14. "creaitonTime":1100,
  15. "data":{
  16. "name":"string",
  17. "ic":"string",
  18. "amt":"number",
  19. "addr":"string",
  20. "identity":"object"
  21. },
  22. "processes":{
  23. "0":{
  24. "id":0,
  25. "startCondition":{
  26. "type":"AND",
  27. "subConditions":[
  28. {
  29. "id":"root",
  30. "type":"EQ",
  31. "field":"state",
  32. "value":"started"
  33. }
  34. ]
  35. },
  36. "service":"service0",
  37. "data":[
  38. "ic"
  39. ]
  40. },
  41. "1":{
  42. "id":1,
  43. "startCondition":{
  44. "type":"AND",
  45. "subConditions":[
  46. {
  47. "id":0,
  48. "type":"EQ",
  49. "field":"state",
  50. "value":"complete"
  51. }
  52. ]
  53. },
  54. "service":"service1",
  55. "data":[
  56. "name","addr"
  57. ]
  58. },
  59. "2":{
  60. "id":2,
  61. "startCondition":{
  62. "type":"MULTIPLE",
  63. "subConditions":[
  64. {
  65. "id":0,
  66. "type":"EQ",
  67. "field":"state",
  68. "value":"complete"
  69. },
  70. {
  71. "type":"AND",
  72. "subConditions":[
  73. {
  74. "id":3,
  75. "type":"EQ",
  76. "field":"state",
  77. "value":"failed"
  78. },
  79. {
  80. "id":3,
  81. "type":"NEQ",
  82. "field":"state",
  83. "value":"complete"
  84. }
  85. ]
  86. }
  87. ]
  88. },
  89. "service":"service2",
  90. "data":[
  91. "identity"
  92. ]
  93. },
  94. "3":{
  95. "id":3,
  96. "startCondition":{
  97. "type":"AND",
  98. "subConditions":[
  99. {
  100. "id":2,
  101. "type":"EQ",
  102. "field":"state",
  103. "value":"complete"
  104. }
  105. ]
  106. },
  107. "service":"service3",
  108. "data":[
  109. "ic"
  110. ]
  111. },
  112. "4":{
  113. "id":4,
  114. "startCondition":{
  115. "type":"AND",
  116. "subConditions":[
  117. {
  118. "id":1,
  119. "type":"EQ",
  120. "field":"state",
  121. "value":"complete"
  122. },
  123. {
  124. "id":2,
  125. "type":"EQ",
  126. "field":"state",
  127. "value":"complete"
  128. },
  129. {
  130. "id":3,
  131. "type":"EQ",
  132. "field":"state",
  133. "value":"complete"
  134. }
  135. ]
  136. },
  137. "service":"service4",
  138. "data":[
  139. "ic"
  140. ]
  141. }
  142. }
  143. };
  144.  
  145. function isEmpty(obj) {
  146. for(var key in obj) {
  147. if(obj.hasOwnProperty(key))
  148. return false;
  149. }
  150. return true;
  151. }
  152.  
  153.  
  154.  
  155.  
  156.  
  157. let eventList = {};
  158. function orchestrate(workflow){
  159. // Show meta
  160. let time = new Date();
  161. console.log(`Starting workflow (${workflow.name}) @ ${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}`);
  162.  
  163. let sampleInput = {name:"tim",
  164. ic:"1128696556565",
  165. amt:100,
  166. addr:"1 jln 100, msia",
  167. identity:{
  168. "name":"tim",
  169. "secret":"awesome"
  170. }
  171. };
  172. console.log(`Sample Input: ${JSON.stringify(sampleInput, null, 2)}`);
  173.  
  174. // Generate workflow state
  175. let state = generateWorkflowState(workflow, sampleInput);
  176. console.log(`Initial workflow State: ${JSON.stringify(state,null,2)}`);
  177.  
  178. // Build event list
  179. buildEventList(workflow);
  180. console.log(`Event list: ${JSON.stringify(eventList, null, 2)}`);
  181.  
  182. // Emit root event
  183. eventList.root.forEach((event)=>{
  184. startConditionEvaluation(state,event);
  185. });
  186. }
  187. orchestrate(definition);
  188.  
  189.  
  190.  
  191.  
  192.  
  193. function generateWorkflowState(workflow, input){
  194. let state = {
  195. "definition":workflow,
  196. "states":{
  197. "data":input,
  198. "processes":{}
  199. }
  200. }
  201. for (let processId in workflow.processes){
  202. let process = workflow.processes[processId];
  203. state.states.processes[processId] = {
  204. data:{
  205. id:processId
  206. }
  207. };
  208. process.data.forEach((key)=>{
  209. state.states.processes[processId]['data'][key] = state.states.data[key];
  210. });
  211. }
  212. return state;
  213. }
  214.  
  215.  
  216.  
  217.  
  218.  
  219. function buildEventList(workflow){
  220. // Build event handling chain from workflow definition
  221. for(let processId in workflow.processes){
  222. let process = workflow.processes[processId];
  223. if (!isEmpty(process.startCondition)){
  224.  
  225. let rootCondition = process.startCondition;
  226. rootCondition.processToStart = process.id;
  227. let addedList = []
  228. addEventEntry(rootCondition, rootCondition, addedList);
  229. }
  230. }
  231. }
  232.  
  233. function addEventEntry(condition, rootCondition, addedList){
  234. if (condition.type === 'AND' || condition.type === 'OR'){
  235. condition.subConditions.forEach((subCondition)=>{
  236. return addEventEntry(subCondition, rootCondition, addedList);
  237. });
  238. }else if (condition.type === 'MULTIPLE'){
  239. condition.subConditions.forEach((subCondition)=>{
  240. subCondition.processToStart = rootCondition.processToStart;
  241. addEventEntry(subCondition, subCondition, addedList);
  242. });
  243. }else{
  244. if(!addedList.includes(condition.id)){
  245. addedList.push(condition.id);
  246. if(isEmpty(eventList[`${condition.id}`])){
  247. eventList[`${condition.id}`] = [rootCondition];
  248. }else{
  249. eventList[`${condition.id}`].push(rootCondition);
  250. }
  251. return;
  252. }
  253. }
  254. }
  255.  
  256.  
  257.  
  258.  
  259.  
  260.  
  261. async function startConditionEvaluation(state, event){
  262. console.log(`Begin evaluating starting condition for process #${event.processToStart}.`);
  263. let passed = conditionEvaluation(state, event);
  264. console.log(`Root condition evaluation for process #${event.processToStart} passed = ${passed}`);
  265. if (passed){
  266. console.log(`Proceed to start process #${event.processToStart}`);
  267. let result = await delegateService(state, event.processToStart);
  268. completeProcess(state, event.processToStart, result);
  269. return;
  270. }
  271. //console.log('Do Nothing');
  272. return;
  273. }
  274.  
  275.  
  276.  
  277. function conditionEvaluation(state, condition){
  278. //console.log(`Evaluating ${JSON.stringify(condition, null, 2)}`);
  279. let passed = true;
  280. if (condition.type === 'AND'){
  281. condition.subConditions.forEach((subCondition)=>{
  282. if (!conditionEvaluation(state, subCondition)){
  283. passed = false;
  284. }
  285. });
  286. //console.log(`AND evaluation passed: ${passed}`);
  287. }else if (condition.type === 'OR'){
  288. passed = false;
  289. condition.subConditions.forEach((subCondition)=>{
  290. if (conditionEvaluation(state, subCondition)){
  291. passed = true;
  292. }
  293. });
  294. //console.log(`OR evaluation passed: ${passed}`);
  295. }else if (condition.type === 'EQ' || condition.type === 'NEQ'){
  296. if (condition.id == 'root'){
  297. passed = true;
  298. }else{
  299. if(!state.states.processes[condition.id]){
  300. console.log(`State of ${condition.id} does not exists, check evaluated as failed.`);
  301. passed = false;
  302. }else if (!state.states.processes[condition.id].result[condition.field]){
  303. console.log(`State of ${condition.id} doesnt contain ${condition.field}, check evaluated as failed.`);
  304. passed = false;
  305. }else {
  306. //console.log(`State value of ${condition.field} is ${state.states.processes[condition.id].result[condition.field]}`);
  307. if (condition.type === 'EQ' && condition.value !== state.states.processes[condition.id].result[condition.field]){
  308. passed = false;
  309. }else if (condition.type === 'NEQ' && condition.value === state.states.processes[condition.id].result[condition.field]){
  310. passed = false;
  311. }
  312. }
  313. }
  314. }
  315. //console.log(`Completed evaluation on ${JSON.stringify(condition, null, 2)}`);
  316. return passed;
  317. }
  318.  
  319.  
  320.  
  321.  
  322.  
  323.  
  324. function completeProcess(state, processId, result){
  325. console.log(`Process #${processId} completed, updating states.`);
  326. // Update state
  327. state.states.processes[processId].result = result;
  328. //console.log(`Updated state: ${JSON.stringify(state.states,null,2)}`);
  329.  
  330. console.log(`State updated with results from process #${processId}, emitting process completion event`);
  331. eventList[processId].forEach((event)=>{
  332. startConditionEvaluation(state,event);
  333. });
  334. }
  335.  
  336.  
  337.  
  338.  
  339.  
  340. function delegateService(state, processId){
  341. // Extract data based on definition
  342. let data = state.states.processes[processId]['data'];
  343.  
  344. if (processId === 0){
  345. return service0(data);
  346. }else if (processId === 1){
  347. return service1(data);
  348. }else if (processId === 2){
  349. return service2(data);
  350. }else if (processId === 3){
  351. return service3(data);
  352. }else if (processId === 4){
  353. return service4(data);
  354. }
  355. }
  356.  
  357.  
  358.  
  359. // ====================================Other chaincode=================================================
  360. // Mock Services
  361. function service0(data){
  362. return new Promise((resolve, reject)=>{
  363. console.log(`service0 started with data ${JSON.stringify(data)}`);
  364. let timeout = setTimeout(()=>{
  365. clearTimeout(timeout);
  366. console.log("service0 completed.");
  367. let result = {
  368. id:data.id,
  369. state:"complete",
  370. status:200,
  371. result:"You done it!"
  372. }
  373. resolve(result);
  374. }, 1000);
  375. });
  376. }
  377. function service1(data){
  378. return new Promise((resolve, reject)=>{
  379. console.log(`service1 started with data ${JSON.stringify(data)}`);
  380. let timeout = setTimeout(()=>{
  381. clearTimeout(timeout);
  382. console.log("service1 completed.");
  383. let result = {
  384. id:data.id,
  385. state:"complete",
  386. status:200,
  387. result:"You done it!"
  388. }
  389. resolve(result);
  390. }, 1500);
  391. });
  392. }
  393. function service2(data){
  394. return new Promise((resolve, reject)=>{
  395. console.log(`service2 started with data ${JSON.stringify(data)}`);
  396. let timeout = setTimeout(()=>{
  397. clearTimeout(timeout);
  398. console.log("service2 completed.");
  399. let result = {
  400. id:data.id,
  401. state:"complete",
  402. status:200,
  403. result:"You done it!"
  404. }
  405. resolve(result);
  406. }, 3000);
  407. });
  408. }
  409. function service3(data){
  410. return new Promise((resolve, reject)=>{
  411. console.log(`service3 started with data ${JSON.stringify(data)}`);
  412. let timeout = setTimeout(()=>{
  413. clearTimeout(timeout);
  414. console.log("service3 completed.");
  415. let result = {
  416. id:data.id,
  417. state:"complete",
  418. status:200,
  419. result:"You done it!"
  420. }
  421. resolve(result);
  422. }, 2000);
  423. });
  424. }
  425. function service4(data){
  426. return new Promise((resolve, reject)=>{
  427. console.log(`service4 started with data ${JSON.stringify(data)}`);
  428. let timeout = setTimeout(()=>{
  429. clearTimeout(timeout);
  430. console.log("service4 completed.");
  431. let result = {
  432. id:data.id,
  433. state:"complete",
  434. status:200,
  435. result:"You done it!"
  436. }
  437. resolve(result);
  438. }, 2500);
  439. });
  440. }
  441. </script>
  442.  
  443.  
  444.  
  445. <script id="jsbin-source-javascript" type="text/javascript">let definition = {
  446. "name":"sampleWorkflowDefinition",
  447. "creaitonTime":1100,
  448. "data":{
  449. "name":"string",
  450. "ic":"string",
  451. "amt":"number",
  452. "addr":"string",
  453. "identity":"object"
  454. },
  455. "processes":{
  456. "0":{
  457. "id":0,
  458. "startCondition":{
  459. "type":"AND",
  460. "subConditions":[
  461. {
  462. "id":"root",
  463. "type":"EQ",
  464. "field":"state",
  465. "value":"started"
  466. }
  467. ]
  468. },
  469. "service":"service0",
  470. "data":[
  471. "ic"
  472. ]
  473. },
  474. "1":{
  475. "id":1,
  476. "startCondition":{
  477. "type":"AND",
  478. "subConditions":[
  479. {
  480. "id":0,
  481. "type":"EQ",
  482. "field":"state",
  483. "value":"complete"
  484. }
  485. ]
  486. },
  487. "service":"service1",
  488. "data":[
  489. "name","addr"
  490. ]
  491. },
  492. "2":{
  493. "id":2,
  494. "startCondition":{
  495. "type":"MULTIPLE",
  496. "subConditions":[
  497. {
  498. "id":0,
  499. "type":"EQ",
  500. "field":"state",
  501. "value":"complete"
  502. },
  503. {
  504. "type":"AND",
  505. "subConditions":[
  506. {
  507. "id":3,
  508. "type":"EQ",
  509. "field":"state",
  510. "value":"failed"
  511. },
  512. {
  513. "id":3,
  514. "type":"NEQ",
  515. "field":"state",
  516. "value":"complete"
  517. }
  518. ]
  519. }
  520. ]
  521. },
  522. "service":"service2",
  523. "data":[
  524. "identity"
  525. ]
  526. },
  527. "3":{
  528. "id":3,
  529. "startCondition":{
  530. "type":"AND",
  531. "subConditions":[
  532. {
  533. "id":2,
  534. "type":"EQ",
  535. "field":"state",
  536. "value":"complete"
  537. }
  538. ]
  539. },
  540. "service":"service3",
  541. "data":[
  542. "ic"
  543. ]
  544. },
  545. "4":{
  546. "id":4,
  547. "startCondition":{
  548. "type":"AND",
  549. "subConditions":[
  550. {
  551. "id":1,
  552. "type":"EQ",
  553. "field":"state",
  554. "value":"complete"
  555. },
  556. {
  557. "id":2,
  558. "type":"EQ",
  559. "field":"state",
  560. "value":"complete"
  561. },
  562. {
  563. "id":3,
  564. "type":"EQ",
  565. "field":"state",
  566. "value":"complete"
  567. }
  568. ]
  569. },
  570. "service":"service4",
  571. "data":[
  572. "ic"
  573. ]
  574. }
  575. }
  576. };
  577.  
  578. function isEmpty(obj) {
  579. for(var key in obj) {
  580. if(obj.hasOwnProperty(key))
  581. return false;
  582. }
  583. return true;
  584. }
  585.  
  586.  
  587.  
  588.  
  589.  
  590. let eventList = {};
  591. function orchestrate(workflow){
  592. // Show meta
  593. let time = new Date();
  594. console.log(`Starting workflow (${workflow.name}) @ ${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}`);
  595.  
  596. let sampleInput = {name:"tim",
  597. ic:"1128696556565",
  598. amt:100,
  599. addr:"1 jln 100, msia",
  600. identity:{
  601. "name":"tim",
  602. "secret":"awesome"
  603. }
  604. };
  605. console.log(`Sample Input: ${JSON.stringify(sampleInput, null, 2)}`);
  606.  
  607. // Generate workflow state
  608. let state = generateWorkflowState(workflow, sampleInput);
  609. console.log(`Initial workflow State: ${JSON.stringify(state,null,2)}`);
  610.  
  611. // Build event list
  612. buildEventList(workflow);
  613. console.log(`Event list: ${JSON.stringify(eventList, null, 2)}`);
  614.  
  615. // Emit root event
  616. eventList.root.forEach((event)=>{
  617. startConditionEvaluation(state,event);
  618. });
  619. }
  620. orchestrate(definition);
  621.  
  622.  
  623.  
  624.  
  625.  
  626. function generateWorkflowState(workflow, input){
  627. let state = {
  628. "definition":workflow,
  629. "states":{
  630. "data":input,
  631. "processes":{}
  632. }
  633. }
  634. for (let processId in workflow.processes){
  635. let process = workflow.processes[processId];
  636. state.states.processes[processId] = {
  637. data:{
  638. id:processId
  639. }
  640. };
  641. process.data.forEach((key)=>{
  642. state.states.processes[processId]['data'][key] = state.states.data[key];
  643. });
  644. }
  645. return state;
  646. }
  647.  
  648.  
  649.  
  650.  
  651.  
  652. function buildEventList(workflow){
  653. // Build event handling chain from workflow definition
  654. for(let processId in workflow.processes){
  655. let process = workflow.processes[processId];
  656. if (!isEmpty(process.startCondition)){
  657.  
  658. let rootCondition = process.startCondition;
  659. rootCondition.processToStart = process.id;
  660. let addedList = []
  661. addEventEntry(rootCondition, rootCondition, addedList);
  662. }
  663. }
  664. }
  665.  
  666. function addEventEntry(condition, rootCondition, addedList){
  667. if (condition.type === 'AND' || condition.type === 'OR'){
  668. condition.subConditions.forEach((subCondition)=>{
  669. return addEventEntry(subCondition, rootCondition, addedList);
  670. });
  671. }else if (condition.type === 'MULTIPLE'){
  672. condition.subConditions.forEach((subCondition)=>{
  673. subCondition.processToStart = rootCondition.processToStart;
  674. addEventEntry(subCondition, subCondition, addedList);
  675. });
  676. }else{
  677. if(!addedList.includes(condition.id)){
  678. addedList.push(condition.id);
  679. if(isEmpty(eventList[`${condition.id}`])){
  680. eventList[`${condition.id}`] = [rootCondition];
  681. }else{
  682. eventList[`${condition.id}`].push(rootCondition);
  683. }
  684. return;
  685. }
  686. }
  687. }
  688.  
  689.  
  690.  
  691.  
  692.  
  693.  
  694. async function startConditionEvaluation(state, event){
  695. console.log(`Begin evaluating starting condition for process #${event.processToStart}.`);
  696. let passed = conditionEvaluation(state, event);
  697. console.log(`Root condition evaluation for process #${event.processToStart} passed = ${passed}`);
  698. if (passed){
  699. console.log(`Proceed to start process #${event.processToStart}`);
  700. let result = await delegateService(state, event.processToStart);
  701. completeProcess(state, event.processToStart, result);
  702. return;
  703. }
  704. //console.log('Do Nothing');
  705. return;
  706. }
  707.  
  708.  
  709.  
  710. function conditionEvaluation(state, condition){
  711. //console.log(`Evaluating ${JSON.stringify(condition, null, 2)}`);
  712. let passed = true;
  713. if (condition.type === 'AND'){
  714. condition.subConditions.forEach((subCondition)=>{
  715. if (!conditionEvaluation(state, subCondition)){
  716. passed = false;
  717. }
  718. });
  719. //console.log(`AND evaluation passed: ${passed}`);
  720. }else if (condition.type === 'OR'){
  721. passed = false;
  722. condition.subConditions.forEach((subCondition)=>{
  723. if (conditionEvaluation(state, subCondition)){
  724. passed = true;
  725. }
  726. });
  727. //console.log(`OR evaluation passed: ${passed}`);
  728. }else if (condition.type === 'EQ' || condition.type === 'NEQ'){
  729. if (condition.id == 'root'){
  730. passed = true;
  731. }else{
  732. if(!state.states.processes[condition.id]){
  733. console.log(`State of ${condition.id} does not exists, check evaluated as failed.`);
  734. passed = false;
  735. }else if (!state.states.processes[condition.id].result[condition.field]){
  736. console.log(`State of ${condition.id} doesnt contain ${condition.field}, check evaluated as failed.`);
  737. passed = false;
  738. }else {
  739. //console.log(`State value of ${condition.field} is ${state.states.processes[condition.id].result[condition.field]}`);
  740. if (condition.type === 'EQ' && condition.value !== state.states.processes[condition.id].result[condition.field]){
  741. passed = false;
  742. }else if (condition.type === 'NEQ' && condition.value === state.states.processes[condition.id].result[condition.field]){
  743. passed = false;
  744. }
  745. }
  746. }
  747. }
  748. //console.log(`Completed evaluation on ${JSON.stringify(condition, null, 2)}`);
  749. return passed;
  750. }
  751.  
  752.  
  753.  
  754.  
  755.  
  756.  
  757. function completeProcess(state, processId, result){
  758. console.log(`Process #${processId} completed, updating states.`);
  759. // Update state
  760. state.states.processes[processId].result = result;
  761. //console.log(`Updated state: ${JSON.stringify(state.states,null,2)}`);
  762.  
  763. console.log(`State updated with results from process #${processId}, emitting process completion event`);
  764. eventList[processId].forEach((event)=>{
  765. startConditionEvaluation(state,event);
  766. });
  767. }
  768.  
  769.  
  770.  
  771.  
  772.  
  773. function delegateService(state, processId){
  774. // Extract data based on definition
  775. let data = state.states.processes[processId]['data'];
  776.  
  777. if (processId === 0){
  778. return service0(data);
  779. }else if (processId === 1){
  780. return service1(data);
  781. }else if (processId === 2){
  782. return service2(data);
  783. }else if (processId === 3){
  784. return service3(data);
  785. }else if (processId === 4){
  786. return service4(data);
  787. }
  788. }
  789.  
  790.  
  791.  
  792. // ====================================Other chaincode=================================================
  793. // Mock Services
  794. function service0(data){
  795. return new Promise((resolve, reject)=>{
  796. console.log(`service0 started with data ${JSON.stringify(data)}`);
  797. let timeout = setTimeout(()=>{
  798. clearTimeout(timeout);
  799. console.log("service0 completed.");
  800. let result = {
  801. id:data.id,
  802. state:"complete",
  803. status:200,
  804. result:"You done it!"
  805. }
  806. resolve(result);
  807. }, 1000);
  808. });
  809. }
  810. function service1(data){
  811. return new Promise((resolve, reject)=>{
  812. console.log(`service1 started with data ${JSON.stringify(data)}`);
  813. let timeout = setTimeout(()=>{
  814. clearTimeout(timeout);
  815. console.log("service1 completed.");
  816. let result = {
  817. id:data.id,
  818. state:"complete",
  819. status:200,
  820. result:"You done it!"
  821. }
  822. resolve(result);
  823. }, 1500);
  824. });
  825. }
  826. function service2(data){
  827. return new Promise((resolve, reject)=>{
  828. console.log(`service2 started with data ${JSON.stringify(data)}`);
  829. let timeout = setTimeout(()=>{
  830. clearTimeout(timeout);
  831. console.log("service2 completed.");
  832. let result = {
  833. id:data.id,
  834. state:"complete",
  835. status:200,
  836. result:"You done it!"
  837. }
  838. resolve(result);
  839. }, 3000);
  840. });
  841. }
  842. function service3(data){
  843. return new Promise((resolve, reject)=>{
  844. console.log(`service3 started with data ${JSON.stringify(data)}`);
  845. let timeout = setTimeout(()=>{
  846. clearTimeout(timeout);
  847. console.log("service3 completed.");
  848. let result = {
  849. id:data.id,
  850. state:"complete",
  851. status:200,
  852. result:"You done it!"
  853. }
  854. resolve(result);
  855. }, 2000);
  856. });
  857. }
  858. function service4(data){
  859. return new Promise((resolve, reject)=>{
  860. console.log(`service4 started with data ${JSON.stringify(data)}`);
  861. let timeout = setTimeout(()=>{
  862. clearTimeout(timeout);
  863. console.log("service4 completed.");
  864. let result = {
  865. id:data.id,
  866. state:"complete",
  867. status:200,
  868. result:"You done it!"
  869. }
  870. resolve(result);
  871. }, 2500);
  872. });
  873. }</script></body>
  874. </html>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement