001package com.ganteater.ae.processor;
002
003import java.awt.Toolkit;
004import java.awt.datatransfer.Clipboard;
005import java.awt.datatransfer.DataFlavor;
006import java.awt.datatransfer.StringSelection;
007import java.io.BufferedReader;
008import java.io.ByteArrayOutputStream;
009import java.io.File;
010import java.io.FileInputStream;
011import java.io.FileNotFoundException;
012import java.io.FileOutputStream;
013import java.io.FileReader;
014import java.io.IOException;
015import java.io.InputStream;
016import java.io.InputStreamReader;
017import java.io.OutputStream;
018import java.io.StringReader;
019import java.io.StringWriter;
020import java.io.UnsupportedEncodingException;
021import java.net.Inet4Address;
022import java.net.InetAddress;
023import java.net.NetworkInterface;
024import java.net.Socket;
025import java.net.SocketException;
026import java.net.SocketTimeoutException;
027import java.net.URL;
028import java.net.URLConnection;
029import java.net.UnknownHostException;
030import java.security.NoSuchAlgorithmException;
031import java.security.SecureRandom;
032import java.text.DateFormat;
033import java.text.SimpleDateFormat;
034import java.util.ArrayList;
035import java.util.Arrays;
036import java.util.Base64;
037import java.util.Calendar;
038import java.util.Collection;
039import java.util.Collections;
040import java.util.Date;
041import java.util.Enumeration;
042import java.util.HashMap;
043import java.util.HashSet;
044import java.util.Iterator;
045import java.util.LinkedHashMap;
046import java.util.LinkedHashSet;
047import java.util.List;
048import java.util.Map;
049import java.util.Map.Entry;
050import java.util.Properties;
051import java.util.Random;
052import java.util.Set;
053import java.util.StringTokenizer;
054import java.util.TimeZone;
055import java.util.UUID;
056import java.util.concurrent.Executors;
057import java.util.concurrent.ThreadPoolExecutor;
058import java.util.concurrent.TimeUnit;
059import java.util.regex.Matcher;
060import java.util.regex.Pattern;
061import java.util.stream.Collectors;
062import java.util.stream.IntStream;
063
064import javax.swing.JOptionPane;
065import javax.xml.transform.Source;
066import javax.xml.transform.Transformer;
067import javax.xml.transform.TransformerFactory;
068import javax.xml.transform.stream.StreamResult;
069import javax.xml.transform.stream.StreamSource;
070
071import org.apache.commons.collections.CollectionUtils;
072import org.apache.commons.collections.MapUtils;
073import org.apache.commons.io.FileUtils;
074import org.apache.commons.io.IOUtils;
075import org.apache.commons.io.filefilter.IOFileFilter;
076import org.apache.commons.io.filefilter.TrueFileFilter;
077import org.apache.commons.jexl3.JexlBuilder;
078import org.apache.commons.jexl3.JexlContext;
079import org.apache.commons.jexl3.JexlEngine;
080import org.apache.commons.jexl3.JexlExpression;
081import org.apache.commons.jexl3.MapContext;
082import org.apache.commons.lang.ArrayUtils;
083import org.apache.commons.lang.BooleanUtils;
084import org.apache.commons.lang.ObjectUtils;
085import org.apache.commons.lang.StringEscapeUtils;
086import org.apache.commons.lang.StringUtils;
087import org.apache.commons.lang.math.NumberUtils;
088import org.apache.commons.lang3.SystemUtils;
089import org.apache.http.auth.AuthScope;
090import org.apache.http.auth.UsernamePasswordCredentials;
091import org.apache.http.client.CredentialsProvider;
092import org.apache.http.client.config.RequestConfig;
093import org.apache.http.client.methods.CloseableHttpResponse;
094import org.apache.http.client.methods.HttpGet;
095import org.apache.http.client.methods.HttpPost;
096import org.apache.http.client.methods.HttpRequestBase;
097import org.apache.http.client.protocol.HttpClientContext;
098import org.apache.http.client.utils.URLEncodedUtils;
099import org.apache.http.entity.ByteArrayEntity;
100import org.apache.http.impl.client.BasicCredentialsProvider;
101import org.apache.http.impl.client.CloseableHttpClient;
102import org.apache.http.impl.client.HttpClients;
103import org.apache.sling.commons.json.JSONArray;
104import org.apache.sling.commons.json.JSONException;
105import org.apache.sling.commons.json.JSONObject;
106
107import com.ganteater.ae.AEManager;
108import com.ganteater.ae.AEWorkspace;
109import com.ganteater.ae.ChoiceTaskRunner;
110import com.ganteater.ae.CommandException;
111import com.ganteater.ae.ILogger;
112import com.ganteater.ae.MultiTaskRunDialog;
113import com.ganteater.ae.RecipeListener;
114import com.ganteater.ae.TaskCancelingException;
115import com.ganteater.ae.processor.annotation.CommandDescription;
116import com.ganteater.ae.processor.annotation.CommandExamples;
117import com.ganteater.ae.util.AEUtils;
118import com.ganteater.ae.util.AssertionFailedError;
119import com.ganteater.ae.util.NameValuePairImplementation;
120import com.ganteater.ae.util.RegexPathFilter;
121import com.ganteater.ae.util.TestCase;
122import com.ganteater.ae.util.xml.easyparser.EasyParser;
123import com.ganteater.ae.util.xml.easyparser.EasyUtils;
124import com.ganteater.ae.util.xml.easyparser.Node;
125import com.opencsv.CSVReader;
126
127import okhttp3.MediaType;
128import okhttp3.OkHttpClient;
129import okhttp3.Request.Builder;
130import okhttp3.Response;
131
132public class BaseProcessor extends Processor {
133
134        private Random random;
135
136        public BaseProcessor() {
137        }
138
139        public BaseProcessor(Processor parent) throws CommandException {
140                init(parent);
141        }
142
143        public BaseProcessor(AEManager manager, ILogger log, File baseDir) {
144                super(manager, log, baseDir);
145        }
146
147        public BaseProcessor(Map<String, Object> hashMap, Node node, File baseDir) {
148                super(hashMap, node, baseDir);
149        }
150
151        public BaseProcessor(Node configNode, Map<String, Object> startVariables, RecipeListener listener, File startDir,
152                        ILogger aLog, Processor parent) throws CommandException {
153                super(configNode, startVariables, listener, startDir, aLog, parent);
154        }
155
156        @CommandExamples({ "<Regexp name='type:property' source='type:property' regex='type:string' group='type:integer'/>",
157                        "<Regexp name='type:property' source='type:property' regex='type:string' />" })
158        public void runCommandRegexp(final Node action) throws Throwable {
159                final String theName = attr(action, "name");
160                String regex = replaceProperties(action.getAttribute("regex"));
161                int group = Integer.parseInt(attr(action, "group", "0"));
162                Object sourceObj = attrValue(action, "source");
163                if (sourceObj == null) {
164                        throw new IllegalArgumentException("The 'source' value must not be null.");
165                }
166
167                if (sourceObj instanceof String) {
168                        sourceObj = new String[] { (String) sourceObj };
169                }
170
171                Pattern pattern = Pattern.compile(regex);
172                List<String> result = new ArrayList<>();
173                if (sourceObj instanceof String[]) {
174                        String[] sources = (String[]) sourceObj;
175
176                        for (String source : sources) {
177                                Matcher matcher = pattern.matcher(source);
178                                if (matcher.find()) {
179                                        String theText = matcher.group(group);
180                                        result.add(theText);
181                                }
182                        }
183                }
184                setVariableValue(theName, result);
185        }
186
187        @CommandExamples({ "<Replace name='type:property' oldChar='type:integer' newChar='type:integer'/>",
188                        "<Replace name='type:property' regex='type:regex' replacement='type:string'/>",
189                        "<Replace name='type:property' unescape='enum:java|csv|html|javascript|xml' />",
190                        "<Replace name='type:property' escape='enum:java|csv|html|javascript|xml|sql' />" })
191        public void runCommandReplace(final Node command) throws Throwable {
192                String name = replaceProperties(command.getAttribute("name"));
193
194                Object variableValue = getVariableValue(name);
195                String value = null;
196                if (variableValue instanceof String) {
197                        value = (String) variableValue;
198                } else if (variableValue instanceof byte[]) {
199                        value = new String((byte[]) variableValue);
200                } else {
201                        value = ObjectUtils.toString(variableValue);
202                }
203                value = replaceProperties(value);
204
205                final String theOldChar = replaceProperties(command.getAttribute("oldChar"));
206                final String theNewChar = replaceProperties(command.getAttribute("newChar"));
207                if (theOldChar != null) {
208                        value = value.replace(theOldChar, theNewChar);
209                }
210
211                final String regex = replaceProperties(command.getAttribute("regex"));
212                final String replacement = replaceProperties(command.getAttribute("replacement"));
213                if (regex != null && value != null) {
214                        value = value.replaceAll(regex, replacement);
215                }
216
217                String unescape = attr(command, "unescape");
218                if (unescape != null) {
219                        unescape = unescape.toLowerCase();
220                        switch (unescape) {
221                        case "csv": {
222                                value = StringEscapeUtils.unescapeCsv(value);
223                                break;
224                        }
225                        case "html": {
226                                value = StringEscapeUtils.unescapeHtml(value);
227                                break;
228                        }
229                        case "java": {
230                                value = StringEscapeUtils.unescapeJava(value);
231                                break;
232                        }
233                        case "javascript": {
234                                value = StringEscapeUtils.unescapeJavaScript(value);
235                                break;
236                        }
237                        case "xml": {
238                                value = StringEscapeUtils.unescapeXml(value);
239                                break;
240                        }
241                        }
242                }
243
244                String escape = attr(command, "escape");
245                if (escape != null) {
246                        escape = escape.toLowerCase();
247                        switch (escape) {
248                        case "csv": {
249                                value = StringEscapeUtils.escapeCsv(value);
250                                break;
251                        }
252                        case "html": {
253                                value = StringEscapeUtils.escapeHtml(value);
254                                break;
255                        }
256                        case "java": {
257                                value = StringEscapeUtils.escapeJava(value);
258                                break;
259                        }
260                        case "javascript": {
261                                value = StringEscapeUtils.escapeJavaScript(value);
262                                break;
263                        }
264                        case "sql": {
265                                value = StringEscapeUtils.escapeSql(value);
266                                break;
267                        }
268                        case "xml": {
269                                value = StringEscapeUtils.escapeXml(value);
270                                break;
271                        }
272                        }
273                }
274
275                setVariableValue(name, value);
276        }
277
278        @CommandDescription("The Clipboard command swaps the contents of the system clipboard with the value of the variable specified by the name attribute")
279        @CommandExamples({ "<Clipboard name='type:property'/>" })
280        public void runCommandClipboard(final Node action) throws Exception {
281                String name = action.getAttribute("name");
282                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
283                String varValue = null;
284                if (name != null) {
285                        varValue = String.valueOf(attrValue(action, "name"));
286
287                        String clipboardValue = (String) Toolkit.getDefaultToolkit().getSystemClipboard()
288                                        .getData(DataFlavor.stringFlavor);
289
290                        setVariableValue(name, clipboardValue);
291                }
292                StringSelection stringSelection = new StringSelection(StringUtils.defaultIfEmpty(varValue, StringUtils.EMPTY));
293                clipboard.setContents(stringSelection, null);
294        }
295
296        @CommandExamples({
297                        "<CheckValue actual='type:string' expected='type:string' mode='enum:normal|trimLines' condition='enum:unequal|equal' onErrorMsg='type:string'/>",
298                        "<CheckValue value='type:string' regex='type:regex' onErrorMsg='type:string'/>" })
299        public void runCommandCheckValue(final Node action) throws Throwable {
300
301                String actual = attr(action, "actual");
302                String expected = attr(action, "expected");
303                final String errorMsg = replaceProperties(action.getAttribute("onErrorMsg"));
304
305                if (actual != null && expected != null) {
306
307                        final String theRegex = replaceProperties(action.getAttribute("regex"));
308
309                        if (theRegex == null) {
310                                final boolean theTrimLines = "trimLines".equals(replaceProperties(action.getAttribute("mode")));
311                                try {
312                                        if (theTrimLines) {
313                                                actual = getTrimmedLines(actual);
314                                                expected = getTrimmedLines(expected);
315                                        }
316
317                                        final boolean unequal = "unequal".equals(replaceProperties(action.getAttribute("condition")));
318                                        if (unequal) {
319                                                boolean equals = StringUtils.equals(expected, actual);
320                                                TestCase.assertFalse(errorMsg, equals);
321                                        } else {
322                                                TestCase.assertEquals(errorMsg, expected, actual);
323                                        }
324                                } catch (final Throwable e) {
325                                        taskNode(action, false);
326                                        throw e;
327                                }
328                        } else {
329                                final String theValue = replaceProperties(action.getAttribute("value"));
330                                final Pattern p = Pattern.compile(theRegex);
331                                final Matcher m = p.matcher(theValue);
332                                if (m.find() == false) {
333                                        taskNode(action, false);
334                                        TestCase.fail("Regex validation failed. Regex:" + theRegex + ", value:" + theValue);
335                                }
336                        }
337                } else {
338                        throw new AssertionFailedError(errorMsg);
339                }
340
341        }
342
343        @CommandExamples({ "<CheckNotNull name='type:property' onErrorMsg='type:string'/>" })
344        public void runCommandCheckNotNull(final Node aCurrentAction) throws Throwable {
345                final String theName = replaceProperties(aCurrentAction.getAttribute("name"));
346                final String theErrorMsg = replaceProperties(aCurrentAction.getAttribute("onErrorMsg"));
347                Object theValue = getVariableValue(theName);
348                if (theValue != null && theValue instanceof String[] && ((String[]) theValue).length == 0) {
349                        theValue = null;
350                }
351                try {
352                        TestCase.assertNotNull(theErrorMsg, theValue);
353                } catch (final Throwable e) {
354                        taskNode(aCurrentAction, false);
355                        throw e;
356                }
357        }
358
359        @CommandExamples({ "<Choice name='type:property'>" + "<Task name='type:string'>...</Task></Choice>",
360                        "<Choice name='type:property' mode='enum:default|table'>" + "<Task name='type:string'>...</Task></Choice>",
361                        "<Choice name='type:property' mode='enum:default|table' type='setup'>"
362                                        + "<Task name='type:string'/></Choice>",
363                        "<Choice name='type:property' mode='enum:default|table' type='call-task'>"
364                                        + "<Task name='type:recipe'/></Choice>",
365                        "<Choice name='type:property'><Task name='type:string'>...</Task><Started>...</Started></Choice>",
366                        "<Choice name='type:property' runAllTaskName='type:string' mode='enum:default|table' type='enum:default|call-task'>"
367                                        + "<Task name='type:string'>...</Task><Started>...</Started></Choice>" })
368        public void runCommandChoice(final Node aCurrentAction) throws Throwable {
369
370                final String nameOfChoice = replaceProperties(aCurrentAction.getAttribute("name"));
371                Object preset = getVariableValue(nameOfChoice);
372
373                String description = replaceProperties(aCurrentAction.getAttribute("description"));
374
375                final String theRunAllTaskName = replaceProperties(aCurrentAction.getAttribute("runAllTaskName"));
376                String type = replaceProperties(aCurrentAction.getAttribute("type"));
377                final boolean callType = "call-task".equals(type);
378
379                final Map<String, Node> tablesNodes = new LinkedHashMap<>();
380
381                final String theMode = replaceProperties(aCurrentAction.getAttribute("mode"));
382
383                final Node[] taskNodes = aCurrentAction.getNodes("Task");
384                for (int i = 0; i < taskNodes.length; i++) {
385                        final String name = replaceProperties(taskNodes[i].getAttribute("name"));
386
387                        if ("table".equals(theMode) && name != null && name.equals(theRunAllTaskName)) {
388                                continue;
389                        }
390
391                        if (!callType || getListener().getManager().getTestPath(name) != null) {
392                                tablesNodes.put(name, taskNodes[i]);
393                        }
394                }
395
396                final String[] names = tablesNodes.keySet().toArray(new String[tablesNodes.size()]);
397
398                debug("Task list: " + nameOfChoice);
399
400                final List<String> activeNodes = new ArrayList<>();
401
402                if ("table".equals(theMode) || "hidden".equals(theMode)) {
403
404                        final String exceptionIgnore = replaceProperties(aCurrentAction.getAttribute("exception"));
405                        boolean startsWith = false;
406                        if (exceptionIgnore != null) {
407                                startsWith = exceptionIgnore.startsWith("ignore");
408                        }
409
410                        ChoiceTaskRunner choiceRunner = new ChoiceTaskRunner(nameOfChoice, preset == null);
411                        boolean visible = !"hidden".equals(theMode);
412                        final MultiTaskRunDialog inputSelectChoice = this.recipeListener.getManager().tasksChoice(choiceRunner,
413                                        names, startsWith, preset, this, visible);
414
415                        try {
416                                final Iterator<String> selectedTasks = inputSelectChoice.getSelectedTasks();
417
418                                List<String> selectedTaskNames = new ArrayList<>();
419                                while (selectedTasks.hasNext()) {
420                                        String selectedTask = selectedTasks.next();
421                                        activeNodes.add(selectedTask);
422
423                                        selectedTaskNames.add(selectedTask);
424                                        inputSelectChoice.begin(selectedTask);
425
426                                        try {
427                                                Node action = tablesNodes.get(selectedTask);
428                                                startCommandInformation(action);
429
430                                                if (callType) {
431                                                        runCommandTask(action);
432                                                } else {
433                                                        taskNode(action, false);
434                                                }
435                                                inputSelectChoice.end(selectedTask, true);
436
437                                        } catch (final Throwable e) {
438                                                inputSelectChoice.end(selectedTask, false);
439                                                if (inputSelectChoice.isExceptionIgnore() == false) {
440                                                        throw e;
441                                                }
442                                        }
443                                }
444
445                                if ("setup".equals(type)) {
446                                        setVariableValue(nameOfChoice, selectedTaskNames.toArray(new String[selectedTaskNames.size()]));
447                                }
448                        } finally {
449                                inputSelectChoice.done();
450                                if (inputSelectChoice.isStarted()) {
451                                        Node[] nodes = aCurrentAction.getNodes("Started");
452                                        for (Node node : nodes) {
453                                                taskNode(node);
454                                        }
455                                }
456                        }
457
458                } else {
459                        if (this.recipeListener.getManager() != null) {
460                                boolean notifyMe = this.recipeListener.isNotifyMe();
461                                description = StringUtils.defaultString(description, nameOfChoice);
462                                final String inputSelectChoice = this.recipeListener.getManager().inputChoice(nameOfChoice, description,
463                                                names, ObjectUtils.toString(preset, null), this, notifyMe);
464                                if (inputSelectChoice != null) {
465                                        activeNodes.add(inputSelectChoice);
466                                }
467                        }
468
469                        debug("Select task: " + (activeNodes != null ? activeNodes : "<not chosen>"));
470
471                        if (!activeNodes.isEmpty()) {
472                                if (activeNodes.get(0).equals(theRunAllTaskName) == false) {
473
474                                        if (callType) {
475                                                runCommandTask(tablesNodes.get(activeNodes.get(0)));
476                                        } else {
477                                                taskNode(tablesNodes.get(activeNodes.get(0)));
478                                        }
479
480                                } else {
481                                        for (int i = 0; i < names.length; i++) {
482                                                final String theActiveNode = names[i];
483                                                if (theActiveNode.equals(theRunAllTaskName) == false) {
484                                                        if (callType) {
485                                                                runCommandTask(tablesNodes.get(theActiveNode));
486                                                        } else {
487                                                                taskNode(tablesNodes.get(theActiveNode));
488                                                        }
489                                                }
490                                        }
491                                }
492                        }
493                }
494
495                setVariableValue(nameOfChoice, activeNodes);
496        }
497
498        @CommandExamples({ "<Runnable name='type:property' threadLog='type:boolean'>...code...</Runnable>" })
499        public void runCommandRunnable(final Node aCurrentAction) throws CommandException {
500                final String theName = replaceProperties(aCurrentAction.getAttribute("name"));
501                String theNameThreads = getTestName(aCurrentAction);
502                if (theNameThreads == null) {
503                        theNameThreads = "Thread";
504                }
505
506                boolean threadLog = Boolean.valueOf(StringUtils.defaultIfBlank(attr(aCurrentAction, "threadLog"), "false"));
507                ILogger theLog = this.log;
508                if (threadLog) {
509                        theLog = this.recipeListener.createLog(theNameThreads, false);
510                }
511
512                Node task = new Node("Task");
513                task.setAttribute("description", aCurrentAction.getAttribute("name"));
514                task.addAll(aCurrentAction);
515                final TaskProcessorThread runnable = new TaskProcessorThread(this, task, getBaseDir(), theLog);
516                setVariableValue(theName, runnable);
517        }
518
519        @CommandExamples({ "<Sort name='type:property' type='enum:natural|random' />" })
520        public void runCommandSort(final Node aCurrentAction) {
521                final String theName = replaceProperties(aCurrentAction.getAttribute("name"));
522                String theType = replaceProperties(aCurrentAction.getAttribute("type"));
523                if (theType == null) {
524                        theType = "natural";
525                }
526
527                final Object theObject = getVariableValue(theName);
528                if (theObject instanceof String[]) {
529                        String[] theArray = (String[]) theObject;
530                        if ("natural".equals(theType)) {
531                                Arrays.sort(theArray);
532                        } else if ("random".equals(theType)) {
533                                theArray = randomSort(theArray);
534                        } else {
535                                throw new IllegalArgumentException("Sort type should be following value: natural, random.");
536                        }
537                        setVariableValue(theName, theArray);
538                }
539        }
540
541        @CommandExamples({ "<CheckInArray value='type:string' array='type:property' onErrorMsg='type:string'/>" })
542        public void runCommandCheckInArray(final Node aCurrentAction) throws Throwable {
543                final String theValue = replaceProperties(aCurrentAction.getAttribute("value"));
544                final String theArrayName = replaceProperties(aCurrentAction.getAttribute("array"));
545                final String theErrorMsg = replaceProperties(aCurrentAction.getAttribute("onErrorMsg"));
546                final Object theObject = checkForArray(getVariableValue(theArrayName));
547
548                if (theObject != null && theObject instanceof Object[]) {
549                        final Object[] theValues = (Object[]) theObject;
550                        Arrays.sort(theValues);
551                        try {
552                                TestCase.assertTrue(theErrorMsg, Arrays.binarySearch(theValues, theValue) >= 0);
553                        } catch (final Throwable e) {
554                                taskNode(aCurrentAction, false);
555                                throw e;
556                        }
557                }
558        }
559
560        @CommandDescription("The `Tokenizer` command is used to split a variable by the name specified in the `name` attribute. "
561                        + "The variable's value will be split by the delimiter specified in the `delim` attribute. The default separator is the `;` character."
562                        + "The result will be an array of strings.")
563        @CommandExamples({ "<Tokenizer name='type:property' delim='type:string'/>" })
564        public void runCommandTokenizer(final Node aCurrentVar) {
565                final Object value = attrValue(aCurrentVar, "name");
566                String theDelimAttribut = aCurrentVar.getAttribute("delim");
567                if (theDelimAttribut == null) {
568                        theDelimAttribut = ";";
569                }
570                if (value != null) {
571                        String theLine = null;
572                        if (value instanceof String) {
573                                theLine = (String) value;
574                        } else if (value instanceof String[] && ((String[]) value).length == 1) {
575                                theLine = ((String[]) value)[0];
576                        }
577                        final ArrayList<String> theArrayList = new ArrayList<String>();
578                        if (theLine != null) {
579                                final StringTokenizer theStringTokenizer = new StringTokenizer(theLine, theDelimAttribut);
580                                while (theStringTokenizer.hasMoreTokens()) {
581                                        theArrayList.add(theStringTokenizer.nextToken());
582                                }
583                                final String[] theArray = new String[theArrayList.size()];
584                                for (int i = 0; i < theArrayList.size(); i++) {
585                                        if (isStopped()) {
586                                                break;
587                                        }
588                                        theArray[i] = theArrayList.get(i);
589                                }
590                                setVariableValue(attr(aCurrentVar, "name"), theArray);
591                        } else {
592                                debug("Tokenized empty string is ignored.");
593                        }
594                }
595        }
596
597        @CommandExamples({ "<Date name='type:string' format='type:string' />",
598                        "<Date name='type:string' format='type:string' source='type:property' sformat='type:string' />",
599                        "<Date name='type:string' format='type:string' shift='type:time' />" })
600        public void runCommandDate(final Node aCurrentAction) throws Throwable {
601                final String name = replaceProperties(aCurrentAction.getAttribute("name"));
602                String value = replaceProperties(aCurrentAction.getAttribute("value"));
603                if (value == null) {
604                        String source = aCurrentAction.getAttribute("source");
605                        if (source != null) {
606                                value = (String) getVariableValue(replaceProperties(source));
607                        }
608                }
609
610                if (value == null) {
611                        Object variableValue = getVariableValue(name);
612                        if (variableValue != null) {
613                                value = ObjectUtils.toString(variableValue);
614                        }
615                }
616
617                String format = replaceProperties(aCurrentAction.getAttribute("format"));
618                String sformat = StringUtils.defaultString(replaceProperties(aCurrentAction.getAttribute("sformat")), format);
619
620                SimpleDateFormat dateFor = null;
621                if (sformat != null && !"ms".equals(sformat)) {
622                        dateFor = new SimpleDateFormat(sformat);
623                        dateFor.setTimeZone(TimeZone.getTimeZone("UTC"));
624                }
625                String stringDate = null;
626                if (value == null) {
627                        if (dateFor != null) {
628                                stringDate = dateFor.format(new Date());
629                        } else {
630                                stringDate = Long.toString(new Date().getTime());
631                        }
632                        value = stringDate;
633                }
634
635                final String shift = replaceProperties(StringUtils.trim(aCurrentAction.getAttribute("shift")));
636
637                Date date;
638                if (dateFor != null) {
639                        date = dateFor.parse(value);
640                } else {
641                        date = new Date(Long.parseLong(value));
642                }
643
644                if (shift != null) {
645                        Calendar c = Calendar.getInstance();
646                        c.setTime(date);
647                        String substring = StringUtils.substring(shift, 0, shift.length() - 1);
648                        int shiftValue = Integer.parseInt(substring);
649                        switch (shift.charAt(shift.length() - 1)) {
650                        case 's':
651                                c.add(Calendar.SECOND, shiftValue);
652                                break;
653                        case 'm':
654                                c.add(Calendar.MINUTE, shiftValue);
655                                break;
656                        case 'h':
657                                c.add(Calendar.HOUR, shiftValue);
658                                break;
659                        case 'd':
660                                c.add(Calendar.DAY_OF_MONTH, shiftValue);
661                                break;
662                        case 'w':
663                                c.add(Calendar.WEEK_OF_MONTH, shiftValue);
664                                break;
665                        case 'M':
666                                c.add(Calendar.MONTH, shiftValue);
667                                break;
668                        case 'Y':
669                                c.add(Calendar.YEAR, shiftValue);
670                                break;
671                        }
672
673                        date = c.getTime();
674                }
675
676                if (format != null && !"ms".equals(format)) {
677                        dateFor = new SimpleDateFormat(format);
678                        dateFor.setTimeZone(TimeZone.getTimeZone("UTC"));
679                        stringDate = dateFor.format(date);
680                } else {
681                        stringDate = Long.toString(date.getTime());
682                }
683
684                setVariableValue(name, stringDate);
685        }
686
687        @CommandExamples({ "<Wait delay='type:time'/>", "<Wait/>" })
688        public void runCommandWait(final Node aCurrentAction) {
689                final long theValue = attrTime(aCurrentAction, "delay", "0");
690                if (theValue > 0) {
691                        quietWait(theValue);
692                } else {
693                        pause();
694                }
695        }
696
697        @CommandDescription("The View command defines a view component to precentation output date provided by Out command.")
698        @CommandExamples({ "<View name='type:string' reuse='type:boolean' type='type:view' />" })
699        public void runCommandView(final Node aCurrentAction) throws Throwable {
700                this.recipeListener.runCommandView(replaceAttributes(aCurrentAction));
701        }
702
703        @CommandExamples({ "<Formater name='type:property' type='enum:xml|http-get-request'/>" })
704        public void runCommandFormater(final Node aCurrentAction) throws Throwable {
705                final String theNameAttribut = replaceProperties(aCurrentAction.getAttribute("name"));
706                final String theTypeAttribut = replaceProperties(aCurrentAction.getAttribute("type"));
707                Object theValue = getVariableValue(theNameAttribut);
708                if (theValue instanceof String) {
709                        if ("json".equals(theTypeAttribut)) {
710                                theValue = AEUtils.format(ObjectUtils.toString(theValue));
711                        } else {
712                                theValue = new String[] { (String) theValue };
713                        }
714
715                        setVariableValue(theNameAttribut, theValue);
716                }
717                if (theValue instanceof String[]) {
718                        final String[] theValueArray = (String[]) theValue;
719                        for (int i = 0; i < theValueArray.length; i++) {
720                                if (isStopped()) {
721                                        break;
722                                }
723                                if ("xml".equals(theTypeAttribut)) {
724                                        final EasyParser theParser = new EasyParser();
725                                        String theCurrentValue = theValueArray[i];
726                                        theCurrentValue = prepareXml(theCurrentValue);
727                                        final Node object = theParser.getObject(theCurrentValue);
728                                        theValueArray[i] = object.getXMLText();
729                                }
730                        }
731                        if (theValueArray.length > 2) {
732                                setVariableValue(theNameAttribut, theValueArray);
733                        } else if (theValueArray.length == 1) {
734                                setVariableValue(theNameAttribut, theValueArray[0]);
735                        } else {
736                                setVariableValue(theNameAttribut, null);
737                        }
738                }
739        }
740
741        @CommandExamples({ "<Trim name='type:property'/>" })
742        public void runCommandTrim(final Node aCurrentAction) throws Throwable {
743                String name = replaceProperties(aCurrentAction.getAttribute("name"));
744                Object value = getVariableValue(name);
745                if (value instanceof String) {
746                        value = StringUtils.trimToNull(ObjectUtils.toString(value));
747                } else if (value instanceof String[]) {
748                        String[] array = (String[]) value;
749                        if (array.length == 0) {
750                                value = null;
751                        } else {
752                                List<String> list = new ArrayList<>();
753                                for (String object : array) {
754                                        String val = ObjectUtils.toString(object);
755                                        if (StringUtils.isNotBlank(val)) {
756                                                list.add(object);
757                                        }
758                                }
759                                value = list.toArray(new String[list.size()]);
760                        }
761                } else if (value instanceof List) {
762                        @SuppressWarnings("unchecked")
763                        List<String> array = (List<String>) value;
764                        if (array.size() == 0) {
765                                value = null;
766                        } else {
767                                List<String> list = new ArrayList<>();
768                                for (String object : array) {
769                                        String val = ObjectUtils.toString(object);
770                                        if (StringUtils.isNotBlank(val)) {
771                                                list.add(object);
772                                        }
773                                }
774                                value = list.toArray(new String[list.size()]);
775                        }
776                }
777                applyResult(aCurrentAction, name, value);
778        }
779
780        @CommandExamples({ "<Xslt name='type:property' xml='type:property' xsl='type:property'/>" })
781        public void runCommandXslt(final Node aCurrentAction) throws Throwable {
782                final String theNameAttribut = attr(aCurrentAction, "name");
783                final String theXmlAttribut = ObjectUtils.toString(attrValue(aCurrentAction, "xml"));
784                final String theXslAttribut = ObjectUtils.toString(attrValue(aCurrentAction, "xsl"));
785                final StringWriter theStringWriter = new StringWriter();
786
787                if (theXmlAttribut == null || theXmlAttribut.trim().length() == 0 || "null".equals(theXmlAttribut)) {
788                        debug("Xml document is empty, transform disable.");
789                        return;
790                }
791
792                if (theXslAttribut == null || theXslAttribut.trim().length() == 0 || "null".equals(theXslAttribut)) {
793                        debug("Xsl document is empty, transform disable.");
794                        return;
795                }
796
797                try {
798                        final Source xslSource = new StreamSource(new StringReader(theXslAttribut));
799                        final Transformer transformer = TransformerFactory.newInstance().newTransformer(xslSource);
800                        final Source xmlSource = new StreamSource(new StringReader(theXmlAttribut));
801
802                        transformer.transform(xmlSource, new StreamResult(theStringWriter));
803
804                } catch (final Throwable e) {
805                        debug("XML Data to transformation:\n" + theXmlAttribut);
806                        debug("XSL:\n" + theXslAttribut);
807                        this.log.error("Xsl transformation is failed.", e);
808                        throw e;
809                }
810
811                setVariableValue(theNameAttribut, theStringWriter.toString());
812        }
813
814        @CommandDescription("`Out` command use for output a variable value or text.\r\n"
815                        + "- to output the value of a variable, you must use the `name` attribute with the name of the variable. "
816                        + "- to output text, you should use inner text.\r\n"
817                        + "- `level` attribute defines log level, default log level = `info`\r\n"
818                        + "- `type` attribute sets type for log view.\r\n"
819                        + "- `file` attribute is used for output data in the file.\r\n"
820                        + "`Out` command can be used for output information to the special output view, for this need to use an optional `view` "
821                        + "attribute with a view name. The view should be defined by the `View` command.")
822        @CommandExamples({ "<Out name='type:property' />", "<Out view='type:string'> ... </Out>",
823                        "<Out name='type:property' type='enum:txt|html|xml|json|~json|url|path|uri|csv'/>",
824                        "<Out view='type:string'>...</Out>", "<Out name='type:property' level='enum:info|debug|warn|error'/>",
825                        "<Out name='type:property' level='enum:info|debug|warn|error'> ... </Out>",
826                        "<Out level='enum:info|debug|warn|error'>... $var{...} ... </Out>",
827                        "<Out file='type:path' append='enum:true|false' encoding='UTF-8'/>" })
828        public void runCommandOut(final Node command) throws UnsupportedEncodingException, IOException {
829                final String name = attr(command, "name");
830                final String description = attr(command, "description");
831                String theOutFileNameAttribut = attr(command, "file");
832
833                String logLevel = command.getAttribute("level");
834                logLevel = replaceProperties(logLevel);
835
836                if (logLevel == null) {
837                        logLevel = StringUtils.defaultIfBlank(getVariableString(DEFAULT_LOG_LEVEL_NAME), "info");
838                }
839
840                String type = attr(command, "type");
841
842                FileOutputStream theFileOutputStream = null;
843                try {
844                        if (theOutFileNameAttribut != null) {
845                                String theAppendAttribut = replaceProperties(command.getAttribute("append"));
846                                if (theAppendAttribut == null) {
847                                        theAppendAttribut = "true";
848                                }
849                                theOutFileNameAttribut = replaceProperties(theOutFileNameAttribut);
850                                final File file = getFile(theOutFileNameAttribut);
851                                file.getParentFile().mkdirs();
852                                theFileOutputStream = new FileOutputStream(file, "true".equals(theAppendAttribut));
853                        }
854                        String theEncoding = replaceProperties(command.getAttribute("encoding"));
855                        if (theEncoding == null) {
856                                theEncoding = "UTF-8";
857                        }
858
859                        String theTextOut = null;
860                        String varName = name;
861                        final Node[] nodes = command.getTextNodes();
862
863                        if (nodes.length > 0) {
864                                Node node = nodes[0];
865                                theTextOut = replaceProperties(node.getText());
866                        } else if (command.size() > 0) {
867                                String innerXMLText = command.getInnerXMLText();
868                                Node node = new EasyParser().getObject(innerXMLText);
869                                EasyUtils.removeTagId(node);
870                                theTextOut = replaceProperties(node.toString());
871                        }
872
873                        if (theTextOut != null) {
874                                if (name != null) {
875                                        varName = theTextOut + ": " + varName;
876                                }
877                                if (theFileOutputStream != null) {
878                                        theFileOutputStream.write(theTextOut.getBytes(theEncoding));
879                                }
880
881                                Properties attributes = replaceAttributes(command);
882                                this.recipeListener.outToView(this, attributes, theTextOut);
883                        }
884
885                        if (name != null) {
886                                Object theValue = getVariableValue(name);
887
888                                if (theValue instanceof byte[]) {
889                                        if (theFileOutputStream != null) {
890                                                final byte[] value = (byte[]) theValue;
891                                                theFileOutputStream.write(value);
892                                                theFileOutputStream.close();
893                                                return;
894                                        } else {
895                                                outputLog(StringUtils.defaultIfEmpty(description, varName), logLevel, theValue, type);
896                                                return;
897                                        }
898                                }
899
900                                if (theValue == null) {
901                                        outputLog(StringUtils.defaultIfEmpty(description, varName), logLevel, null, type);
902
903                                } else if (theValue instanceof String) {
904                                        if (theFileOutputStream == null) {
905                                                outputLog(StringUtils.defaultIfEmpty(description, varName), logLevel, theValue.toString(),
906                                                                type);
907                                        } else {
908                                                theFileOutputStream.write(theValue.toString().getBytes(theEncoding));
909                                        }
910
911                                        this.recipeListener.outToView(this, replaceAttributes(command), theValue);
912
913                                } else if (theValue instanceof String[]) {
914                                        final StringBuffer theBuffer = new StringBuffer();
915
916                                        final String[] theValueArray = (String[]) theValue;
917                                        for (int i = 0; i < theValueArray.length; i++) {
918                                                if (isStopped()) {
919                                                        break;
920                                                }
921
922                                                if (theBuffer.length() > 0) {
923                                                        theBuffer.append("\n");
924                                                }
925                                                theBuffer.append(theValueArray[i]);
926
927                                                Properties params = replaceAttributes(command);
928                                                String msg = theValueArray[i];
929                                                this.recipeListener.outToView(this, params, msg);
930                                        }
931                                        if (theFileOutputStream == null) {
932                                                outputLog(StringUtils.defaultIfEmpty(description, varName), logLevel, theBuffer.toString(),
933                                                                type);
934                                        } else {
935                                                theFileOutputStream.write(theBuffer.toString().getBytes(theEncoding));
936                                        }
937                                } else {
938                                        outputLog(StringUtils.defaultIfEmpty(description, varName), logLevel, theValue, type);
939                                        this.recipeListener.outToView(this, replaceAttributes(command), theValue);
940                                }
941                        } else {
942                                outputLog(StringUtils.defaultIfEmpty(description, varName), logLevel, theTextOut, type);
943                        }
944                } finally {
945                        if (theFileOutputStream != null) {
946                                theFileOutputStream.flush();
947                                theFileOutputStream.close();
948                        }
949                }
950        }
951
952        @CommandDescription("The <About> information tag (non-executable) is used to display detailed information about a recipe. "
953                        + "This tag includes a <description> element, which provides a description of the recipe. "
954                        + "The description can be written in plain text or in Markdown format and displayed as additional information on the menu page. "
955                        + "In Markdown format, avoid indenting text lines to properly recognize the description type. "
956                        + "Each text line must be properly formatted and less than 120 characters long.")
957        @CommandExamples({ "<About><description>...</description></About>",
958                        "<About><attach><file name='type:url|path' /></attach></About>",
959                        "<About><author name='type:string' email='type:string' messager='type:string' phone='type:string'/>"
960                                        + "<attach><file name='type:url|path' width='type:number' height='type:number'/></attach></About>" })
961        public void runCommandAbout(final Node aCurrentAction) throws Throwable {
962        }
963
964        @CommandExamples({ "<Exist name='type:property' text='type:string' value='type:string'/>",
965                        "<Exist name='type:property' array='type:property' value='type:string'/>" })
966        public void runCommandExist(final Node aCurrentAction) throws Throwable {
967                final String theName = replaceProperties(aCurrentAction.getAttribute("name"));
968                final String theText = replaceProperties(aCurrentAction.getAttribute("text"));
969                final String theArrayName = replaceProperties(aCurrentAction.getAttribute("array"));
970                final String theValue = replaceProperties(aCurrentAction.getAttribute("value"));
971
972                if (theArrayName != null) {
973                        Object variableValue = getVariableValue(theArrayName);
974                        if (variableValue instanceof List) {
975                                variableValue = ((List) variableValue).toArray(new String[0]);
976                        }
977                        if (variableValue instanceof String[]) {
978                                final String[] variableValueArray = (String[]) variableValue;
979                                for (int i = 0; i < variableValueArray.length; i++) {
980                                        if (theValue.equals(variableValueArray[i])) {
981                                                setVariableValue(theName, "true");
982                                                return;
983                                        }
984                                }
985                        }
986
987                        if (variableValue instanceof String) {
988                                final String value = (String) variableValue;
989                                if (theValue.equals(value)) {
990                                        setVariableValue(theName, "true");
991                                        return;
992                                }
993                        }
994                        setVariableValue(theName, "false");
995                } else {
996                        if (theText == null || theValue == null || theName == null) {
997                                return;
998                        }
999                        final int indexOf = theText.indexOf(theValue);
1000                        setVariableValue(theName, indexOf >= 0 ? "true" : "false");
1001                }
1002        }
1003
1004        @CommandExamples({ "<Load name='type:property' init='console'/>",
1005                        "<Load name='type:property' init='console'/>...</Load>", "<Load name='type:property' file='type:path'/>",
1006                        "<Load name='type:property' file='type:path' timeout='type:time' mode='enum:default|noreplace|escapeJS|bytes'/>",
1007                        "<Load name='type:property' url='type:url' timeout='type:time' mode='enum:default|noreplace|escapeJS|bytes'/>" })
1008        public void runCommandLoad(final Node action) throws Throwable {
1009                final boolean noreplace = "noreplace".equals(action.getAttribute("mode"));
1010                final boolean isArray = "array".equals(action.getAttribute("mode"));
1011                final boolean defaultMode = "default".equals(action.getAttribute("mode"));
1012                final boolean escapeJSValue = "escapeJS".equals(action.getAttribute("mode"));
1013                final boolean bytes = "bytes".equals(action.getAttribute("mode"));
1014                final boolean base64 = "base64".equals(action.getAttribute("mode"));
1015
1016                String theEncoding = replaceProperties(action.getAttribute("encoding"));
1017                if (theEncoding == null) {
1018                        theEncoding = "UTF-8";
1019                }
1020
1021                final String name = replaceProperties(action.getAttribute("name"));
1022                int timeout = (int) attrTime(action, "timeout", "0");
1023
1024                final String theUrl = replaceProperties(action.getAttribute("url"));
1025                if (theUrl != null) {
1026                        URL url = new URL(theUrl);
1027                        long startTime = System.currentTimeMillis();
1028                        while (timeout == 0 || System.currentTimeMillis() < startTime + timeout) {
1029                                if (isStopped()) {
1030                                        break;
1031                                }
1032                                try {
1033                                        URLConnection openConnection = url.openConnection();
1034                                        openConnection.setReadTimeout(timeout);
1035                                        byte[] byteArray = IOUtils.toByteArray(openConnection.getInputStream());
1036                                        if (escapeJSValue) {
1037                                                String data = StringEscapeUtils.escapeJavaScript(new String(byteArray));
1038                                                setVariableValue(name, data);
1039                                        } else {
1040                                                setVariableValue(name, new String(byteArray, theEncoding));
1041                                        }
1042                                        return;
1043
1044                                } catch (IOException e) {
1045                                        if (timeout == 0) {
1046                                                throw e;
1047                                        }
1048                                        quietWait(200);
1049                                }
1050                        }
1051                }
1052
1053                String filePath = (String) attr(action, "file");
1054                final boolean theDialog = isActiveInitFor(action, "console");
1055
1056                if (theDialog) {
1057                        File theFile = null;
1058                        if (filePath != null) {
1059                                theFile = getFile(filePath);
1060                        }
1061                        filePath = this.recipeListener.getManager().inputFile(name, null, theFile, log, this);
1062                }
1063
1064                if (filePath != null) {
1065                        if (base64 || bytes) {
1066                                File file = getFile(filePath);
1067                                debug("Loading file: " + file);
1068                                if (file.exists()) {
1069                                        try (FileInputStream input = new FileInputStream(file)) {
1070                                                Object byteArray = IOUtils.toByteArray(input);
1071                                                if (base64) {
1072                                                        byteArray = Base64.getEncoder().encodeToString((byte[]) byteArray);
1073                                                }
1074                                                setVariableValue(name, byteArray);
1075                                        }
1076                                } else {
1077                                        throw new FileNotFoundException(file.getAbsolutePath());
1078                                }
1079                                return;
1080                        }
1081
1082                        int iterations = timeout / 1000;
1083                        if (iterations == 0) {
1084                                iterations = 1;
1085                        }
1086                        for (int i = 0; i < iterations; i++) {
1087                                if (isStopped()) {
1088                                        break;
1089                                }
1090                                try {
1091
1092                                        if (!action.isEmpty()) {
1093                                                File file = getFile(filePath);
1094                                                try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
1095                                                        String line;
1096                                                        while ((line = reader.readLine()) != null) {
1097                                                                if (this.breakFlag > 0) {
1098                                                                        break;
1099                                                                }
1100                                                                setVariableValue(name, line);
1101                                                                taskNode(action, false);
1102                                                        }
1103                                                } finally {
1104                                                        if (this.breakFlag > 0) {
1105                                                                breakFlag--;
1106                                                        }
1107                                                }
1108
1109                                        } else {
1110
1111                                                String text = AEUtils.loadResource(filePath, getBaseDir(), theEncoding);
1112                                                if (!noreplace) {
1113                                                        text = replaceProperties(text, defaultMode);
1114                                                }
1115
1116                                                if (escapeJSValue) {
1117                                                        String data = StringEscapeUtils.escapeJavaScript(text);
1118                                                        setVariableValue(name, data);
1119                                                } else {
1120                                                        Object val = text;
1121                                                        if (isArray) {
1122                                                                val = StringUtils.split(text, "\n\r");
1123                                                        }
1124
1125                                                        setVariableValue(name, val);
1126                                                }
1127                                        }
1128
1129                                        break;
1130
1131                                } catch (final FileNotFoundException e) {
1132                                        if (timeout == 0) {
1133                                                throw e;
1134                                        }
1135                                        quietWait(1000);
1136                                } catch (final Exception e) {
1137                                        throw e;
1138                                }
1139                        }
1140                } else {
1141                        if (isActiveInitFor(action, "mandatory")) {
1142                                stop();
1143                        }
1144                }
1145
1146        }
1147
1148        @CommandExamples({ "<Remove name='type:property'/>", "<Remove file='type:path'/>",
1149                        "<Remove name='type:property' history='type:boolean'/>" })
1150        public void runCommandRemove(final Node aCurrentAction) throws Throwable {
1151                String theFileName = aCurrentAction.getAttribute("file");
1152                if (theFileName != null) {
1153                        theFileName = replaceProperties(theFileName);
1154                        final File theFile = getFile(theFileName);
1155                        if (theFile.isDirectory()) {
1156                                FileUtils.deleteDirectory(theFile);
1157                        } else {
1158                                if (theFile.delete() == false) {
1159                                        new Exception("File '" + theFile.getAbsolutePath() + "' is not deleted.");
1160                                }
1161                        }
1162                }
1163                String theVarName = aCurrentAction.getAttribute("name");
1164                if (theVarName != null) {
1165                        theVarName = replaceProperties(theVarName);
1166                        setVariableValue(theVarName, null);
1167                        if (Boolean.parseBoolean((attr(aCurrentAction, "history")))) {
1168                                AEWorkspace.getInstance().setDefaultUserConfiguration(".inputValue." + theVarName, null);
1169                        }
1170                }
1171        }
1172
1173        @CommandDescription("Load the web page by `url`. The response is stored in the variable defined by the `name` attribute.\r\n"
1174                        + "The optional `auth` attribute set the 'Authorization' header of request.")
1175        @CommandExamples({ "<Get name='type:property' url='type:url' /> ",
1176                        "<Get name='type:property' url='type:url' timeout='type:time'/> ",
1177                        "<Get name='type:property' url='type:url' auth='type:string' mediaType='type:string' retries='1' timeout='type:time'/> " })
1178        public void runCommandGet(final Node action) throws Throwable {
1179
1180                String mediaTypeStr = attr(action, "mediaType");
1181                mediaTypeStr = StringUtils.defaultIfEmpty(mediaTypeStr, "application/json");
1182
1183                String url = attr(action, "url");
1184                String auth = attr(action, "auth");
1185                int retries = Integer.parseInt(attr(action, "retries", "1"));
1186
1187                Builder builder = new okhttp3.Request.Builder().url(url).get();
1188
1189                if (mediaTypeStr != null) {
1190                        builder.addHeader("Content-Type", mediaTypeStr);
1191                }
1192                if (auth != null) {
1193                        builder.addHeader("Authorization", auth);
1194                }
1195
1196                okhttp3.Request request = builder.build();
1197
1198                for (int i = 0; i < retries; i++) {
1199                        if (isStopped()) {
1200                                break;
1201                        }
1202
1203                        try {
1204                                okhttp3.OkHttpClient.Builder newBuilder = new OkHttpClient().newBuilder();
1205                                long timeout = attrTime(action, "timeout", "0");
1206                                if (timeout > 0) {
1207                                        newBuilder.connectTimeout(timeout, TimeUnit.MILLISECONDS);
1208                                        newBuilder.readTimeout(timeout, TimeUnit.MILLISECONDS);
1209                                        newBuilder.writeTimeout(timeout, TimeUnit.MILLISECONDS);
1210                                }
1211                                OkHttpClient client = newBuilder.build();
1212
1213                                try (Response response = client.newCall(request).execute()) {
1214                                        String value = new String(response.body().bytes(), "UTF-8");
1215                                        String name = attr(action, "name");
1216                                        if (name != null) {
1217                                                setVariableValue(name, value);
1218                                        }
1219                                }
1220
1221                                break;
1222                        } catch (SocketTimeoutException e) {
1223                                if (i == 2) {
1224                                        throw e;
1225                                }
1226                        }
1227                }
1228        }
1229
1230        @CommandDescription("Post the body defined by the `body` attribute to `url`. "
1231                        + "The response is stored in the variable defined by the `name` attribute. "
1232                        + "If the body is not simple, you should create a special variable and pass its value via $var{}.\r\n"
1233                        + "The optional `auth` attribute set the 'Authorization' header of request.")
1234        @CommandExamples({ "<Post name='type:property' url='type:url' body='type:string'/> ",
1235                        "<Post name='type:property' url='type:url' body='type:string' timeout='type:time'/> ",
1236                        "<Post name='type:property' url='type:url' auth='type:string' body='type:string' mediaType='type:string' timeout='type:time'/> " })
1237        public void runCommandPost(final Node aCurrentAction) throws Throwable {
1238                String mediaTypeStr = attr(aCurrentAction, "mediaType");
1239                mediaTypeStr = StringUtils.defaultIfEmpty(mediaTypeStr, "application/json");
1240
1241                String url = attr(aCurrentAction, "url");
1242                String auth = attr(aCurrentAction, "auth");
1243                String bodyStr = StringUtils.defaultIfEmpty(replaceProperties(aCurrentAction.getAttribute("body")), "");
1244
1245                MediaType mediaType = MediaType.parse(mediaTypeStr);
1246                okhttp3.RequestBody body = okhttp3.RequestBody.create(mediaType, bodyStr);
1247
1248                Builder addHeader = new okhttp3.Request.Builder().url(url).method("POST", body).addHeader("Content-Type",
1249                                mediaTypeStr);
1250                if (auth != null) {
1251                        addHeader = addHeader.addHeader("Authorization", auth);
1252                }
1253                okhttp3.Request request = addHeader.addHeader("Accept", "*/*").build();
1254                okhttp3.OkHttpClient.Builder newBuilder = new OkHttpClient().newBuilder();
1255
1256                long timeout = attrTime(aCurrentAction, "timeout", "0");
1257                if (timeout > 0) {
1258                        newBuilder.connectTimeout(timeout, TimeUnit.MILLISECONDS);
1259                        newBuilder.readTimeout(timeout, TimeUnit.MILLISECONDS);
1260                        newBuilder.writeTimeout(timeout, TimeUnit.MILLISECONDS);
1261                }
1262                OkHttpClient client = newBuilder.build();
1263                try (Response response = client.newCall(request).execute()) {
1264                        String value = new String(response.body().bytes(), "UTF-8");
1265
1266//      if(response.code() == 404) {
1267//              
1268//      }
1269
1270                        String name = attr(aCurrentAction, "name");
1271                        if (name != null) {
1272                                setVariableValue(name, value);
1273                        }
1274                }
1275        }
1276
1277        @CommandDescription("This method allows you to send either HTTP(S) requests or raw socket messages, "
1278                        + "with support for parameters, headers, authentication, and response handling."
1279                        + "The `Request` command executes a network request based on the configuration attributes:\r\n"
1280                        + "- `method`: Specifies the type of network request to perform. Common values are `get` for HTTP GET requests, "
1281                        + "`post` for HTTP POST requests, or `socket` for raw socket communication.\r\n"
1282                        + "- `request`: is the name of the request map variable which contains the data to be sent with the request. "
1283                        + "For HTTP requests, this may include the body and headers. For socket communication, "
1284                        + "it can be a string or byte array representing the message to send. \r\n"
1285                        + "- `response` is the name of the variable where the response data will be stored after the request is executed. "
1286                        + "This allows later access to the result of the network operation.\r\n"
1287                        + "- `host` is the target URL or host address for the network request. "
1288                        + "For HTTP(S), this is the full URL. For socket communication, "
1289                        + "it is typically in the format `hostname:port`\r\n"
1290                        + "- `timeout` is the maximum time to wait for the network request to complete before aborting.\r\n"
1291                        + "- `userName` is the username used for HTTP Basic Authentication, if required by the target server.\r\n"
1292                        + "- `password` is the password used for HTTP Basic Authentication, paired with `userName`.\r\n")
1293        @CommandExamples({
1294                        "<Request method='enum:socket|get|post' request='type:property[Map{header;body}]' "
1295                                        + "response='type:property[Map{status;body}]' host='type:url' timeout='type:time'/>",
1296                        "<Request method='get' response='type:property[Map{status;body}]' host='type:url' "
1297                                        + "userName='type:string' password='type:string'><param name='type:string' value='type:string'/></Request>" })
1298        public void runCommandRequest(final Node aCurrentAction) throws Throwable {
1299                Object request = attrValue(aCurrentAction, "request");
1300                final String responseName = attr(aCurrentAction, "response");
1301                final String method = StringUtils.defaultIfEmpty(attr(aCurrentAction, "method"), "socket");
1302                String theUrl = attr(aCurrentAction, "host");
1303
1304                String userName = attr(aCurrentAction, "userName");
1305                String password = attr(aCurrentAction, "password");
1306                HttpClientContext localContext = null;
1307                if (userName != null && password != null) {
1308                        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
1309                        credentialsProvider.setCredentials(AuthScope.ANY,
1310                                        new UsernamePasswordCredentials(userName + ":" + password));
1311                        localContext = HttpClientContext.create();
1312                        localContext.setCredentialsProvider(credentialsProvider);
1313                }
1314
1315                HttpRequestBase httpRequest = null;
1316
1317                final Node[] paramNodes = aCurrentAction.getNodes("param");
1318                if (paramNodes != null && paramNodes.length > 0) {
1319                        List<NameValuePairImplementation> parameters = new ArrayList<>();
1320                        for (Node node : paramNodes) {
1321                                String name = attr(node, "name");
1322                                String value = attr(node, "value");
1323
1324                                NameValuePairImplementation nameValuePair = new NameValuePairImplementation(name, value);
1325                                parameters.add(nameValuePair);
1326                        }
1327
1328                        String query = URLEncodedUtils.format(parameters, "UTF-8");
1329                        theUrl = theUrl + "?" + query;
1330                }
1331
1332                if (theUrl.toLowerCase().startsWith("http")) {
1333                        if ("get".equalsIgnoreCase(method)) {
1334                                httpRequest = new HttpGet(theUrl);
1335
1336                        } else if ("post".equalsIgnoreCase(method)) {
1337                                HttpPost httpPost = new HttpPost(theUrl);
1338                                if (request != null) {
1339                                        @SuppressWarnings("unchecked")
1340                                        final Map<String, Object> requestMap = (Map<String, Object>) request;
1341                                        Object bodyObject = requestMap.get("body");
1342                                        byte[] body = null;
1343                                        if (bodyObject instanceof byte[]) {
1344                                                body = (byte[]) bodyObject;
1345                                        } else if (bodyObject instanceof String) {
1346                                                body = ((String) bodyObject).getBytes();
1347                                        }
1348                                        httpPost.setEntity(new ByteArrayEntity(body));
1349                                }
1350                                httpRequest = httpPost;
1351
1352                        }
1353                        int timeout = (int) attrTime(aCurrentAction, "timeout", "5000");
1354
1355                        RequestConfig config = RequestConfig.custom().setConnectTimeout(timeout)
1356                                        .setConnectionRequestTimeout(timeout).setSocketTimeout(timeout).build();
1357                        httpRequest.setConfig(config);
1358                        if (request != null) {
1359                                @SuppressWarnings("unchecked")
1360                                Object headerObj = ((Map<String, Object>) request).get("header");
1361                                if (headerObj instanceof Map) {
1362                                        @SuppressWarnings("unchecked")
1363                                        Map<String, String> header = (Map<String, String>) headerObj;
1364                                        if (header != null) {
1365                                                Set<Entry<String, String>> entrySet = header.entrySet();
1366                                                for (Entry<String, String> entry : entrySet) {
1367                                                        httpRequest.setHeader(entry.getKey(), entry.getValue());
1368                                                }
1369                                        }
1370                                } else {
1371                                        String header = String.valueOf(headerObj);
1372                                        httpRequest.setHeader(StringUtils.substringBefore(header, ":").trim(),
1373                                                        StringUtils.substringAfter(header, ":").trim());
1374                                }
1375                        }
1376
1377                        CloseableHttpResponse response;
1378                        CloseableHttpClient httpclient = HttpClients.createDefault();
1379
1380                        long duration = System.currentTimeMillis();
1381                        if (localContext == null) {
1382                                response = httpclient.execute(httpRequest);
1383                        } else {
1384                                response = httpclient.execute(httpRequest, localContext);
1385                        }
1386                        ByteArrayOutputStream body = new ByteArrayOutputStream();
1387                        response.getEntity().writeTo(body);
1388
1389                        HashMap<String, Object> aValue = new HashMap<String, Object>();
1390                        aValue.put("body", body.toByteArray());
1391                        aValue.put("status", response.getStatusLine().getStatusCode());
1392                        aValue.put("header", response.getAllHeaders());
1393                        aValue.put("duration", Long.toString(System.currentTimeMillis() - duration));
1394                        setVariableValue(responseName, aValue);
1395
1396                } else {
1397
1398                        Socket socket = null;
1399                        try {
1400                                String host = StringUtils.substringBefore(theUrl, ":");
1401                                int port = Integer.parseInt(StringUtils.defaultIfBlank(StringUtils.substringAfter(theUrl, ":"), "80"));
1402                                socket = new Socket(host, port);
1403                                OutputStream outputStream = socket.getOutputStream();
1404                                if (request instanceof byte[]) {
1405                                        IOUtils.write((byte[]) request, outputStream);
1406                                } else if (request instanceof String) {
1407                                        IOUtils.write(((String) request).getBytes(), outputStream);
1408                                }
1409                                InputStream inputStream = socket.getInputStream();
1410                                byte[] response = IOUtils.toByteArray(inputStream);
1411                                setVariableValue(responseName, response);
1412                        } finally {
1413                                if (socket != null) {
1414                                        socket.close();
1415                                }
1416                        }
1417
1418                }
1419
1420        }
1421
1422        @CommandExamples({ "<Table name='The table of variables'><var name='type:property' value='type:string'/></Table>",
1423                        "<Table name='The table of variables' file='type:path'><var name='type:property' value='type:string'/></Table>" })
1424        public void runCommandTable(final Node aCurrentVar) throws Throwable {
1425                AEManager manager = this.recipeListener.getManager();
1426                boolean notifyMe = this.recipeListener.isNotifyMe();
1427
1428                manager.inputDataTable(this, aCurrentVar, notifyMe);
1429        }
1430
1431        @SuppressWarnings({ "unchecked" })
1432        @CommandExamples({ "<Loop name='type:property' source='type:property' numbers='type:integer'> ... </Loop>",
1433                        "<Loop numbers='type:integer'> ... </Loop>",
1434                        "<Loop name='type:property' query='select * from ...' maxCount='type:integer'> ... </Loop>" })
1435        public void runCommandLoop(final Node aCurrentVar) throws Throwable {
1436                String theDescript = replaceProperties(aCurrentVar.getAttribute("description"));
1437                final String theNameAttribut = attr(aCurrentVar, "name");
1438                boolean mainLoopCommand = false;
1439
1440                final String theNumbers = StringUtils.trimToNull(replaceProperties(aCurrentVar.getAttribute("numbers")));
1441                if (theDescript == null) {
1442                        theDescript = "Loop";
1443                }
1444                String sourceAttribute = aCurrentVar.getAttribute("source");
1445                final String theAttribut = replaceProperties(sourceAttribute);
1446                Object loopArrayVar = null;
1447                if (theAttribut != null) {
1448                        loopArrayVar = getVariableValue(theAttribut);
1449                }
1450                List<Object> theLoopArray = new ArrayList<>();
1451                if (loopArrayVar != null) {
1452                        if (loopArrayVar instanceof String) {
1453                                theLoopArray.add((String) loopArrayVar);
1454                        } else if (loopArrayVar instanceof String[]) {
1455                                List<String> asList = Arrays.asList((String[]) loopArrayVar);
1456                                theLoopArray.addAll(asList);
1457                        } else if (loopArrayVar instanceof List) {
1458                                theLoopArray = (List<Object>) loopArrayVar;
1459                        } else if (loopArrayVar instanceof Map) {
1460                                Map map = (Map) loopArrayVar;
1461                                theLoopArray.addAll(map.keySet());
1462                        } else if (loopArrayVar instanceof JSONArray) {
1463                                JSONArray array = (JSONArray) loopArrayVar;
1464                                theLoopArray = array.toList();
1465                        } else {
1466                                return;
1467                        }
1468                }
1469                int theCount = 0;
1470                boolean infinityLoop = false;
1471                if (StringUtils.isNotBlank(theNumbers)) {
1472                        theCount = (int) Double.parseDouble(theNumbers);
1473                } else {
1474                        if (!theLoopArray.isEmpty()) {
1475                                theCount = theLoopArray.size();
1476                        } else {
1477                                infinityLoop = sourceAttribute == null;
1478                        }
1479                }
1480                if (isLoopActivated() == false) {
1481                        this.mainLoopCommand = true;
1482                        mainLoopCommand = true;
1483                }
1484
1485                if (infinityLoop) {
1486                        this.mainLoopCommand = false;
1487                }
1488                try {
1489                        for (int i = 0; (i < theCount || infinityLoop) && isStopped() == false; i++) {
1490                                if (isStopped()) {
1491                                        break;
1492                                }
1493                                if (this.breakFlag > 0) {
1494                                        break;
1495                                }
1496                                if ((mainLoopCommand && !infinityLoop) || theCount > 5) {
1497                                        this.recipeListener.setProgress(theDescript, theCount, i, false);
1498                                }
1499                                startCommandInformation(aCurrentVar);
1500                                if (theLoopArray != null && theLoopArray.size() > 0) {
1501                                        if (i < theLoopArray.size()) {
1502                                                setVariableValue(theNameAttribut, theLoopArray.get(i));
1503                                        }
1504                                } else {
1505                                        setVariableValue(theNameAttribut, ObjectUtils.toString(i));
1506                                }
1507                                taskNode(aCurrentVar, false);
1508                        }
1509                } finally {
1510                        if (this.breakFlag > 0) {
1511                                breakFlag--;
1512                        }
1513                        if (mainLoopCommand) {
1514                                this.mainLoopCommand = false;
1515                        }
1516                }
1517        }
1518
1519        @CommandExamples({ "<Append name='type:property' value='type:string' type='enum:all|unique'/>",
1520                        "<Append name='type:property'>...</Append>" })
1521        public synchronized void runCommandAppend(final Node aCurrentVar) throws Throwable {
1522                final String name = aCurrentVar.getAttribute("name");
1523                Object value = attr(aCurrentVar, "value");
1524                if (value == null) {
1525                        value = replaceProperties(aCurrentVar.getInnerText());
1526                }
1527
1528                String source = aCurrentVar.getAttribute("source");
1529                if (source != null) {
1530                        value = getVariableValue(replaceProperties(source));
1531                }
1532
1533                Object theOldValue = getVariableValue(name, false);
1534                if (theOldValue == null) {
1535                        return;
1536                }
1537
1538                theOldValue = checkForArray(theOldValue);
1539
1540                if (theOldValue instanceof String) {
1541                        final String theValue1 = (String) theOldValue;
1542                        value = theValue1 + value;
1543                        setVariableValue(name, value);
1544
1545                } else if (theOldValue instanceof Object[]) {
1546                        final boolean theUniqueTypeAttribut = "unique".equals(aCurrentVar.getAttribute("type"));
1547
1548                        final Object[] theValue1 = (Object[]) theOldValue;
1549                        Object[] theValue2 = null;
1550
1551                        if (theUniqueTypeAttribut) {
1552                                Set<Object> targetSet = new LinkedHashSet<>(Arrays.asList(theValue1));
1553                                if (value instanceof String) {
1554                                        targetSet.add((String) value);
1555                                } else if (value instanceof String[]) {
1556                                        for (String val : (String[]) value) {
1557                                                targetSet.add(val);
1558                                        }
1559                                }
1560
1561                                String[] array = new String[targetSet.size()];
1562                                Iterator<Object> iterator = targetSet.iterator();
1563                                int i = 0;
1564                                while (iterator.hasNext()) {
1565                                        String object = (String) iterator.next();
1566                                        array[i++] = object;
1567                                }
1568
1569                                theValue2 = array;
1570
1571                        } else {
1572                                List<Object> targetList = new ArrayList<>(Arrays.asList(theValue1));
1573                                if (value instanceof String[]) {
1574                                        for (String val : (String[]) value) {
1575                                                targetList.add(val);
1576                                        }
1577                                } else {
1578                                        targetList.add((String) value);
1579                                }
1580
1581                                String[] array = new String[targetList.size()];
1582                                int i = 0;
1583                                for (Object val : targetList) {
1584                                        if (val instanceof String) {
1585                                                array[i++] = (String) val;
1586                                        } else {
1587                                                array[i++] = ObjectUtils.toString(val);
1588                                        }
1589                                }
1590
1591                                theValue2 = array;
1592                        }
1593
1594                        applyResult(aCurrentVar, name, theValue2);
1595                }
1596        }
1597
1598        private Object checkForArray(Object theOldValue) {
1599                if (theOldValue instanceof List) {
1600                        theOldValue = ((List) theOldValue).toArray();
1601                }
1602                return theOldValue;
1603        }
1604
1605        @CommandExamples({ "<Rnd name='type:property' symbols='type:integer' type='enum:number|default'/>",
1606                        "<Rnd name='type:property' type='uuid'/>",
1607                        "<Rnd name='type:property' symbols='type:integer' startCode='type:integer' endCode='type:integer'/>",
1608                        "<Rnd name='type:property' source='type:property'/>" })
1609        public void runCommandRnd(final Node aCurrentVar) throws Throwable {
1610                final String theNameAttribut = replaceProperties(aCurrentVar.getAttribute("name"));
1611                String result = null;
1612                String type = replaceProperties(aCurrentVar.getAttribute("type"));
1613
1614                if ("uuid".equalsIgnoreCase(type)) {
1615                        result = UUID.randomUUID().toString();
1616
1617                } else {
1618                        final String source = replaceProperties(aCurrentVar.getAttribute("source"));
1619                        if (this.random == null) {
1620                                this.random = SecureRandom.getInstance("SHA1PRNG");
1621                        }
1622                        if (source != null) {
1623                                Object value = getVariableValue(source);
1624                                if (value instanceof String[]) {
1625                                        String[] strings = (String[]) value;
1626                                        int index = this.random.nextInt(strings.length);
1627                                        value = strings[index];
1628                                }
1629                                setVariableValue(theNameAttribut, value);
1630                                return;
1631                        }
1632
1633                        final String theSymbolsAttribut = replaceProperties(aCurrentVar.getAttribute("symbols"));
1634
1635                        final long theBegNum = Long.parseLong(theSymbolsAttribut);
1636                        int startCode = 0x41;
1637                        final String startCodeStr = replaceProperties(aCurrentVar.getAttribute("startCode"));
1638                        if (startCodeStr != null) {
1639                                if (startCodeStr.indexOf('x') > 0) {
1640                                        startCode = Integer.parseInt(startCodeStr.substring(2), 16);
1641                                } else {
1642                                        startCode = Integer.parseInt(startCodeStr);
1643                                }
1644                                type = "-number";
1645                        }
1646
1647                        int endCode = 0x05A;
1648                        final String endCodeStr = replaceProperties(aCurrentVar.getAttribute("endCode"));
1649                        if (endCodeStr != null) {
1650                                if (endCodeStr.indexOf('x') > 0) {
1651                                        endCode = Integer.parseInt(endCodeStr.substring(2), 16);
1652                                } else {
1653                                        endCode = Integer.parseInt(endCodeStr);
1654                                }
1655                                type = "-number";
1656                        }
1657
1658                        if ("number".equals(type) == false) {
1659                                final StringBuffer theBuffer = new StringBuffer();
1660                                for (int i = 0; i < theBegNum; i++) {
1661                                        theBuffer.append((char) (this.random.nextInt(endCode - startCode) + startCode));
1662                                }
1663                                result = theBuffer.toString();
1664                        } else {
1665                                final StringBuffer theBuffer = new StringBuffer();
1666                                for (int i = 0; i < theBegNum; i++) {
1667                                        theBuffer.append(this.random.nextInt(9));
1668                                }
1669                                result = theBuffer.toString();
1670                        }
1671                }
1672
1673                setVariableValue(theNameAttribut, result);
1674        }
1675
1676        @CommandExamples({ "<Inc name='type:property' increase='type:number'/>" })
1677        public void runCommandInc(final Node aCurrentVar) throws Throwable {
1678                String theValueAttribut = aCurrentVar.getAttribute("increase");
1679                theValueAttribut = replaceProperties(theValueAttribut);
1680                long theIncLong = 1;
1681                if (theValueAttribut != null) {
1682                        theIncLong = Long.parseLong(theValueAttribut);
1683                }
1684                final String theAttribut = aCurrentVar.getAttribute("name");
1685                Object theOldValue = getVariableValue(theAttribut);
1686
1687                if (!(theOldValue instanceof Object[])) {
1688                        theOldValue = ObjectUtils.toString(theOldValue, null);
1689                }
1690
1691                if (theOldValue instanceof String) {
1692                        final long theLongValue = Long.parseLong((String) theOldValue) + theIncLong;
1693                        setVariableValue(theAttribut, Long.toString(theLongValue));
1694                }
1695                if (theOldValue instanceof String[] && ((String[]) theOldValue).length > 0) {
1696                        final long theLongValue = Long.parseLong(((String[]) theOldValue)[0]) + theIncLong;
1697                        setVariableValue(theAttribut, Long.toString(theLongValue));
1698                } else {
1699                        new ClassCastException("Tag <Inc> enabled only one number argument.");
1700                }
1701        }
1702
1703        @CommandExamples({
1704                        "<If name='type:property' startsWith='type:string' endsWith='type:string' contains='type:string' equals='type:string' notEqual='type:string'> ... </If>",
1705                        "<If value='type:property' startsWith='type:string' endsWith='type:string' contains='type:string' equals='type:string' notEqual='type:string'> ... <Else> ... </Else></If>",
1706                        "<If expression='type:string'> ... </If>", "<If isNull='type:property'> ... </If>",
1707                        "<If isNotNull='type:property'> ... </If>" })
1708        public void runCommandIf(final Node action) throws Throwable {
1709                runIf(action);
1710        }
1711
1712        private boolean runIf(final Node action) throws CommandException, Throwable, ClassNotFoundException {
1713                String theExpressionAttribut = action.getAttribute("expression");
1714                String isNull = action.getAttribute("isNull");
1715                String isNotNull = action.getAttribute("isNotNull");
1716                String nameAttr = attr(action, "name");
1717
1718                String valueAttr = action.getAttribute("value");
1719
1720                String theValue1Attribut = action.getAttribute("value1");
1721                String theValue2Attribut = action.getAttribute("value2");
1722                String theConditionAttribut = action.getAttribute("condition");
1723
1724                boolean result = false;
1725                if (nameAttr != null || valueAttr != null) {
1726                        Object variableValue = getVariableValue(nameAttr);
1727                        String value;
1728
1729                        if (variableValue == null) {
1730                                value = replaceProperties(valueAttr);
1731                        } else {
1732                                if (variableValue instanceof String[]) {
1733                                        value = StringUtils.join((String[]) variableValue, "\n");
1734                                } else {
1735                                        value = ObjectUtils.toString(variableValue);
1736                                }
1737                        }
1738
1739                        String startsWith = attr(action, "startsWith");
1740                        String endsWith = attr(action, "endsWith");
1741                        String contains = attr(action, "contains");
1742                        String equals = attr(action, "equals");
1743                        String regex = attr(action, "regex");
1744                        String notEqual = attr(action, "notEqual");
1745
1746                        boolean ignoreCase = Boolean.valueOf(attr(action, "ignoreCase", "false"));
1747
1748                        boolean condition1 = startsWith == null || (ignoreCase ? StringUtils.startsWithIgnoreCase(value, startsWith)
1749                                        : StringUtils.startsWith(value, startsWith));
1750                        boolean condition2 = endsWith == null || (ignoreCase ? StringUtils.endsWithIgnoreCase(value, endsWith)
1751                                        : StringUtils.endsWith(value, endsWith));
1752                        boolean condition3 = contains == null || (ignoreCase ? StringUtils.containsIgnoreCase(value, contains)
1753                                        : StringUtils.contains(value, contains));
1754                        boolean condition4 = equals == null
1755                                        || (ignoreCase ? StringUtils.equalsIgnoreCase(value, equals) : StringUtils.equals(value, equals));
1756
1757                        boolean condition5 = true;
1758                        if (regex != null) {
1759                                final Pattern p = Pattern.compile(regex);
1760                                final Matcher m = p.matcher(value);
1761                                condition5 = m.find();
1762                        }
1763
1764                        boolean condition6 = notEqual == null || (ignoreCase ? !StringUtils.equalsIgnoreCase(value, notEqual)
1765                                        : !StringUtils.equals(value, notEqual));
1766
1767                        result = condition1 && condition2 && condition3 && condition4 && condition5 && condition6;
1768                        execIf(action, result);
1769                        return result;
1770                } else if (isNull != null) {
1771                        result = getVariableValue(replaceProperties(isNull)) == null;
1772                        execIf(action, result);
1773                        return result;
1774                } else if (isNotNull != null) {
1775                        Object variableValue = getVariableValue(replaceProperties(isNotNull));
1776                        result = variableValue != null;
1777                        execIf(action, result);
1778                        return result;
1779                } else if (theExpressionAttribut != null) {
1780                        theExpressionAttribut = replaceProperties(theExpressionAttribut);
1781                        JexlEngine jexl = new JexlBuilder().create();
1782
1783                        JexlExpression expr_c = jexl.createExpression(theExpressionAttribut);
1784                        JexlContext context = new MapContext();
1785
1786                        if (expr_c != null) {
1787                                Object val = expr_c.evaluate(context);
1788                                result = "true".equals(val.toString());
1789                        }
1790
1791                        execIf(action, result);
1792                        return result;
1793                } else if (theValue1Attribut != null || theValue2Attribut != null) {
1794                        if (theConditionAttribut == null) {
1795                                theConditionAttribut = "==";
1796                        }
1797                        theValue1Attribut = replaceProperties(theValue1Attribut);
1798                        theValue2Attribut = replaceProperties(theValue2Attribut);
1799
1800                        if ("==".equals(theConditionAttribut) && theValue1Attribut.equals(theValue2Attribut)) {
1801                                taskNode(action, false);
1802                                result = true;
1803                        } else if (("!=".equals(theConditionAttribut) || "unequal".equals(theConditionAttribut))
1804                                        && (theValue1Attribut.equals(theValue2Attribut) == false)) {
1805                                taskNode(action, false);
1806                                result = true;
1807                        } else if ("less".equals(theConditionAttribut)
1808                                        && (Long.parseLong(theValue1Attribut) < Long.parseLong(theValue2Attribut))) {
1809                                taskNode(action, false);
1810                                result = true;
1811                        } else if ("bigger".equals(theConditionAttribut)
1812                                        && (Long.parseLong(theValue1Attribut) > Long.parseLong(theValue2Attribut))) {
1813                                taskNode(action, false);
1814                                result = true;
1815                        } else {
1816                                for (Node command : action.getNodes("Else")) {
1817                                        taskNode(command, false);
1818                                }
1819                        }
1820                }
1821
1822                return result;
1823        }
1824
1825        private void execIf(final Node action, boolean result) throws CommandException, Throwable {
1826                if (result) {
1827                        taskNode(action, false);
1828                } else {
1829                        for (Node command : action.getNodes("Else")) {
1830                                if (command.getAttribute("name") == null && action.getAttribute("name") != null) {
1831                                        command.setAttribute("name", action.getAttribute("name"));
1832                                }
1833                                if (command.getAttribute("value") == null && action.getAttribute("value") != null) {
1834                                        command.setAttribute("value", action.getAttribute("value"));
1835                                }
1836                                if (command.getAttribute("name") != null || command.getAttribute("value") != null) {
1837                                        if (runIf(command)) {
1838                                                break;
1839                                        }
1840                                } else {
1841                                        taskNode(command);
1842                                }
1843                        }
1844                }
1845        }
1846
1847        @CommandDescription("")
1848        @CommandExamples({
1849                        "<While name='type:property' startsWith='type:string' endsWith='type:string' contains='type:string' equals='type:string' notEqual='type:string'> ... </While>",
1850                        "<While value1='type:string' value2='type:string' condition='unequal'> ... <Else value1='type:string' value2='type:string' condition='enum:unequal|equal'> ... </Else></While>",
1851                        "<While expression='type:string'> ... </While>", "<While isNull='type:property'> ... </While>",
1852                        "<While isNotNull='type:property'> ... </While>" })
1853        public void runCommandWhile(final Node aCurrentVar) throws Throwable {
1854                try {
1855                        for (; runIf(aCurrentVar);) {
1856                                if (this.breakFlag > 0) {
1857                                        break;
1858                                }
1859                        }
1860                } finally {
1861                        if (this.breakFlag > 0) {
1862                                breakFlag--;
1863                        }
1864                }
1865        }
1866
1867        @CommandExamples({ "<Pragma event='console-input' action='default'/>",
1868                        "<Pragma event='random-select' action='on'/>", "<Pragma event='log-window' action='single'/>" })
1869        public void runCommandPragma(final Node aCurrentVar) throws Throwable {
1870                final String theEventAttribut = aCurrentVar.getAttribute("event");
1871                final String theActionAttribut = aCurrentVar.getAttribute("action");
1872
1873                if ("console-input".equals(theEventAttribut)) {
1874                        boolean consoleDefaultInput = "default".equals(theActionAttribut);
1875                        getListener().getManager().setConsoleDefaultInput(consoleDefaultInput);
1876                } else if ("random-select".equals(theEventAttribut)) {
1877                        randomSelect = "on".equals(theActionAttribut);
1878                } else {
1879                        this.log.error("Pragma ignored. Event: " + theEventAttribut);
1880                }
1881        }
1882
1883        @SuppressWarnings("unchecked")
1884        @CommandDescription("The `name` attribute is a name of variable which provides Runnable or List<Runnable> value. "
1885                        + "To define this value, you should use the Runnable command tag.")
1886        @CommandExamples({
1887                        "<Threads name='type:property' threadLog='type:boolean' numbers='type:integer' multi='enum:true|false' />",
1888                        "<Threads numbers='type:integer' multi='enum:true|false' mode='enum:wait|nowait'><Out name='"
1889                                        + TaskProcessorThread.THREAD_ID + "'/></Threads>",
1890                        "<Threads multi='enum:true|false'><Out name='" + TaskProcessorThread.THREAD_ID + "'/></Threads>" })
1891        public void runCommandThreads(final Node aCurrentAction) throws InterruptedException, CommandException {
1892
1893                boolean threadLog = Boolean
1894                                .parseBoolean(StringUtils.defaultIfBlank(attr(aCurrentAction, "threadLog"), "false"));
1895                int numbers = Integer.parseInt(StringUtils.defaultIfBlank(attr(aCurrentAction, "numbers"), "1"));
1896                boolean multi = Boolean.parseBoolean(StringUtils.defaultIfBlank(attr(aCurrentAction, "multi"), "true"));
1897
1898                ThreadPoolExecutor threadPool;
1899                if (multi) {
1900                        if (numbers > 0) {
1901                                threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(numbers);
1902                        } else {
1903                                threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
1904                        }
1905                } else {
1906                        threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
1907                }
1908
1909                String callableListName = attr(aCurrentAction, "name");
1910                if (callableListName == null) {
1911                        final Iterator<?> theIterator = aCurrentAction.iterator();
1912
1913                        if (numbers == 0) {
1914                                while (theIterator.hasNext()) {
1915                                        final Node theNode = (Node) theIterator.next();
1916
1917                                        String theNameThreads = getTestName(theNode);
1918                                        if (theNameThreads == null) {
1919                                                theNameThreads = "Thread";
1920                                        }
1921
1922                                        ILogger theLog = this.log;
1923                                        if (threadLog) {
1924                                                theLog = this.recipeListener.createLog(theNameThreads, false);
1925                                        }
1926
1927                                        final TaskProcessorThread runnable = new TaskProcessorThread(this, theNode, getBaseDir(), theLog);
1928
1929                                        threadPool.execute(runnable);
1930
1931                                }
1932                        } else {
1933                                final Node theNode = (Node) aCurrentAction.clone();
1934                                theNode.setTag("Task");
1935                                theNode.removeAttribute(callableListName);
1936
1937                                for (int i = 0; i < numbers; i++) {
1938                                        String theNameThreads = getTestName(theNode);
1939                                        String logName = "Thread #" + i;
1940                                        if (theNameThreads != null) {
1941                                                logName = theNameThreads + " #" + i;
1942                                        }
1943
1944                                        ILogger theLog = this.log;
1945                                        if (threadLog) {
1946                                                theLog = this.recipeListener.createLog(logName, false);
1947                                        }
1948
1949                                        final TaskProcessorThread runnable = new TaskProcessorThread(this, theNode, getBaseDir(), theLog);
1950                                        runnable.getVaribleValue(TaskProcessorThread.THREAD_ID, String.valueOf(i));
1951
1952                                        threadPool.execute(runnable);
1953                                }
1954                        }
1955
1956                } else {
1957                        Object callableList = getVariableValue(callableListName);
1958                        if (callableList instanceof List) {
1959                                for (Runnable runnable : (List<Runnable>) callableList) {
1960                                        threadPool.execute(runnable);
1961                                }
1962                        } else if (callableList instanceof Runnable) {
1963                                for (int i = 0; i < numbers; i++) {
1964                                        if (callableList instanceof TaskProcessorThread) {
1965                                                threadPool.execute(((TaskProcessorThread) callableList).clone(i));
1966                                        } else {
1967                                                threadPool.execute((Runnable) callableList);
1968                                        }
1969                                }
1970                        } else {
1971                                throw new IllegalArgumentException(
1972                                                "Variable of type must be Runnable or List<Runnable>, actually: " + callableList);
1973                        }
1974                }
1975
1976                String description = replaceProperties(aCurrentAction.getAttribute("description"));
1977
1978                long completedTaskCount = 0;
1979                long size = threadPool.getTaskCount();
1980
1981                threadPoolList.add(threadPool);
1982                do {
1983                        completedTaskCount = threadPool.getCompletedTaskCount();
1984                        progress(size, completedTaskCount, description, false);
1985                        Thread.sleep(200);
1986
1987                } while (completedTaskCount < size);
1988                threadPoolList.remove(threadPool);
1989        }
1990
1991        @CommandExamples({ "Run recipe by name: <Task name='type:recipe' />",
1992                        "Run recipe by name in async mode: <Task name='type:recipe' mode='enum:sync|async' optional='enum:false|true'/>",
1993                        "Recipe command grouping: <Task> ... </Task>", "Run recipe by file path: <Task file='type:path' />" })
1994        public void runCommandTask(final Node action) throws Throwable {
1995
1996                final boolean optional = Boolean.valueOf(attr(action, "optional", "false"));
1997
1998                final String taskName = replaceProperties(action.getAttribute("name"));
1999                String taskFile = replaceProperties(action.getAttribute("file"));
2000
2001                if (taskFile == null) {
2002                        if (taskName != null) {
2003                                taskFile = getListener().getManager().getTestPath(taskName);
2004
2005                                if (taskFile == null) {
2006                                        if (optional) {
2007                                                return;
2008                                        } else {
2009                                                throw new Exception("Task with name: '" + taskName + "' not found.");
2010                                        }
2011                                }
2012                        }
2013                } else {
2014                        if (!(new File(taskFile)).exists()) {
2015                                if (optional) {
2016                                        return;
2017                                } else {
2018                                        throw new Exception("Task with name: '" + taskFile + "' not found.");
2019                                }
2020                        }
2021                }
2022
2023                final File theBaseDir = getBaseDir();
2024                try {
2025                        if (taskName != null) {
2026                                String mode = replaceProperties(action.getAttribute("mode"));
2027                                if ("async".equals(mode)) {
2028                                        getListener().getManager().runTask(taskName, true);
2029
2030                                } else {
2031                                        Properties taskParameters = MapUtils.toProperties(action.getAttributes());
2032                                        Set<Entry<Object, Object>> entrySet = taskParameters.entrySet();
2033
2034                                        String currentLogLevel = (String) this.variables.get(DEFAULT_LOG_LEVEL_NAME);
2035                                        String level = (String) taskParameters.get("level");
2036
2037                                        for (Entry<Object, Object> entry : entrySet) {
2038                                                String name = (String) entry.getKey();
2039                                                if (!"$ID".equals(name) && !"name".equals(name) && !"file".equals(name)
2040                                                                && !"level".equals(name)) {
2041                                                        String value = replaceProperties((String) entry.getValue());
2042                                                        this.variables.put(toUpperCaseName(name), value);
2043                                                }
2044                                        }
2045
2046                                        this.variables.put(DEFAULT_LOG_LEVEL_NAME, level);
2047                                        this.variables = processTesting(taskFile, this.variables, getBaseDir());
2048                                        this.variables.put(DEFAULT_LOG_LEVEL_NAME, currentLogLevel);
2049                                }
2050                        } else {
2051                                taskNode(action, false);
2052                        }
2053                } finally {
2054                        setBaseDir(theBaseDir);
2055                }
2056        }
2057
2058        @CommandExamples({ "<IterationRules name='type:property'><Out name='Iteration'/></IterationRules>",
2059                        "<IterationRules name='type:property' timeLimit='type:time' start='type:integer'> ... </IterationRules>",
2060                        "<Var name='rules'>#\n[multiple]\na=1\nb $enum=1;2\nc $inc=0;10\nd $file=file_name\n[single]\n...\n[concurrently]\n...\n[independent]\n...\n#</Var>"
2061                                        + "<IterationRules name='rules'><Out name='Iteration'/></IterationRules>", })
2062        public void runCommandIterationRules(final Node action) throws IOException, CommandException {
2063                final String name = (String) attrValue(action, "name");
2064                String theStartAttribut = action.getAttribute("start");
2065                long theLimit = attrTime(action, "timeLimit", "180000");
2066
2067                if (theStartAttribut == null) {
2068                        theStartAttribut = "0";
2069                }
2070
2071                final int theStart = Integer.parseInt(theStartAttribut);
2072                if (name == null) {
2073                        throw new CommandException("In tag <IterationRules> variable rules is not defined.", this);
2074                }
2075                try {
2076                        this.iteratorMode = true;
2077                        final TestIterator theTestIterator = new TestIterator(this, name);
2078                        final int theMax = theTestIterator.count();
2079                        progress(theMax, 0, "Iteration process", false);
2080                        debug("Iteration block. Total count: " + theMax);
2081                        final long theStertTime = System.currentTimeMillis();
2082                        for (int i = theStart; i < theMax; i++) {
2083                                if (isStopped()) {
2084                                        break;
2085                                }
2086                                setVariableValue("Iteration", Integer.toString(i + 1));
2087                                theTestIterator.nextIteration(this);
2088
2089                                taskNode(action, false);
2090
2091                                progress(theMax, i, "Iteration process", false);
2092                                if (i == 0) {
2093                                        long theTotalTime = ((System.currentTimeMillis() - theStertTime) * theMax) / 60000;
2094                                        debug("Total Iteration time: " + Long.toString(theTotalTime) + " min.");
2095                                        if (theTotalTime > theLimit) {
2096                                                int theConfirm = 0;
2097                                                if (this.recipeListener.getManager() != null) {
2098                                                        if (this.recipeListener.getManager().isConsoleDefaultInput(attr(action, "name"),
2099                                                                        attr(action, "description")) == false) {
2100                                                                if (theTotalTime < 60) {
2101                                                                        theConfirm = JOptionPane.showConfirmDialog(
2102                                                                                        JOptionPane.getRootFrame(), "Total Iteration time: "
2103                                                                                                        + Long.toString(theTotalTime) + " min. \nContinue?",
2104                                                                                        "Warning", JOptionPane.YES_NO_OPTION);
2105                                                                } else {
2106                                                                        theTotalTime /= 60;
2107                                                                        theConfirm = JOptionPane.showConfirmDialog(JOptionPane.getRootFrame(),
2108                                                                                        "Total Iteration time: " + Long.toString(theTotalTime) + " h. \nContinue?",
2109                                                                                        "Warning", JOptionPane.YES_NO_OPTION);
2110                                                                }
2111                                                        }
2112                                                        if (theConfirm != JOptionPane.YES_OPTION) {
2113                                                                break;
2114                                                        }
2115                                                }
2116                                        }
2117                                }
2118                        }
2119                } finally {
2120                        this.iteratorMode = false;
2121                }
2122        }
2123
2124        @CommandExamples({ "<Listdir path='type:path' name='type:property'/>",
2125                        "<Listdir name='type:property' path='type:path' filter='type:regex'/>",
2126                        "<Listdir name='type:property' path='type:path' filter='type:regex' dirFilter='type:regex' />",
2127                        "<Listdir path='type:path' name='type:property' />" })
2128        public void runCommandListdir(final Node aCurrentVar) throws Throwable {
2129                final String pathAttribut = replaceProperties(aCurrentVar.getAttribute("path"));
2130                final String theNameAttribut = replaceProperties(aCurrentVar.getAttribute("name"));
2131                final String fileFilterRegex = attr(aCurrentVar, "filter");
2132                final String dirFilterRegex = attr(aCurrentVar, "dirFilter");
2133                File path = getFile(pathAttribut);
2134
2135                if (isActiveInitFor(aCurrentVar, "console")) {
2136                        String pathStr = this.recipeListener.getManager().inputFile(theNameAttribut, null, path, log, this);
2137                        if (pathStr != null) {
2138                                path = new File(pathStr);
2139                        }
2140                }
2141
2142                if (path != null) {
2143                        debug("Listing of directory: " + path.getAbsolutePath());
2144
2145                        IOFileFilter fileFilter = TrueFileFilter.TRUE;
2146
2147                        if (fileFilterRegex != null) {
2148                                fileFilter = new RegexPathFilter(fileFilterRegex, dirFilterRegex);
2149                        }
2150
2151                        Collection<File> files = FileUtils.listFiles(path, fileFilter,
2152                                        dirFilterRegex != null ? TrueFileFilter.TRUE : null);
2153                        List<String> result = files.stream().map(f -> f.getAbsolutePath()).collect(Collectors.toList());
2154
2155                        if (!result.isEmpty()) {
2156                                Collections.sort(result);
2157                                setVariableValue(theNameAttribut, result);
2158                        }
2159                } else {
2160                        throw new TaskCancelingException();
2161                }
2162        }
2163
2164        @CommandExamples({ "<NetworkInterfaces name='type:property' />",
2165                        "<NetworkInterfaces name='type:property' host='type:string' filterFor='type:string'/>" })
2166        public void runCommandNetworkInterfaces(final Node aCurrentAction) throws SocketException, UnknownHostException {
2167                final String name = replaceProperties(aCurrentAction.getAttribute("name"));
2168                String host = replaceProperties(aCurrentAction.getAttribute("host"));
2169
2170                if (host == null) {
2171                        Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
2172                        Set<String> hostIps = new HashSet<>();
2173                        while (networkInterfaces.hasMoreElements()) {
2174                                NetworkInterface nextElement = networkInterfaces.nextElement();
2175                                Enumeration<InetAddress> inetAddresses = nextElement.getInetAddresses();
2176                                while (inetAddresses.hasMoreElements()) {
2177                                        InetAddress nextElement2 = inetAddresses.nextElement();
2178                                        if (nextElement2 instanceof Inet4Address) {
2179                                                Inet4Address address = (Inet4Address) nextElement2;
2180                                                String hostAddress = address.getHostAddress();
2181                                                hostIps.add(hostAddress);
2182                                        }
2183                                }
2184                        }
2185                        setVariableValue(name, new ArrayList<>(hostIps));
2186                } else {
2187                        try {
2188                                URL url = new URL(host);
2189                                host = url.getHost();
2190                        } catch (Exception e) {
2191
2192                        }
2193
2194                        InetAddress address1 = InetAddress.getByName(host);
2195                        setVariableValue(name, address1.getHostAddress());
2196                }
2197        }
2198
2199        @CommandDescription("Executes a command on the local computer. The command will be executed if the regular expression specified in `os` matches the OS_NAME system property. "
2200                        + "The `dir` attribute specifies the current directory for the command being executed. This attribute can use `~` as the directory for the current recipe.")
2201        @CommandExamples({ "<Command os='type:regex'>...</Command>",
2202                        "<Command os='type:regex' env='type:property'>...</Command>",
2203                        "<Command name='type:property' os='type:regex' exitValue='type:property' cmd='type:string' noCommandLog='type:boolean' dir='type:path'/>",
2204                        "<Command name='type:property' os='type:regex' exitValue='type:property' cmd='type:string' noCommandLog='type:boolean' dir='type:path'/>...</Command>" })
2205        public void runCommandCommand(final Node action) throws Throwable {
2206
2207                String command = attr(action, "cmd");
2208                final String name = attr(action, "name");
2209                final String dir = attr(action, "dir");
2210                final String os = attr(action, "os");
2211
2212                String osName = SystemUtils.OS_NAME;
2213
2214                if (os == null || Pattern.compile(os).matcher(osName).matches()) {
2215                        if (command == null) {
2216                                final Node[] theNodes = action.getTextNodes();
2217                                if (theNodes.length > 0) {
2218                                        command = replaceProperties(theNodes[0].getText());
2219                                }
2220                        }
2221
2222                        Object env = attrValue(action, "env");
2223                        if (env != null) {
2224                                if (!(env instanceof Map)) {
2225                                        throw new CommandException("'env' attribute shuld be Map", this);
2226                                }
2227                        }
2228                        @SuppressWarnings("unchecked")
2229                        Map<String, String> environment = (Map<String, String>) env;
2230                        runSystemCommand(command, name, action, dir, environment);
2231                }
2232        }
2233
2234        private void runSystemCommand(final String command, final String theNameAttribut, final Node aCurrentNode,
2235                        String dir, Map<? extends String, ? extends String> env) throws Throwable {
2236                final String prefix = "start ";
2237                if (command.startsWith(prefix) == false) {
2238
2239                        String regExp = "\"(\\\"|[^\"])*?\"|[^ ]+";
2240                        Pattern pattern = Pattern.compile(regExp, Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
2241                        Matcher matcher = pattern.matcher(command);
2242                        List<String> commandTokens = new ArrayList<String>();
2243
2244                        if (SystemUtils.IS_OS_WINDOWS) {
2245                                commandTokens.add("cmd.exe");
2246                                commandTokens.add("/c");
2247                        }
2248
2249                        while (matcher.find()) {
2250                                String trim = matcher.group().trim();
2251                                if (StringUtils.isNotBlank(trim)) {
2252                                        commandTokens.add(trim);
2253                                }
2254                        }
2255                        String[] parsedCommand = commandTokens.toArray(new String[] {});
2256
2257                        ProcessBuilder builder = new ProcessBuilder(parsedCommand);
2258
2259                        if (env != null) {
2260                                Map<String, String> environment = builder.environment();
2261                                environment.putAll(env);
2262                        }
2263
2264                        if (dir != null) {
2265                                File directory;
2266                                if (dir.startsWith("~/")) {
2267                                        dir = dir.substring(1);
2268                                        String recipeFile = this.recipeListener.getManager().getTestPath(testName);
2269                                        if (recipeFile != null) {
2270                                                directory = new File(new File(recipeFile).getParent(), dir);
2271                                                builder.directory(directory);
2272                                        }
2273                                } else {
2274                                        directory = new File(dir);
2275                                        builder.directory(directory);
2276                                }
2277                        }
2278
2279                        builder.redirectErrorStream(true);
2280                        Process process = builder.start();
2281
2282                        final String stdin = replaceProperties(aCurrentNode.getAttribute("stdin"));
2283                        if (stdin != null) {
2284                                process.getOutputStream().write(stdin.getBytes());
2285                                process.getOutputStream().close();
2286                        }
2287
2288                        processes.add(process);
2289
2290                        final BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getInputStream()));
2291                        boolean noCommandLog = Boolean.valueOf(attr(aCurrentNode, "noCommandLog"));
2292                        if (!noCommandLog) {
2293                                debug("Command: " + command);
2294                        } else {
2295                                debug("Command: ****** **** *****");
2296                        }
2297
2298                        try {
2299                                final BufferedReader theOutputStream = new BufferedReader(
2300                                                new InputStreamReader(process.getInputStream()));
2301                                String theLine;
2302
2303                                boolean line_output = aCurrentNode.size() == 0;
2304                                final StringBuffer theBuffer = new StringBuffer();
2305                                while ((theLine = theOutputStream.readLine()) != null && !isStoppedTest()) {
2306                                        if (breakFlag > 0) {
2307                                                break;
2308                                        }
2309
2310                                        if (line_output) {
2311                                                theBuffer.append(theLine);
2312                                                theBuffer.append('\n');
2313                                        } else {
2314                                                setVariableValue(theNameAttribut, theLine);
2315                                                taskNode(aCurrentNode, false);
2316                                        }
2317                                }
2318
2319                                if (this.breakFlag > 0) {
2320                                        breakFlag--;
2321                                }
2322
2323                                String exitValueName = attr(aCurrentNode, "exitValue");
2324                                if (exitValueName != null) {
2325                                        int exitValue = 0;
2326                                        try {
2327                                                exitValue = process.exitValue();
2328                                                if (exitValue > 0) {
2329                                                        String error = IOUtils.toString(errorStream);
2330                                                        if (StringUtils.isNotBlank(error)) {
2331                                                                throw new RuntimeException(error);
2332                                                        }
2333                                                }
2334                                        } catch (IllegalThreadStateException e) {
2335                                                //
2336                                        }
2337                                        setVariableValue(exitValueName, Integer.toString(exitValue));
2338                                }
2339
2340                                if (line_output) {
2341                                        if (theNameAttribut == null) {
2342                                                debug("System output:" + theBuffer.toString());
2343                                        } else {
2344                                                setVariableValue(theNameAttribut, theBuffer.toString());
2345                                        }
2346                                }
2347                        } finally {
2348                                processes.remove(process);
2349                                process.destroyForcibly();
2350                                try {
2351                                        if (!process.waitFor(5, TimeUnit.SECONDS)) {
2352                                                process.destroyForcibly();
2353                                        }
2354                                } catch (InterruptedException e) {
2355                                        Thread.currentThread().interrupt();
2356                                        process.destroyForcibly();
2357                                }
2358                        }
2359
2360                } else {
2361                        final Thread thread = new SystemCommandStart(command.substring(prefix.length()), this.log);
2362                        thread.start();
2363                }
2364        }
2365
2366        @CommandExamples({ "<ArraySize name = 'type:property' array = 'type:property'/>" })
2367        public void runCommandArraySize(final Node aCurrentVar) throws Throwable {
2368                final String theNameAttribut = replaceProperties(aCurrentVar.getAttribute("name"));
2369                final String theArrayNameAttribut = replaceProperties(aCurrentVar.getAttribute("array"));
2370                final Object theValue = getVariableValue(theArrayNameAttribut);
2371
2372                int theResult = 0;
2373                if (theValue instanceof String[]) {
2374                        theResult = ((String[]) theValue).length;
2375                } else if (theValue instanceof List) {
2376                        theResult = ((List<String>) theValue).size();
2377                } else if (theValue instanceof byte[]) {
2378                        theResult = ((byte[]) theValue).length;
2379                } else if (theValue instanceof String && StringUtils.isNotBlank((String) theValue)) {
2380                        theResult = 1;
2381                } else {
2382                        theResult = 0;
2383                }
2384
2385                setVariableValue(theNameAttribut, String.valueOf(theResult));
2386        }
2387
2388        @CommandExamples({ "Get string or array length: <Size name = 'type:property' source = 'type:property'/>" })
2389        public void runCommandSize(final Node aCurrentVar) throws Throwable {
2390                final String theNameAttribut = replaceProperties(aCurrentVar.getAttribute("name"));
2391                final String theArrayNameAttribut = replaceProperties(aCurrentVar.getAttribute("source"));
2392                final Object theValue = getVariableValue(theArrayNameAttribut);
2393
2394                int theResult = 0;
2395                if (theValue instanceof String[]) {
2396                        String[] theValue2 = (String[]) theValue;
2397                        for (int i = 0; i < theValue2.length; i++) {
2398                                theResult += theValue2[i].length();
2399                        }
2400                } else if (theValue instanceof byte[]) {
2401                        theResult = ((byte[]) theValue).length;
2402                } else if (theValue instanceof Collection) {
2403                        theResult = ((Collection) theValue).size();
2404                } else if (theValue instanceof String && StringUtils.isNotBlank((String) theValue)) {
2405                        theResult = ((String) theValue).length();
2406                } else {
2407                        theResult = 0;
2408                }
2409
2410                setVariableValue(theNameAttribut, String.valueOf(theResult));
2411        }
2412
2413        @CommandExamples({ "<Time name = 'type:property' action = 'enum:start|continue|pause|stop'/>",
2414                        "<Time name = 'type:property' action = 'format' format='mm:ss:SSS'/>",
2415                        "<Time name = 'type:property' action = 'duration' >...code whose execution time is to be measured...</Time>" })
2416        public void runCommandTime(final Node command) throws Throwable {
2417                final String theNameAttribut = replaceProperties(command.getAttribute("name"));
2418                final String format = replaceProperties(command.getAttribute("format"));
2419                final String theTimeCaption = "Time";
2420                String action = replaceProperties(command.getAttribute("action"));
2421
2422                if ("duration".equals(action)) {
2423                        long start = System.currentTimeMillis();
2424
2425                        taskNode(command, false);
2426
2427                        long stop = System.currentTimeMillis();
2428                        final String[] variableValue = new String[] { theTimeCaption, String.valueOf(start), String.valueOf(stop),
2429                                        "" };
2430
2431                        String result = formatTime(format, variableValue);
2432                        setVariableValue(theNameAttribut, result);
2433                        return;
2434                }
2435
2436                if ("start".equals(action)) {
2437                        final String[] theTime = new String[] { theTimeCaption, String.valueOf(System.currentTimeMillis()), "",
2438                                        "" };
2439                        setVariableValue(theNameAttribut, theTime);
2440                        return;
2441                }
2442
2443                if ("continue".equals(action)) {
2444                        String[] theTime = (String[]) getVariableValue(theNameAttribut);
2445                        if (theTime == null) {
2446                                theTime = new String[] { theTimeCaption, String.valueOf(System.currentTimeMillis()), "",
2447                                                String.valueOf(System.currentTimeMillis()) };
2448                        }
2449
2450                        if (theTime == null || theTime[3] == null || theTime[3].length() == 0) {
2451                                throw new Exception("Timer is not paused.");
2452                        }
2453
2454                        if (theTimeCaption.equals(theTime[0])) {
2455                                final long theStart = Long.parseLong(theTime[1]);
2456                                final long thePaused = Long.parseLong(theTime[3]);
2457                                theTime[1] = String.valueOf(theStart - (System.currentTimeMillis() - thePaused));
2458                                theTime[3] = "";
2459                                setVariableValue(theNameAttribut, theTime);
2460                        } else {
2461                                throw new Exception("Incorrect type.");
2462                        }
2463                        return;
2464                }
2465
2466                if ("pause".equals(action)) {
2467                        final String[] theTime = (String[]) getVariableValue(theNameAttribut);
2468
2469                        if (theTime[3] != null && theTime[3].length() > 0) {
2470                                throw new Exception("Timer is paused.");
2471                        }
2472                        if (theTimeCaption.equals(theTime[0])) {
2473                                theTime[3] = String.valueOf(System.currentTimeMillis());
2474                                setVariableValue(theNameAttribut, theTime);
2475                        } else {
2476                                throw new Exception("Incorrect type.");
2477                        }
2478                        return;
2479                }
2480
2481                if ("stop".equals(action)) {
2482                        final String[] theTime = (String[]) getVariableValue(theNameAttribut);
2483
2484                        final long theStart = Long.parseLong(theTime[1]);
2485
2486                        if (theTime[3] != null && theTime[3].length() > 0) {
2487                                final long thePaused = Long.parseLong(theTime[3]);
2488                                theTime[1] = String.valueOf(theStart + (System.currentTimeMillis() - thePaused));
2489                                theTime[3] = "";
2490                        }
2491
2492                        if (theTimeCaption.equals(theTime[0])) {
2493                                theTime[2] = String.valueOf(System.currentTimeMillis());
2494                                setVariableValue(theNameAttribut, theTime);
2495                        } else {
2496                                throw new Exception("Incorrect type.");
2497                        }
2498                        return;
2499                }
2500
2501                if ("format".equals(action)) {
2502                        Object variableValue = getVariableValue(theNameAttribut);
2503                        String theResult = formatTime(format, variableValue);
2504                        setVariableValue(theNameAttribut, theResult);
2505                        return;
2506                }
2507        }
2508
2509        private String formatTime(final String format, Object variableValue) throws Exception {
2510                final String[] theTime;
2511                if (variableValue instanceof String[]) {
2512                        theTime = (String[]) variableValue;
2513                } else {
2514                        theTime = new String[] { "", "0", (String) variableValue };
2515                }
2516
2517                if (ArrayUtils.getLength(theTime) < 2) {
2518                        throw new Exception("Timer is not defined.");
2519                }
2520
2521                if (ArrayUtils.getLength(theTime) < 3) {
2522                        throw new Exception("Timer is not stoped.");
2523                }
2524
2525                String theResult = null;
2526                if (format == null) {
2527                        theResult = String.valueOf(Long.parseLong(theTime[2]) - Long.parseLong(theTime[1]));
2528                } else {
2529                        final DateFormat dateFormat = new SimpleDateFormat(format);
2530                        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
2531                        long date = Long.parseLong(theTime[2]) - Long.parseLong(theTime[1]);
2532                        theResult = dateFormat.format(new Date(date));
2533                }
2534                return theResult;
2535        }
2536
2537        @CommandDescription("The ArrayElement command is used to get an element from an array by element ID (elementId).")
2538        @CommandExamples({ "<ArrayElement name='type:property' array='type:property' elementId='type:number'/>" })
2539        public void runCommandArrayElement(final Node aCurrentVar) throws Throwable {
2540                final String name = replaceProperties(aCurrentVar.getAttribute("name"));
2541                final String arrayName = replaceProperties(aCurrentVar.getAttribute("array"));
2542                if (arrayName == null) {
2543                        throw new IllegalArgumentException("'array' attribute required.");
2544                }
2545
2546                final String theElementAttribut = replaceProperties(aCurrentVar.getAttribute("elementId"));
2547                if (theElementAttribut == null) {
2548                        throw new IllegalArgumentException("'elementId' attribute required.");
2549                }
2550
2551                final String type = replaceProperties(aCurrentVar.getAttribute("type"));
2552                Object theValue = getVariableValue(arrayName);
2553
2554                final int theElementId = Integer.parseInt(theElementAttribut);
2555
2556                theValue = convert(theValue, type);
2557
2558                Object theResult = null;
2559                if (theValue instanceof JSONArray) {
2560                        JSONArray jsonArray = (JSONArray) theValue;
2561                        theResult = jsonArray.get(theElementId);
2562                } else if (theValue instanceof String[]) {
2563                        theResult = ((String[]) theValue)[theElementId];
2564                } else if (theValue instanceof List) {
2565                        theResult = ((List) theValue).get(theElementId);
2566                } else if (theValue instanceof String) {
2567                        if ("string".equalsIgnoreCase(type)) {
2568                                theResult = new String(new char[] { ((String) theValue).charAt(theElementId) });
2569                        } else if (theElementId == 0) {
2570                                theResult = theValue;
2571                        }
2572                } else if (theElementId == 0) {
2573                        theResult = theValue;
2574                }
2575
2576                setVariableValue(name, theResult);
2577        }
2578
2579        @CommandDescription("The Calculate command is implemented in the Java Expression Language (JEXL), "
2580                        + "for more information see: https://commons.apache.org/proper/commons-jexl. "
2581                        + "The value of the expression will be stored in the variable with the name specified in the `name` attribute.")
2582        @CommandExamples({ "<Calculate name='type:property' expressions='type:string'/>",
2583                        "<Calculate name='type:property'>...</Calculate>" })
2584        public void runCommandCalculate(final Node command) throws Throwable {
2585                final String name = replaceProperties(command.getAttribute("name"));
2586                String expressions = replaceProperties(command.getAttribute("expressions"));
2587                if (expressions == null) {
2588                        expressions = replaceProperties(command.getInnerText());
2589                }
2590
2591                Map<String, Object> namespaces = new HashMap<>();
2592                namespaces.put("Math", Math.class);
2593                JexlEngine jexl = new JexlBuilder().namespaces(namespaces).create();
2594
2595                JexlExpression expr_c = jexl.createExpression(expressions);
2596                JexlContext context = new MapContext();
2597
2598                Map<String, String> attributes = command.getAttributes();
2599                for (Map.Entry<String, String> entry : attributes.entrySet()) {
2600                        String key = entry.getKey();
2601                        String val = entry.getValue();
2602
2603                        Object variableValue = getVariableValue(val);
2604                        if (variableValue instanceof String) {
2605                                try {
2606                                        variableValue = Double.parseDouble((String) variableValue);
2607                                } catch (NumberFormatException e) {
2608                                        // DO NOTHING
2609                                }
2610                        }
2611
2612                        context.set(key, variableValue);
2613                }
2614
2615                if (expr_c != null) {
2616                        Object result = expr_c.evaluate(context);
2617
2618                        if (result != null) {
2619                                setVariableValue(name, result);
2620                        } else {
2621                                setVariableValue(name, null);
2622                        }
2623                }
2624        }
2625
2626        @CommandExamples({
2627                        "<Parse name='type:property' source='type:property' type='enum:array|json|csv|fixed-length-line'/>",
2628                        "<Parse name='type:property' source='type:property' type='fixed-length-line' length='type:integer'/>" })
2629        public void runCommandParse(final Node command) throws Throwable {
2630                Object source = attrValue(command, "source");
2631                final String theNameAttribut = replaceProperties(command.getAttribute("name"));
2632                final String type = replaceProperties(command.getAttribute("type"));
2633
2634                if ("json".equalsIgnoreCase(type)) {
2635                        String theValue = ObjectUtils.toString(source);
2636                        if (StringUtils.isNotBlank(theValue)) {
2637                                Object obj = new JSONObject(theValue);
2638                                if (obj instanceof JSONObject) {
2639                                        JSONObject result = new JSONObject(theValue);
2640                                        setVariableValue(theNameAttribut, result);
2641                                } else if (obj instanceof JSONArray) {
2642                                        JSONArray result = new JSONArray(theValue);
2643                                        String[] array = new String[result.length()];
2644                                        for (int i = 0; i < result.length(); i++) {
2645                                                array[i] = ObjectUtils.toString(result.get(i));
2646                                        }
2647                                        setVariableValue(theNameAttribut, array);
2648                                }
2649                        } else {
2650                                setVariableValue(theNameAttribut, theValue);
2651                        }
2652
2653                } else if ("array".equalsIgnoreCase(type)) {
2654                        String theValue = ObjectUtils.toString(source);
2655
2656                        String[] array = StringUtils.split(theValue, "\r\n");
2657                        setVariableValue(theNameAttribut, array);
2658                } else if ("csv".equalsIgnoreCase(type)) {
2659                        String theValue = ObjectUtils.toString(source);
2660
2661                        CSVReader reader = new CSVReader(new StringReader(theValue));
2662                        List<String[]> r = reader.readAll();
2663
2664                        setVariableValue(theNameAttribut, r);
2665                } else if ("fixed-length-line".equalsIgnoreCase(type)) {
2666                        if (source instanceof String) {
2667                                String input = (String) source;
2668                                int lineLength = Integer.parseInt(attr(command, "length"));
2669                                List<Object> result = IntStream.range(0, (input.length() + lineLength - 1) / lineLength)
2670                                                .mapToObj(i -> input.substring(i * lineLength, Math.min((i + 1) * lineLength, input.length())))
2671                                                .collect(Collectors.toList());
2672                                setVariableValue(theNameAttribut, result);
2673                        }
2674                }
2675        }
2676
2677        @CommandExamples({ "<FindObject name='type:property' source='type:property' withValue='type:string'/>" })
2678        public void runCommandFindObject(final Node aCurrentNode) throws Throwable {
2679                Object json = getVariableValue(replaceProperties(aCurrentNode.getAttribute("source")));
2680                String withValue = replaceProperties(aCurrentNode.getAttribute("withValue"));
2681
2682                if (json instanceof String) {
2683                        String jsonStr = (String) json;
2684                        if (StringUtils.startsWith(jsonStr, "{")) {
2685                                json = new JSONObject(jsonStr);
2686                        } else if (StringUtils.startsWith(jsonStr, "[")) {
2687                                json = new JSONArray(jsonStr);
2688                        }
2689                } else if (json instanceof String[]) {
2690                        String[] jsonStrArray = (String[]) json;
2691                        JSONArray array = new JSONArray();
2692                        for (int i = 0; i < jsonStrArray.length; i++) {
2693                                String jsonStr = jsonStrArray[i];
2694                                array.put(new JSONObject(jsonStr));
2695                        }
2696                        json = array;
2697                }
2698
2699                Object value = find(json, withValue);
2700
2701                final String name = replaceProperties(aCurrentNode.getAttribute("name"));
2702                applyResult(aCurrentNode, name, value);
2703        }
2704
2705        private JSONObject find(Object obj, String withValue) throws JSONException {
2706                if (obj instanceof JSONObject) {
2707                        JSONObject json = (JSONObject) obj;
2708
2709                        JSONArray names = json.names();
2710                        if (names != null) {
2711                                for (int i = 0; i < names.length(); i++) {
2712                                        String name = names.getString(i);
2713                                        Object object = json.get(name);
2714                                        if (object instanceof String) {
2715                                                if (ObjectUtils.equals(object, withValue)) {
2716                                                        return json;
2717                                                }
2718                                        } else {
2719                                                JSONObject find = find(object, withValue);
2720                                                if (find != null) {
2721                                                        return find;
2722                                                }
2723                                        }
2724                                }
2725                        }
2726                } else if (obj instanceof JSONArray) {
2727                        JSONArray array = (JSONArray) obj;
2728
2729                        for (int j = 0; j < array.length(); j++) {
2730                                Object item = array.get(j);
2731
2732                                JSONObject find = find(item, withValue);
2733                                if (find != null) {
2734                                        return find;
2735                                }
2736                        }
2737                }
2738                return null;
2739        }
2740
2741        @CommandDescription("`Var` command is used to define or update a variable.\r\n"
2742                        + "Attribute `init` defines how variable will be set. This attribute is optional; if it is not defined,\r\n"
2743                        + "the variable will be initialized by `value` attribute or inner data. It can be set to the following values:\r\n"
2744                        + "- `default` is used to initialize the variable's value if it is not defined,\r\n"
2745                        + "- `console` is used for input data by the user,\r\n"
2746                        + "- `mandatory` is used for show the dialog if the variable is not defined  and raises an error if the user select the 'Skip' input action.\r\n"
2747                        + "- `file` is used for loadind variables from property file.\r\n"
2748                        + "Attribute `type` defines a type of variable. It is used to define the variable type and special rules for the input dialog:\r\n"
2749                        + "The `string` parameter is the default and is not required to define a string property, it should only be used to convert the variable's value to a string type.;\r\n"
2750                        + "- `array` is used for create the value of the array type;\r\n"
2751                        + "- `map` is used for create the value of the map type;\r\n"
2752                        + "- `json` is used for create the property of the json type. Json should be defined as a inner text, don't use `value` attribute;\r\n"
2753                        + "- `password` the user dialog text field will be masked;\r\n"
2754                        + "- `path` a dialog for a dir opening will be shown; \r\n"
2755                        + "- `text` type is used only for large text values, and the value is not saved in the preferences.")
2756        @CommandExamples({
2757                        "<Var name='type:property' init='enum:console|mandatory|default' type='enum:text|password|path'/>",
2758                        "<Var name='type:property' type='enum:array|number|map|json|string' />",
2759                        "<Var name='type:property' source='type:property' start='type:string' end='type:string'/>",
2760                        "<Var name='type:property'><item>...</item></Var>",
2761                        "<Var name='type:property' type='map'><item key='type:string'>...</item></Var>",
2762                        "<Var name='type:property' type='enum:array|number' file='type:path'/>",
2763                        "<Var init='file' file='type:path'/>", "<Var name='type:property' source='type:property' />" })
2764        public void runCommandVar(final Node action) throws Throwable {
2765                final String name = attr(action, "name");
2766                String description = attr(action, "description");
2767                String theInit = attr(action, "init");
2768
2769                Object theOldValue = getVariableValue(name);
2770                if ("default".equals(theInit) && theOldValue != null) {
2771                        if (theOldValue instanceof String) {
2772                                if (StringUtils.isNotBlank((String) theOldValue)) {
2773                                        return;
2774                                }
2775                        } else if (theOldValue instanceof String[] && ((String[]) theOldValue).length > 0) {
2776                                return;
2777                        }
2778                }
2779
2780                String file = attr(action, "file");
2781                if ("file".equals(theInit)) {
2782                        File propertiesFile = getFile(file);
2783                        loadProperties(propertiesFile, this.variables, true);
2784                        return;
2785                }
2786
2787                String source = replaceProperties(action.getAttribute("source"), true);
2788                Object theValue = getVariableValue(name);
2789                if (source != null) {
2790                        String sourceVarName = replaceProperties(source);
2791                        theValue = getVariableValue(sourceVarName);
2792                        theOldValue = theValue;
2793                        setVariableValue(name, theValue);
2794                }
2795
2796                String value = action.getAttribute("value");
2797                if (value != null) {
2798                        theValue = replaceProperties(value);
2799                }
2800
2801                // Item setting
2802                String type = StringUtils.defaultIfEmpty(action.getAttribute("type"), "");
2803                if (action.size() > 0) {
2804                        if (Node.TEXT_TEAG_NAME.equals(action.getNode(0).getTag())) {
2805                                final Node theTextNode = action.getNode(0);
2806                                theValue = replaceProperties(theTextNode.getText());
2807
2808                        } else {
2809                                switch (type) {
2810                                case "map":
2811                                        Node[] nodes = action.getNodes("item");
2812                                        theValue = new LinkedHashMap<String, String>();
2813                                        for (Node node : nodes) {
2814                                                ((Map) theValue).put(node.getAttribute("key"), replaceProperties(node.getInnerText()));
2815                                        }
2816                                        break;
2817
2818                                default:
2819                                        List<Object> theItemArray = new ArrayList<>();
2820                                        for (int i = 0; i < action.size(); i++) {
2821                                                final Node theCurrentAction = (Node) action.get(i);
2822                                                Node[] theItemNodes = theCurrentAction.getTextNodes();
2823                                                if (theItemNodes != null && theItemNodes.length == 1) {
2824                                                        final String replaceProperties = replaceProperties(theItemNodes[0].getText(), true);
2825                                                        theItemArray.add(replaceProperties);
2826                                                } else {
2827                                                        if (theCurrentAction.size() == 1) {
2828                                                                Node node = theCurrentAction.get(0);
2829                                                                EasyUtils.removeAllAttributes(node, Node.TAG_ID);
2830                                                                theItemArray.add(replaceProperties(node.getXMLText(), true));
2831                                                        } else {
2832                                                                theItemArray = null;
2833                                                        }
2834                                                        break;
2835                                                }
2836                                        }
2837                                        theValue = theItemArray;
2838                                }
2839                        }
2840                }
2841
2842                boolean contains = StringUtils.contains(theInit, "mandatory");
2843                boolean isConsoleInput = StringUtils.contains(theInit, "console");
2844                boolean mandatory = StringUtils.contains(theInit, "mandatory");
2845                if (mandatory) {
2846                        if (!isEmpty(theOldValue)) {
2847                                setVariableValue(name, theValue);
2848                        } else {
2849                                isConsoleInput = true;
2850                        }
2851                }
2852
2853                final boolean theIntegerType = "number".equals(type);
2854                if (theIntegerType) {
2855                        String string = ObjectUtils.toString(theValue);
2856                        if (StringUtils.isNotBlank(string) && !NumberUtils.isNumber(string)) {
2857                                setVariableValue(name, null);
2858                                throw new NumberFormatException(string);
2859                        }
2860                }
2861
2862                final boolean theArrayType = "array".equals(type);
2863                if (name != null && !(theValue == null && theArrayType == false)) {
2864                        if (theArrayType) {
2865                                char separatorChar = ',';
2866                                if (theValue instanceof String) {
2867                                        String[] split = StringUtils.split((String) theValue, separatorChar);
2868                                        theValue = split != null ? Arrays.asList(split) : null;
2869                                }
2870                        }
2871                        if (theArrayType && theValue == null) {
2872                                theValue = new String[0];
2873                        }
2874
2875                        if (theArrayType && file != null) {
2876                                ArrayList<String> buffer = new ArrayList<String>();
2877
2878                                if (theValue instanceof String[]) {
2879                                        for (String string : (String[]) theValue) {
2880                                                buffer.add(string);
2881                                        }
2882                                } else if (theValue instanceof Collection) {
2883                                        buffer = new ArrayList<String>((Collection<String>) theValue);
2884                                }
2885
2886                                try (InputStream inputStream = AEUtils.getInputStream(file, getBaseDir())) {
2887                                        String encoding = replaceProperties(action.getAttribute("charset"));
2888                                        if (encoding == null) {
2889                                                encoding = "UTF-8";
2890                                        }
2891                                        final BufferedReader theFileReader = new BufferedReader(
2892                                                        new InputStreamReader(inputStream, encoding));
2893                                        String readLine;
2894                                        while ((readLine = theFileReader.readLine()) != null) {
2895                                                buffer.add(readLine);
2896                                        }
2897                                }
2898                                theValue = buffer;
2899                        }
2900
2901                        theValue = convert(theValue, type);
2902                        setVariableValue(name, theValue);
2903                }
2904
2905                if (isConsoleInput) {
2906
2907                        if (theOldValue instanceof List && CollectionUtils.size(theOldValue) == 1) {
2908                                theOldValue = ((List) theOldValue).get(0);
2909                        }
2910
2911                        if ((action.size() == 0 || !action.getInnerText().isEmpty())
2912                                        && (isEmpty(theOldValue) || theOldValue instanceof String)) {
2913                                Object theInitialSelectionValue = null;
2914                                final Object o = getVariableValue(name);
2915                                if (o instanceof String) {
2916                                        theInitialSelectionValue = o;
2917                                }
2918                                if (o instanceof String[] && ((String[]) o).length > 0) {
2919                                        if (this.random == null) {
2920                                                this.random = SecureRandom.getInstance("SHA1PRNG");
2921                                        }
2922                                        theInitialSelectionValue = ((String[]) o)[this.random.nextInt(((String[]) o).length)];
2923                                }
2924
2925                                String defaultValue = AEWorkspace.getInstance().getDefaultUserConfiguration(".inputValue." + name,
2926                                                (String) theInitialSelectionValue);
2927                                if (this.recipeListener.getManager().isConsoleDefaultInput(name, description)
2928                                                && !(mandatory && defaultValue == null)) {
2929
2930                                        if ((o instanceof String[] && ArrayUtils.contains((String[]) o, defaultValue)) || o == null) {
2931                                                theInitialSelectionValue = defaultValue;
2932                                        }
2933
2934                                        setVariableValue(name, theInitialSelectionValue);
2935                                } else {
2936                                        boolean notifyMe = this.recipeListener.isNotifyMe();
2937                                        Object inputValue;
2938
2939                                        inputValue = this.recipeListener.getManager().inputValue(name, description, defaultValue, log, type,
2940                                                        notifyMe, this);
2941
2942                                        setVariableValue(name, inputValue);
2943                                }
2944
2945                        } else {
2946                                List<Object> possibleValues = null;
2947                                if (theOldValue instanceof List) {
2948                                        @SuppressWarnings("unchecked")
2949                                        final List<Object> theStringArray = (List<Object>) theOldValue;
2950                                        possibleValues = theStringArray;
2951                                } else if (theOldValue instanceof String[]) {
2952                                        possibleValues = new ArrayList<>();
2953                                        String[] array = (String[]) theOldValue;
2954                                        for (String item : array) {
2955                                                possibleValues.add(item);
2956                                        }
2957                                } else {
2958                                        possibleValues = new ArrayList<>();
2959                                        for (int i = 0; i < action.size(); i++) {
2960                                                final Node theCurrentAction = (Node) action.get(i);
2961                                                possibleValues.add(theCurrentAction.getInnerText());
2962                                        }
2963                                }
2964
2965                                if (this.recipeListener.getManager().isConsoleDefaultInput(name, null)) {
2966                                        String theInitialSelectionValue = randomSelect(possibleValues);
2967                                        if (!randomSelect) {
2968                                                theInitialSelectionValue = AEWorkspace.getInstance()
2969                                                                .getDefaultUserConfiguration(".choiceValue." + name, theInitialSelectionValue);
2970
2971                                        }
2972                                        setVariableValue(name, theInitialSelectionValue);
2973                                } else {
2974                                        boolean notifyMe = this.recipeListener.isNotifyMe();
2975                                        final Object theValueOut = this.recipeListener.getManager().choiceValue(name, description,
2976                                                        possibleValues.toArray(), log, notifyMe, this);
2977                                        setVariableValue(name, theValueOut);
2978                                }
2979                        }
2980                }
2981
2982                theValue = getVariableValue(name);
2983
2984                String start = replaceProperties(action.getAttribute("start"));
2985                String end = replaceProperties(action.getAttribute("end"));
2986
2987                if (StringUtils.isNotEmpty(start)) {
2988                        if (theValue instanceof byte[]) {
2989                                theValue = new String((byte[]) theValue);
2990                        }
2991                        theValue = StringUtils.substringAfter(ObjectUtils.toString(theValue), start);
2992                        if (StringUtils.isBlank((String) theValue)) {
2993                                theValue = null;
2994                        }
2995                }
2996                if (StringUtils.isNotEmpty(end)) {
2997                        theValue = StringUtils.substringBefore((String) theValue, end);
2998                        if (StringUtils.isBlank((String) theValue)) {
2999                                theValue = null;
3000                        }
3001                }
3002
3003                applyResult(action, name, theValue);
3004
3005                if (contains && theValue == null) {
3006                        stop();
3007                }
3008        }
3009
3010        private Object convert(Object theValue, String type) throws JSONException {
3011                if ("json".equals(type)) {
3012                        Object parse = theValue;
3013                        if (theValue instanceof String[]) {
3014                                String[] array = (String[]) theValue;
3015                                List<String> asList = Arrays.asList(array);
3016                                theValue = new JSONArray(asList);
3017                        } else if (theValue instanceof String) {
3018                                if (StringUtils.startsWith((String) theValue, "{")) {
3019                                        parse = new JSONObject(theValue.toString());
3020                                } else if (StringUtils.startsWith((String) theValue, "[")) {
3021                                        parse = new JSONArray(theValue.toString());
3022                                }
3023                        }
3024                        theValue = parse;
3025                } else if ("string".equals(type) && theValue instanceof String[]) {
3026                        theValue = StringUtils.join((String[]) theValue);
3027                }
3028
3029                return theValue;
3030        }
3031
3032        private String randomSelect(List possibleValues) throws NoSuchAlgorithmException {
3033                final String theInitialSelectionValue;
3034                if (this.random == null) {
3035                        this.random = SecureRandom.getInstance("SHA1PRNG");
3036                }
3037                theInitialSelectionValue = (String) possibleValues.get(this.random.nextInt(possibleValues.size()));
3038                return theInitialSelectionValue;
3039        }
3040
3041        @CommandExamples({ "<Note name='type:string' connection='type:string'>...</Note>" })
3042        public void runCommandNote(final Node aCurrentAction) throws Throwable {
3043        }
3044
3045        @CommandExamples({ "<!-- ... -->" })
3046        public void runCommandComment(final Node aCurrentAction) throws Throwable {
3047        }
3048
3049        @CommandExamples({ "<Extern class='type:processor'>\n...\n</Extern>" })
3050        public void runCommandExtern(final Node command) throws Throwable {
3051                final Processor newInstance = makeProcessor(command);
3052                boolean success = false;
3053                try {
3054                        this.externProcessor = newInstance;
3055                        externProcessor.setTestName(getTestName());
3056                        newInstance.init(this, command);
3057                        boolean rootRecipe = isRootRecipe();
3058                        newInstance.setRootContext(rootRecipe);
3059                        newInstance.stackTask.addAll(this.stackTask);
3060                        newInstance.taskNode(command, false);
3061
3062                        this.variables = newInstance.variables;
3063                        success = true;
3064                } finally {
3065                        externProcessor.complete(success);
3066                        externProcessor = null;
3067                }
3068        }
3069
3070        @CommandDescription("The Confirm command is used to confirm the user's decision to continue a "
3071                        + "task or execute internal code. The name attribute is used as the dialog box title "
3072                        + "and as a variable name if confirmation is required without displaying a dialog box "
3073                        + "to the user. If the variable exists, confirmation is automatically applied if the "
3074                        + "value is true, or rejected if the value is not true.")
3075        @CommandExamples({ "<Confirm message='type:string' name='type:string'/>",
3076                        "<Confirm message='type:string' name='type:string'>...</Confirm>" })
3077        public void runCommandConfirm(final Node aCurrentAction) throws Throwable {
3078                final String message = attr(aCurrentAction, "message");
3079                final String name = attr(aCurrentAction, "name");
3080                final Object nameVar = getVariableValue(name);
3081
3082                boolean confirmed = false;
3083                if (nameVar != null && nameVar instanceof String) {
3084                        confirmed = BooleanUtils.toBoolean((String) nameVar);
3085                } else {
3086                        AEManager manager = this.recipeListener.getManager();
3087                        confirmed = manager.confirmation(name, message, this, this.recipeListener.isNotifyMe());
3088                }
3089
3090                if (aCurrentAction.size() > 0) {
3091                        if (confirmed) {
3092                                taskNode(aCurrentAction, false);
3093                        }
3094                } else {
3095                        if (!confirmed) {
3096                                stop();
3097                        }
3098                }
3099        }
3100
3101        @CommandDescription("The WhileRun command is used to notify the user that a process has started. "
3102                        + "The user can close the WhileRun dialog box without affecting the process's execution. "
3103                        + "The dialog box closes after the code within the WhileRun tag completes execution. "
3104                        + "The name attribute is used as the dialog box title and variable name. "
3105                        + "If the dialog box should not be opened, a variable with a name corresponding to "
3106                        + "the dialog box name should be assigned a value other than `true`.")
3107        @CommandExamples({ "<WhileRun name='type:string' message='type:string'>...</WhileRun>" })
3108        public void runCommandWhileRun(final Node aCurrentAction) throws Throwable {
3109                final String message = attr(aCurrentAction, "message");
3110                final String name = attr(aCurrentAction, "name");
3111                final Object nameVar = getVariableValue(name);
3112
3113                if (!(nameVar instanceof String) || BooleanUtils.toBoolean((String) nameVar)) {
3114                        AEManager manager = this.recipeListener.getManager();
3115                        MessageHandler handler = manager.message(this, name, message, this.recipeListener.isNotifyMe());
3116                        taskNode(aCurrentAction, false);
3117                        handler.close();
3118                }
3119        };
3120
3121        @CommandDescription("The `Server` command initializes and starts a server socket listener, "
3122                        + "operating as a blocking command that prevents the execution of subsequent commands until it completes.")
3123        @CommandExamples({ "<Server port='type:integer' request='type:property' response='type:property' > ... </Server>",
3124                        "<Server port='type:integer' numbers='type:integer' request='type:property' response='type:property' > ... </Server>" })
3125        public void runCommandServer(final Node aCurrentAction) throws Throwable {
3126
3127                String encoding = replaceProperties(aCurrentAction.getAttribute("charset"));
3128                if (encoding == null) {
3129                        encoding = "UTF-8";
3130                }
3131
3132                final int thePort = Integer.parseInt(replaceProperties(aCurrentAction.getAttribute("port")));
3133                int maxNumber = 0;
3134
3135                final String theMaxNumbers = replaceProperties(aCurrentAction.getAttribute("numbers"));
3136                if (theMaxNumbers != null) {
3137                        maxNumber = Integer.parseInt(theMaxNumbers);
3138                }
3139                final String request = attr(aCurrentAction, "request");
3140                final String response = attr(aCurrentAction, "response");
3141
3142                ServerAction server = new ServerAction(this, aCurrentAction, thePort, request, response, encoding, maxNumber);
3143                server.perform();
3144        }
3145
3146        @CommandExamples({ "<Restore/>", "<Restore name='type:property'> ... </Restore>",
3147                        "<Restore except='type:property'> ... </Restore>" })
3148        public void runCommandRestore(final Node command) throws Throwable {
3149                String name = attr(command, "name");
3150                String except = attr(command, "except");
3151
3152                if (CollectionUtils.isEmpty(command)) {
3153                        final Map<String, Object> systemVariables = getListener().getManager().getSystemVariables();
3154                        this.variables.clear();
3155                        this.variables.putAll(systemVariables);
3156                        debug("Task variables has been restored.");
3157                } else if (name != null) {
3158                        Object savedVariable = getVariableValue(name);
3159                        taskNode(command, false);
3160                        setVariableValue(name, savedVariable);
3161
3162                } else if (except != null) {
3163                        Map<String, Object> savedVariables = this.variables;
3164                        this.variables = new HashMap<String, Object>(this.variables);
3165
3166                        taskNode(command, false);
3167
3168                        Object object = getVariableValue(except);
3169                        this.variables.clear();
3170                        this.variables = savedVariables;
3171                        setVariableValue(except, object);
3172                }
3173
3174        }
3175
3176        @CommandDescription("Use the Finally command tag before protected code to ensure it is executed "
3177                        + "before exiting the current parent tag, as usual Finally is defined as a first executable command.\n"
3178                        + "Example: `<Recipe><Finally> ...finally code... </Finally> ...some code... </Recipe>`")
3179        @CommandExamples({ "<Finally> ... </Finally>" })
3180        public void runCommandFinally(final Node aCurrentAction) throws Throwable {
3181        }
3182
3183        @CommandExamples({ "<Break/>" })
3184        public void runCommandBreak(final Node aCurrentAction) throws Throwable {
3185        }
3186
3187        @CommandExamples({ "<Stop/>", "<Stop ifNull='type:property'/>" })
3188        public void runCommandStop(final Node aCurrentAction) throws Throwable {
3189        }
3190
3191        private boolean isEmpty(Object theOldValue) {
3192                boolean result = false;
3193                if (theOldValue == null) {
3194                        result = true;
3195                } else if (theOldValue instanceof String[] && ((String[]) theOldValue).length == 0) {
3196                        result = true;
3197                } else if (theOldValue instanceof Map && ((Map) theOldValue).size() == 0) {
3198                        result = true;
3199                }
3200                return result;
3201        }
3202
3203        public void setVariableValue(String name, final Object value) {
3204                if (name != null) {
3205                        super.setVariableValue(name, value);
3206                        if (this.recipeListener != null && name.startsWith("!") == false) {
3207                                this.recipeListener.changeVariable(name, value);
3208                        }
3209                }
3210        }
3211
3212}