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                        final Object variableValue = getVariableValue(theArrayName);
974                        if (variableValue instanceof String[]) {
975                                final String[] variableValueArray = (String[]) variableValue;
976                                for (int i = 0; i < variableValueArray.length; i++) {
977                                        if (theValue.equals(variableValueArray[i])) {
978                                                setVariableValue(theName, "true");
979                                                return;
980                                        }
981                                }
982                        }
983
984                        if (variableValue instanceof String) {
985                                final String value = (String) variableValue;
986                                if (theValue.equals(value)) {
987                                        setVariableValue(theName, "true");
988                                        return;
989                                }
990                        }
991                        setVariableValue(theName, "false");
992                } else {
993                        if (theText == null || theValue == null || theName == null) {
994                                return;
995                        }
996                        final int indexOf = theText.indexOf(theValue);
997                        setVariableValue(theName, indexOf >= 0 ? "true" : "false");
998                }
999        }
1000
1001        @CommandExamples({ "<Load name='type:property' init='console'/>",
1002                        "<Load name='type:property' init='console'/>...</Load>", "<Load name='type:property' file='type:path'/>",
1003                        "<Load name='type:property' file='type:path' timeout='type:time' mode='enum:default|noreplace|escapeJS|bytes'/>",
1004                        "<Load name='type:property' url='type:url' timeout='type:time' mode='enum:default|noreplace|escapeJS|bytes'/>" })
1005        public void runCommandLoad(final Node action) throws Throwable {
1006                final boolean noreplace = "noreplace".equals(action.getAttribute("mode"));
1007                final boolean isArray = "array".equals(action.getAttribute("mode"));
1008                final boolean defaultMode = "default".equals(action.getAttribute("mode"));
1009                final boolean escapeJSValue = "escapeJS".equals(action.getAttribute("mode"));
1010                final boolean bytes = "bytes".equals(action.getAttribute("mode"));
1011                final boolean base64 = "base64".equals(action.getAttribute("mode"));
1012
1013                String theEncoding = replaceProperties(action.getAttribute("encoding"));
1014                if (theEncoding == null) {
1015                        theEncoding = "UTF-8";
1016                }
1017
1018                final String name = replaceProperties(action.getAttribute("name"));
1019                int timeout = (int) attrTime(action, "timeout", "0");
1020
1021                final String theUrl = replaceProperties(action.getAttribute("url"));
1022                if (theUrl != null) {
1023                        URL url = new URL(theUrl);
1024                        long startTime = System.currentTimeMillis();
1025                        while (timeout == 0 || System.currentTimeMillis() < startTime + timeout) {
1026                                if (isStopped()) {
1027                                        break;
1028                                }
1029                                try {
1030                                        URLConnection openConnection = url.openConnection();
1031                                        openConnection.setReadTimeout(timeout);
1032                                        byte[] byteArray = IOUtils.toByteArray(openConnection.getInputStream());
1033                                        if (escapeJSValue) {
1034                                                String data = StringEscapeUtils.escapeJavaScript(new String(byteArray));
1035                                                setVariableValue(name, data);
1036                                        } else {
1037                                                setVariableValue(name, new String(byteArray, theEncoding));
1038                                        }
1039                                        return;
1040
1041                                } catch (IOException e) {
1042                                        if (timeout == 0) {
1043                                                throw e;
1044                                        }
1045                                        quietWait(200);
1046                                }
1047                        }
1048                }
1049
1050                String filePath = (String) attr(action, "file");
1051                final boolean theDialog = isActiveInitFor(action, "console");
1052
1053                if (theDialog) {
1054                        File theFile = null;
1055                        if (filePath != null) {
1056                                theFile = getFile(filePath);
1057                        }
1058                        filePath = this.recipeListener.getManager().inputFile(name, null, theFile, log, this);
1059                }
1060
1061                if (filePath != null) {
1062                        if (base64 || bytes) {
1063                                File file = getFile(filePath);
1064                                debug("Loading file: " + file);
1065                                if (file.exists()) {
1066                                        try (FileInputStream input = new FileInputStream(file)) {
1067                                                Object byteArray = IOUtils.toByteArray(input);
1068                                                if (base64) {
1069                                                        byteArray = Base64.getEncoder().encodeToString((byte[]) byteArray);
1070                                                }
1071                                                setVariableValue(name, byteArray);
1072                                        }
1073                                } else {
1074                                        throw new FileNotFoundException(file.getAbsolutePath());
1075                                }
1076                                return;
1077                        }
1078
1079                        int iterations = timeout / 1000;
1080                        if (iterations == 0) {
1081                                iterations = 1;
1082                        }
1083                        for (int i = 0; i < iterations; i++) {
1084                                if (isStopped()) {
1085                                        break;
1086                                }
1087                                try {
1088
1089                                        if (!action.isEmpty()) {
1090                                                File file = getFile(filePath);
1091                                                try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
1092                                                        String line;
1093                                                        while ((line = reader.readLine()) != null) {
1094                                                                if (this.breakFlag > 0) {
1095                                                                        break;
1096                                                                }
1097                                                                setVariableValue(name, line);
1098                                                                taskNode(action, false);
1099                                                        }
1100                                                } finally {
1101                                                        if (this.breakFlag > 0) {
1102                                                                breakFlag--;
1103                                                        }
1104                                                }
1105
1106                                        } else {
1107
1108                                                String text = AEUtils.loadResource(filePath, getBaseDir(), theEncoding);
1109                                                if (!noreplace) {
1110                                                        text = replaceProperties(text, defaultMode);
1111                                                }
1112
1113                                                if (escapeJSValue) {
1114                                                        String data = StringEscapeUtils.escapeJavaScript(text);
1115                                                        setVariableValue(name, data);
1116                                                } else {
1117                                                        Object val = text;
1118                                                        if (isArray) {
1119                                                                val = StringUtils.split(text, "\n\r");
1120                                                        }
1121
1122                                                        setVariableValue(name, val);
1123                                                }
1124                                        }
1125
1126                                        break;
1127
1128                                } catch (final FileNotFoundException e) {
1129                                        if (timeout == 0) {
1130                                                throw e;
1131                                        }
1132                                        quietWait(1000);
1133                                } catch (final Exception e) {
1134                                        throw e;
1135                                }
1136                        }
1137                } else {
1138                        if (isActiveInitFor(action, "mandatory")) {
1139                                stop();
1140                        }
1141                }
1142
1143        }
1144
1145        @CommandExamples({ "<Remove name='type:property'/>", "<Remove file='type:path'/>",
1146                        "<Remove name='type:property' history='type:boolean'/>" })
1147        public void runCommandRemove(final Node aCurrentAction) throws Throwable {
1148                String theFileName = aCurrentAction.getAttribute("file");
1149                if (theFileName != null) {
1150                        theFileName = replaceProperties(theFileName);
1151                        final File theFile = getFile(theFileName);
1152                        if (theFile.isDirectory()) {
1153                                FileUtils.deleteDirectory(theFile);
1154                        } else {
1155                                if (theFile.delete() == false) {
1156                                        new Exception("File '" + theFile.getAbsolutePath() + "' is not deleted.");
1157                                }
1158                        }
1159                }
1160                String theVarName = aCurrentAction.getAttribute("name");
1161                if (theVarName != null) {
1162                        theVarName = replaceProperties(theVarName);
1163                        setVariableValue(theVarName, null);
1164                        if (Boolean.parseBoolean((attr(aCurrentAction, "history")))) {
1165                                AEWorkspace.getInstance().setDefaultUserConfiguration(".inputValue." + theVarName, null);
1166                        }
1167                }
1168        }
1169
1170        @CommandDescription("Load the web page by `url`. The response is stored in the variable defined by the `name` attribute.\r\n"
1171                        + "The optional `auth` attribute set the 'Authorization' header of request.")
1172        @CommandExamples({ "<Get name='type:property' url='type:url' /> ",
1173                        "<Get name='type:property' url='type:url' timeout='type:time'/> ",
1174                        "<Get name='type:property' url='type:url' auth='type:string' mediaType='type:string' retries='1' timeout='type:time'/> " })
1175        public void runCommandGet(final Node action) throws Throwable {
1176
1177                String mediaTypeStr = attr(action, "mediaType");
1178                mediaTypeStr = StringUtils.defaultIfEmpty(mediaTypeStr, "application/json");
1179
1180                String url = attr(action, "url");
1181                String auth = attr(action, "auth");
1182                int retries = Integer.parseInt(attr(action, "retries", "1"));
1183
1184                Builder builder = new okhttp3.Request.Builder().url(url).get();
1185
1186                if (mediaTypeStr != null) {
1187                        builder.addHeader("Content-Type", mediaTypeStr);
1188                }
1189                if (auth != null) {
1190                        builder.addHeader("Authorization", auth);
1191                }
1192
1193                okhttp3.Request request = builder.build();
1194
1195                for (int i = 0; i < retries; i++) {
1196                        if (isStopped()) {
1197                                break;
1198                        }
1199
1200                        try {
1201                                okhttp3.OkHttpClient.Builder newBuilder = new OkHttpClient().newBuilder();
1202                                long timeout = attrTime(action, "timeout", "0");
1203                                if (timeout > 0) {
1204                                        newBuilder.connectTimeout(timeout, TimeUnit.MILLISECONDS);
1205                                        newBuilder.readTimeout(timeout, TimeUnit.MILLISECONDS);
1206                                        newBuilder.writeTimeout(timeout, TimeUnit.MILLISECONDS);
1207                                }
1208                                OkHttpClient client = newBuilder.build();
1209
1210                                try (Response response = client.newCall(request).execute()) {
1211                                        String value = new String(response.body().bytes(), "UTF-8");
1212                                        String name = attr(action, "name");
1213                                        if (name != null) {
1214                                                setVariableValue(name, value);
1215                                        }
1216                                }
1217
1218                                break;
1219                        } catch (SocketTimeoutException e) {
1220                                if (i == 2) {
1221                                        throw e;
1222                                }
1223                        }
1224                }
1225        }
1226
1227        @CommandDescription("Post the body defined by the `body` attribute to `url`. "
1228                        + "The response is stored in the variable defined by the `name` attribute. "
1229                        + "If the body is not simple, you should create a special variable and pass its value via $var{}.\r\n"
1230                        + "The optional `auth` attribute set the 'Authorization' header of request.")
1231        @CommandExamples({ "<Post name='type:property' url='type:url' body='type:string'/> ",
1232                        "<Post name='type:property' url='type:url' body='type:string' timeout='type:time'/> ",
1233                        "<Post name='type:property' url='type:url' auth='type:string' body='type:string' mediaType='type:string' timeout='type:time'/> " })
1234        public void runCommandPost(final Node aCurrentAction) throws Throwable {
1235                String mediaTypeStr = attr(aCurrentAction, "mediaType");
1236                mediaTypeStr = StringUtils.defaultIfEmpty(mediaTypeStr, "application/json");
1237
1238                String url = attr(aCurrentAction, "url");
1239                String auth = attr(aCurrentAction, "auth");
1240                String bodyStr = StringUtils.defaultIfEmpty(replaceProperties(aCurrentAction.getAttribute("body")), "");
1241
1242                MediaType mediaType = MediaType.parse(mediaTypeStr);
1243                okhttp3.RequestBody body = okhttp3.RequestBody.create(mediaType, bodyStr);
1244
1245                Builder addHeader = new okhttp3.Request.Builder().url(url).method("POST", body).addHeader("Content-Type",
1246                                mediaTypeStr);
1247                if (auth != null) {
1248                        addHeader = addHeader.addHeader("Authorization", auth);
1249                }
1250                okhttp3.Request request = addHeader.addHeader("Accept", "*/*").build();
1251                okhttp3.OkHttpClient.Builder newBuilder = new OkHttpClient().newBuilder();
1252
1253                long timeout = attrTime(aCurrentAction, "timeout", "0");
1254                if (timeout > 0) {
1255                        newBuilder.connectTimeout(timeout, TimeUnit.MILLISECONDS);
1256                        newBuilder.readTimeout(timeout, TimeUnit.MILLISECONDS);
1257                        newBuilder.writeTimeout(timeout, TimeUnit.MILLISECONDS);
1258                }
1259                OkHttpClient client = newBuilder.build();
1260                try (Response response = client.newCall(request).execute()) {
1261                        String value = new String(response.body().bytes(), "UTF-8");
1262
1263//      if(response.code() == 404) {
1264//              
1265//      }
1266
1267                        String name = attr(aCurrentAction, "name");
1268                        if (name != null) {
1269                                setVariableValue(name, value);
1270                        }
1271                }
1272        }
1273
1274        @CommandDescription("This method allows you to send either HTTP(S) requests or raw socket messages, "
1275                        + "with support for parameters, headers, authentication, and response handling."
1276                        + "The `Request` command executes a network request based on the configuration attributes:\r\n"
1277                        + "- `method`: Specifies the type of network request to perform. Common values are `get` for HTTP GET requests, "
1278                        + "`post` for HTTP POST requests, or `socket` for raw socket communication.\r\n"
1279                        + "- `request`: is the name of the request map variable which contains the data to be sent with the request. "
1280                        + "For HTTP requests, this may include the body and headers. For socket communication, "
1281                        + "it can be a string or byte array representing the message to send. \r\n"
1282                        + "- `response` is the name of the variable where the response data will be stored after the request is executed. "
1283                        + "This allows later access to the result of the network operation.\r\n"
1284                        + "- `host` is the target URL or host address for the network request. "
1285                        + "For HTTP(S), this is the full URL. For socket communication, "
1286                        + "it is typically in the format `hostname:port`\r\n"
1287                        + "- `timeout` is the maximum time to wait for the network request to complete before aborting.\r\n"
1288                        + "- `userName` is the username used for HTTP Basic Authentication, if required by the target server.\r\n"
1289                        + "- `password` is the password used for HTTP Basic Authentication, paired with `userName`.\r\n")
1290        @CommandExamples({
1291                        "<Request method='enum:socket|get|post' request='type:property[Map{header;body}]' "
1292                                        + "response='type:property[Map{status;body}]' host='type:url' timeout='type:time'/>",
1293                        "<Request method='get' response='type:property[Map{status;body}]' host='type:url' "
1294                                        + "userName='type:string' password='type:string'><param name='type:string' value='type:string'/></Request>" })
1295        public void runCommandRequest(final Node aCurrentAction) throws Throwable {
1296                Object request = attrValue(aCurrentAction, "request");
1297                final String responseName = attr(aCurrentAction, "response");
1298                final String method = StringUtils.defaultIfEmpty(attr(aCurrentAction, "method"), "socket");
1299                String theUrl = attr(aCurrentAction, "host");
1300
1301                String userName = attr(aCurrentAction, "userName");
1302                String password = attr(aCurrentAction, "password");
1303                HttpClientContext localContext = null;
1304                if (userName != null && password != null) {
1305                        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
1306                        credentialsProvider.setCredentials(AuthScope.ANY,
1307                                        new UsernamePasswordCredentials(userName + ":" + password));
1308                        localContext = HttpClientContext.create();
1309                        localContext.setCredentialsProvider(credentialsProvider);
1310                }
1311
1312                HttpRequestBase httpRequest = null;
1313
1314                final Node[] paramNodes = aCurrentAction.getNodes("param");
1315                if (paramNodes != null && paramNodes.length > 0) {
1316                        List<NameValuePairImplementation> parameters = new ArrayList<>();
1317                        for (Node node : paramNodes) {
1318                                String name = attr(node, "name");
1319                                String value = attr(node, "value");
1320
1321                                NameValuePairImplementation nameValuePair = new NameValuePairImplementation(name, value);
1322                                parameters.add(nameValuePair);
1323                        }
1324
1325                        String query = URLEncodedUtils.format(parameters, "UTF-8");
1326                        theUrl = theUrl + "?" + query;
1327                }
1328
1329                if (theUrl.toLowerCase().startsWith("http")) {
1330                        if ("get".equalsIgnoreCase(method)) {
1331                                httpRequest = new HttpGet(theUrl);
1332
1333                        } else if ("post".equalsIgnoreCase(method)) {
1334                                HttpPost httpPost = new HttpPost(theUrl);
1335                                if (request != null) {
1336                                        @SuppressWarnings("unchecked")
1337                                        final Map<String, Object> requestMap = (Map<String, Object>) request;
1338                                        Object bodyObject = requestMap.get("body");
1339                                        byte[] body = null;
1340                                        if (bodyObject instanceof byte[]) {
1341                                                body = (byte[]) bodyObject;
1342                                        } else if (bodyObject instanceof String) {
1343                                                body = ((String) bodyObject).getBytes();
1344                                        }
1345                                        httpPost.setEntity(new ByteArrayEntity(body));
1346                                }
1347                                httpRequest = httpPost;
1348
1349                        }
1350                        int timeout = (int) attrTime(aCurrentAction, "timeout", "5000");
1351
1352                        RequestConfig config = RequestConfig.custom().setConnectTimeout(timeout)
1353                                        .setConnectionRequestTimeout(timeout).setSocketTimeout(timeout).build();
1354                        httpRequest.setConfig(config);
1355                        if (request != null) {
1356                                @SuppressWarnings("unchecked")
1357                                Object headerObj = ((Map<String, Object>) request).get("header");
1358                                if (headerObj instanceof Map) {
1359                                        @SuppressWarnings("unchecked")
1360                                        Map<String, String> header = (Map<String, String>) headerObj;
1361                                        if (header != null) {
1362                                                Set<Entry<String, String>> entrySet = header.entrySet();
1363                                                for (Entry<String, String> entry : entrySet) {
1364                                                        httpRequest.setHeader(entry.getKey(), entry.getValue());
1365                                                }
1366                                        }
1367                                } else {
1368                                        String header = String.valueOf(headerObj);
1369                                        httpRequest.setHeader(StringUtils.substringBefore(header, ":").trim(),
1370                                                        StringUtils.substringAfter(header, ":").trim());
1371                                }
1372                        }
1373
1374                        CloseableHttpResponse response;
1375                        CloseableHttpClient httpclient = HttpClients.createDefault();
1376
1377                        long duration = System.currentTimeMillis();
1378                        if (localContext == null) {
1379                                response = httpclient.execute(httpRequest);
1380                        } else {
1381                                response = httpclient.execute(httpRequest, localContext);
1382                        }
1383                        ByteArrayOutputStream body = new ByteArrayOutputStream();
1384                        response.getEntity().writeTo(body);
1385
1386                        HashMap<String, Object> aValue = new HashMap<String, Object>();
1387                        aValue.put("body", body.toByteArray());
1388                        aValue.put("status", response.getStatusLine().getStatusCode());
1389                        aValue.put("header", response.getAllHeaders());
1390                        aValue.put("duration", Long.toString(System.currentTimeMillis() - duration));
1391                        setVariableValue(responseName, aValue);
1392
1393                } else {
1394
1395                        Socket socket = null;
1396                        try {
1397                                String host = StringUtils.substringBefore(theUrl, ":");
1398                                int port = Integer.parseInt(StringUtils.defaultIfBlank(StringUtils.substringAfter(theUrl, ":"), "80"));
1399                                socket = new Socket(host, port);
1400                                OutputStream outputStream = socket.getOutputStream();
1401                                if (request instanceof byte[]) {
1402                                        IOUtils.write((byte[]) request, outputStream);
1403                                } else if (request instanceof String) {
1404                                        IOUtils.write(((String) request).getBytes(), outputStream);
1405                                }
1406                                InputStream inputStream = socket.getInputStream();
1407                                byte[] response = IOUtils.toByteArray(inputStream);
1408                                setVariableValue(responseName, response);
1409                        } finally {
1410                                if (socket != null) {
1411                                        socket.close();
1412                                }
1413                        }
1414
1415                }
1416
1417        }
1418
1419        @CommandExamples({ "<Table name='The table of variables'><var name='type:property' value='type:string'/></Table>",
1420                        "<Table name='The table of variables' file='type:path'><var name='type:property' value='type:string'/></Table>" })
1421        public void runCommandTable(final Node aCurrentVar) throws Throwable {
1422                AEManager manager = this.recipeListener.getManager();
1423                boolean notifyMe = this.recipeListener.isNotifyMe();
1424
1425                manager.inputDataTable(this, aCurrentVar, notifyMe);
1426        }
1427
1428        @SuppressWarnings({ "unchecked" })
1429        @CommandExamples({ "<Loop name='type:property' source='type:property' numbers='type:integer'> ... </Loop>",
1430                        "<Loop numbers='type:integer'> ... </Loop>",
1431                        "<Loop name='type:property' query='select * from ...' maxCount='type:integer'> ... </Loop>" })
1432        public void runCommandLoop(final Node aCurrentVar) throws Throwable {
1433                String theDescript = replaceProperties(aCurrentVar.getAttribute("description"));
1434                final String theNameAttribut = attr(aCurrentVar, "name");
1435                boolean mainLoopCommand = false;
1436
1437                final String theNumbers = StringUtils.trimToNull(replaceProperties(aCurrentVar.getAttribute("numbers")));
1438                if (theDescript == null) {
1439                        theDescript = "Loop";
1440                }
1441                String sourceAttribute = aCurrentVar.getAttribute("source");
1442                final String theAttribut = replaceProperties(sourceAttribute);
1443                Object loopArrayVar = null;
1444                if (theAttribut != null) {
1445                        loopArrayVar = getVariableValue(theAttribut);
1446                }
1447                List<Object> theLoopArray = new ArrayList<>();
1448                if (loopArrayVar != null) {
1449                        if (loopArrayVar instanceof String) {
1450                                theLoopArray.add((String) loopArrayVar);
1451                        } else if (loopArrayVar instanceof String[]) {
1452                                List<String> asList = Arrays.asList((String[]) loopArrayVar);
1453                                theLoopArray.addAll(asList);
1454                        } else if (loopArrayVar instanceof List) {
1455                                theLoopArray = (List<Object>) loopArrayVar;
1456                        } else if (loopArrayVar instanceof Map) {
1457                                Map map = (Map) loopArrayVar;
1458                                theLoopArray.addAll(map.keySet());
1459                        } else if (loopArrayVar instanceof JSONArray) {
1460                                JSONArray array = (JSONArray) loopArrayVar;
1461                                theLoopArray = array.toList();
1462                        } else {
1463                                return;
1464                        }
1465                }
1466                int theCount = 0;
1467                boolean infinityLoop = false;
1468                if (StringUtils.isNotBlank(theNumbers)) {
1469                        theCount = (int) Double.parseDouble(theNumbers);
1470                } else {
1471                        if (!theLoopArray.isEmpty()) {
1472                                theCount = theLoopArray.size();
1473                        } else {
1474                                infinityLoop = sourceAttribute == null;
1475                        }
1476                }
1477                if (isLoopActivated() == false) {
1478                        this.mainLoopCommand = true;
1479                        mainLoopCommand = true;
1480                }
1481
1482                if (infinityLoop) {
1483                        this.mainLoopCommand = false;
1484                }
1485                try {
1486                        for (int i = 0; (i < theCount || infinityLoop) && isStopped() == false; i++) {
1487                                if (isStopped()) {
1488                                        break;
1489                                }
1490                                if (this.breakFlag > 0) {
1491                                        break;
1492                                }
1493                                if ((mainLoopCommand && !infinityLoop) || theCount > 5) {
1494                                        this.recipeListener.setProgress(theDescript, theCount, i, false);
1495                                }
1496                                startCommandInformation(aCurrentVar);
1497                                if (theLoopArray != null && theLoopArray.size() > 0) {
1498                                        if (i < theLoopArray.size()) {
1499                                                setVariableValue(theNameAttribut, theLoopArray.get(i));
1500                                        }
1501                                } else {
1502                                        setVariableValue(theNameAttribut, ObjectUtils.toString(i));
1503                                }
1504                                taskNode(aCurrentVar, false);
1505                        }
1506                } finally {
1507                        if (this.breakFlag > 0) {
1508                                breakFlag--;
1509                        }
1510                        if (mainLoopCommand) {
1511                                this.mainLoopCommand = false;
1512                        }
1513                }
1514        }
1515
1516        @CommandExamples({ "<Append name='type:property' value='type:string' type='enum:all|unique'/>",
1517                        "<Append name='type:property'>...</Append>" })
1518        public synchronized void runCommandAppend(final Node aCurrentVar) throws Throwable {
1519                final String name = aCurrentVar.getAttribute("name");
1520                Object value = attr(aCurrentVar, "value");
1521                if (value == null) {
1522                        value = replaceProperties(aCurrentVar.getInnerText());
1523                }
1524
1525                String source = aCurrentVar.getAttribute("source");
1526                if (source != null) {
1527                        value = getVariableValue(replaceProperties(source));
1528                }
1529
1530                Object theOldValue = getVariableValue(name, false);
1531                if (theOldValue == null) {
1532                        return;
1533                }
1534
1535                theOldValue = checkForArray(theOldValue);
1536
1537                if (theOldValue instanceof String) {
1538                        final String theValue1 = (String) theOldValue;
1539                        value = theValue1 + value;
1540                        setVariableValue(name, value);
1541
1542                } else if (theOldValue instanceof Object[]) {
1543                        final boolean theUniqueTypeAttribut = "unique".equals(aCurrentVar.getAttribute("type"));
1544
1545                        final Object[] theValue1 = (Object[]) theOldValue;
1546                        Object[] theValue2 = null;
1547
1548                        if (theUniqueTypeAttribut) {
1549                                Set<Object> targetSet = new LinkedHashSet<>(Arrays.asList(theValue1));
1550                                if (value instanceof String) {
1551                                        targetSet.add((String) value);
1552                                } else if (value instanceof String[]) {
1553                                        for (String val : (String[]) value) {
1554                                                targetSet.add(val);
1555                                        }
1556                                }
1557
1558                                String[] array = new String[targetSet.size()];
1559                                Iterator<Object> iterator = targetSet.iterator();
1560                                int i = 0;
1561                                while (iterator.hasNext()) {
1562                                        String object = (String) iterator.next();
1563                                        array[i++] = object;
1564                                }
1565
1566                                theValue2 = array;
1567
1568                        } else {
1569                                List<Object> targetList = new ArrayList<>(Arrays.asList(theValue1));
1570                                if (value instanceof String[]) {
1571                                        for (String val : (String[]) value) {
1572                                                targetList.add(val);
1573                                        }
1574                                } else {
1575                                        targetList.add((String) value);
1576                                }
1577
1578                                String[] array = new String[targetList.size()];
1579                                int i = 0;
1580                                for (Object val : targetList) {
1581                                        if (val instanceof String) {
1582                                                array[i++] = (String) val;
1583                                        } else {
1584                                                array[i++] = ObjectUtils.toString(val);
1585                                        }
1586                                }
1587
1588                                theValue2 = array;
1589                        }
1590
1591                        applyResult(aCurrentVar, name, theValue2);
1592                }
1593        }
1594
1595        private Object checkForArray(Object theOldValue) {
1596                if (theOldValue instanceof List) {
1597                        theOldValue = ((List) theOldValue).toArray();
1598                }
1599                return theOldValue;
1600        }
1601
1602        @CommandExamples({ "<Rnd name='type:property' symbols='type:integer' type='enum:number|default'/>",
1603                        "<Rnd name='type:property' type='uuid'/>",
1604                        "<Rnd name='type:property' symbols='type:integer' startCode='type:integer' endCode='type:integer'/>",
1605                        "<Rnd name='type:property' source='type:property'/>" })
1606        public void runCommandRnd(final Node aCurrentVar) throws Throwable {
1607                final String theNameAttribut = replaceProperties(aCurrentVar.getAttribute("name"));
1608                String result = null;
1609                String type = replaceProperties(aCurrentVar.getAttribute("type"));
1610
1611                if ("uuid".equalsIgnoreCase(type)) {
1612                        result = UUID.randomUUID().toString();
1613
1614                } else {
1615                        final String source = replaceProperties(aCurrentVar.getAttribute("source"));
1616                        if (this.random == null) {
1617                                this.random = SecureRandom.getInstance("SHA1PRNG");
1618                        }
1619                        if (source != null) {
1620                                Object value = getVariableValue(source);
1621                                if (value instanceof String[]) {
1622                                        String[] strings = (String[]) value;
1623                                        int index = this.random.nextInt(strings.length);
1624                                        value = strings[index];
1625                                }
1626                                setVariableValue(theNameAttribut, value);
1627                                return;
1628                        }
1629
1630                        final String theSymbolsAttribut = replaceProperties(aCurrentVar.getAttribute("symbols"));
1631
1632                        final long theBegNum = Long.parseLong(theSymbolsAttribut);
1633                        int startCode = 0x41;
1634                        final String startCodeStr = replaceProperties(aCurrentVar.getAttribute("startCode"));
1635                        if (startCodeStr != null) {
1636                                if (startCodeStr.indexOf('x') > 0) {
1637                                        startCode = Integer.parseInt(startCodeStr.substring(2), 16);
1638                                } else {
1639                                        startCode = Integer.parseInt(startCodeStr);
1640                                }
1641                                type = "-number";
1642                        }
1643
1644                        int endCode = 0x05A;
1645                        final String endCodeStr = replaceProperties(aCurrentVar.getAttribute("endCode"));
1646                        if (endCodeStr != null) {
1647                                if (endCodeStr.indexOf('x') > 0) {
1648                                        endCode = Integer.parseInt(endCodeStr.substring(2), 16);
1649                                } else {
1650                                        endCode = Integer.parseInt(endCodeStr);
1651                                }
1652                                type = "-number";
1653                        }
1654
1655                        if ("number".equals(type) == false) {
1656                                final StringBuffer theBuffer = new StringBuffer();
1657                                for (int i = 0; i < theBegNum; i++) {
1658                                        theBuffer.append((char) (this.random.nextInt(endCode - startCode) + startCode));
1659                                }
1660                                result = theBuffer.toString();
1661                        } else {
1662                                final StringBuffer theBuffer = new StringBuffer();
1663                                for (int i = 0; i < theBegNum; i++) {
1664                                        theBuffer.append(this.random.nextInt(9));
1665                                }
1666                                result = theBuffer.toString();
1667                        }
1668                }
1669
1670                setVariableValue(theNameAttribut, result);
1671        }
1672
1673        @CommandExamples({ "<Inc name='type:property' increase='type:number'/>" })
1674        public void runCommandInc(final Node aCurrentVar) throws Throwable {
1675                String theValueAttribut = aCurrentVar.getAttribute("increase");
1676                theValueAttribut = replaceProperties(theValueAttribut);
1677                long theIncLong = 1;
1678                if (theValueAttribut != null) {
1679                        theIncLong = Long.parseLong(theValueAttribut);
1680                }
1681                final String theAttribut = aCurrentVar.getAttribute("name");
1682                Object theOldValue = getVariableValue(theAttribut);
1683
1684                if (!(theOldValue instanceof Object[])) {
1685                        theOldValue = ObjectUtils.toString(theOldValue, null);
1686                }
1687
1688                if (theOldValue instanceof String) {
1689                        final long theLongValue = Long.parseLong((String) theOldValue) + theIncLong;
1690                        setVariableValue(theAttribut, Long.toString(theLongValue));
1691                }
1692                if (theOldValue instanceof String[] && ((String[]) theOldValue).length > 0) {
1693                        final long theLongValue = Long.parseLong(((String[]) theOldValue)[0]) + theIncLong;
1694                        setVariableValue(theAttribut, Long.toString(theLongValue));
1695                } else {
1696                        new ClassCastException("Tag <Inc> enabled only one number argument.");
1697                }
1698        }
1699
1700        @CommandExamples({
1701                        "<If name='type:property' startsWith='type:string' endsWith='type:string' contains='type:string' equals='type:string' notEqual='type:string'> ... </If>",
1702                        "<If value='type:property' startsWith='type:string' endsWith='type:string' contains='type:string' equals='type:string' notEqual='type:string'> ... <Else> ... </Else></If>",
1703                        "<If expression='type:string'> ... </If>", "<If isNull='type:property'> ... </If>",
1704                        "<If isNotNull='type:property'> ... </If>" })
1705        public void runCommandIf(final Node action) throws Throwable {
1706                runIf(action);
1707        }
1708
1709        private boolean runIf(final Node action) throws CommandException, Throwable, ClassNotFoundException {
1710                String theExpressionAttribut = action.getAttribute("expression");
1711                String isNull = action.getAttribute("isNull");
1712                String isNotNull = action.getAttribute("isNotNull");
1713                String nameAttr = attr(action, "name");
1714
1715                String valueAttr = action.getAttribute("value");
1716
1717                String theValue1Attribut = action.getAttribute("value1");
1718                String theValue2Attribut = action.getAttribute("value2");
1719                String theConditionAttribut = action.getAttribute("condition");
1720
1721                boolean result = false;
1722                if (nameAttr != null || valueAttr != null) {
1723                        Object variableValue = getVariableValue(nameAttr);
1724                        String value;
1725
1726                        if (variableValue == null) {
1727                                value = replaceProperties(valueAttr);
1728                        } else {
1729                                if (variableValue instanceof String[]) {
1730                                        value = StringUtils.join((String[]) variableValue, "\n");
1731                                } else {
1732                                        value = ObjectUtils.toString(variableValue);
1733                                }
1734                        }
1735
1736                        String startsWith = attr(action, "startsWith");
1737                        String endsWith = attr(action, "endsWith");
1738                        String contains = attr(action, "contains");
1739                        String equals = attr(action, "equals");
1740                        String regex = attr(action, "regex");
1741                        String notEqual = attr(action, "notEqual");
1742
1743                        boolean ignoreCase = Boolean.valueOf(attr(action, "ignoreCase", "false"));
1744
1745                        boolean condition1 = startsWith == null || (ignoreCase ? StringUtils.startsWithIgnoreCase(value, startsWith)
1746                                        : StringUtils.startsWith(value, startsWith));
1747                        boolean condition2 = endsWith == null || (ignoreCase ? StringUtils.endsWithIgnoreCase(value, endsWith)
1748                                        : StringUtils.endsWith(value, endsWith));
1749                        boolean condition3 = contains == null || (ignoreCase ? StringUtils.containsIgnoreCase(value, contains)
1750                                        : StringUtils.contains(value, contains));
1751                        boolean condition4 = equals == null
1752                                        || (ignoreCase ? StringUtils.equalsIgnoreCase(value, equals) : StringUtils.equals(value, equals));
1753
1754                        boolean condition5 = true;
1755                        if (regex != null) {
1756                                final Pattern p = Pattern.compile(regex);
1757                                final Matcher m = p.matcher(value);
1758                                condition5 = m.find();
1759                        }
1760
1761                        boolean condition6 = notEqual == null || (ignoreCase ? !StringUtils.equalsIgnoreCase(value, notEqual)
1762                                        : !StringUtils.equals(value, notEqual));
1763
1764                        result = condition1 && condition2 && condition3 && condition4 && condition5 && condition6;
1765                        execIf(action, result);
1766                        return result;
1767                } else if (isNull != null) {
1768                        result = getVariableValue(replaceProperties(isNull)) == null;
1769                        execIf(action, result);
1770                        return result;
1771                } else if (isNotNull != null) {
1772                        Object variableValue = getVariableValue(replaceProperties(isNotNull));
1773                        result = variableValue != null;
1774                        execIf(action, result);
1775                        return result;
1776                } else if (theExpressionAttribut != null) {
1777                        theExpressionAttribut = replaceProperties(theExpressionAttribut);
1778                        JexlEngine jexl = new JexlBuilder().create();
1779
1780                        JexlExpression expr_c = jexl.createExpression(theExpressionAttribut);
1781                        JexlContext context = new MapContext();
1782
1783                        if (expr_c != null) {
1784                                Object val = expr_c.evaluate(context);
1785                                result = "true".equals(val.toString());
1786                        }
1787
1788                        execIf(action, result);
1789                        return result;
1790                } else if (theValue1Attribut != null || theValue2Attribut != null) {
1791                        if (theConditionAttribut == null) {
1792                                theConditionAttribut = "==";
1793                        }
1794                        theValue1Attribut = replaceProperties(theValue1Attribut);
1795                        theValue2Attribut = replaceProperties(theValue2Attribut);
1796
1797                        if ("==".equals(theConditionAttribut) && theValue1Attribut.equals(theValue2Attribut)) {
1798                                taskNode(action, false);
1799                                result = true;
1800                        } else if (("!=".equals(theConditionAttribut) || "unequal".equals(theConditionAttribut))
1801                                        && (theValue1Attribut.equals(theValue2Attribut) == false)) {
1802                                taskNode(action, false);
1803                                result = true;
1804                        } else if ("less".equals(theConditionAttribut)
1805                                        && (Long.parseLong(theValue1Attribut) < Long.parseLong(theValue2Attribut))) {
1806                                taskNode(action, false);
1807                                result = true;
1808                        } else if ("bigger".equals(theConditionAttribut)
1809                                        && (Long.parseLong(theValue1Attribut) > Long.parseLong(theValue2Attribut))) {
1810                                taskNode(action, false);
1811                                result = true;
1812                        } else {
1813                                for (Node command : action.getNodes("Else")) {
1814                                        taskNode(command, false);
1815                                }
1816                        }
1817                }
1818
1819                return result;
1820        }
1821
1822        private void execIf(final Node action, boolean result) throws CommandException, Throwable {
1823                if (result) {
1824                        taskNode(action, false);
1825                } else {
1826                        for (Node command : action.getNodes("Else")) {
1827                                if (command.getAttribute("name") == null && action.getAttribute("name") != null) {
1828                                        command.setAttribute("name", action.getAttribute("name"));
1829                                }
1830                                if (command.getAttribute("value") == null && action.getAttribute("value") != null) {
1831                                        command.setAttribute("value", action.getAttribute("value"));
1832                                }
1833                                if (command.getAttribute("name") != null || command.getAttribute("value") != null) {
1834                                        if (runIf(command)) {
1835                                                break;
1836                                        }
1837                                } else {
1838                                        taskNode(command);
1839                                }
1840                        }
1841                }
1842        }
1843
1844        @CommandDescription("")
1845        @CommandExamples({
1846                        "<While name='type:property' startsWith='type:string' endsWith='type:string' contains='type:string' equals='type:string' notEqual='type:string'> ... </While>",
1847                        "<While value1='type:string' value2='type:string' condition='unequal'> ... <Else value1='type:string' value2='type:string' condition='enum:unequal|equal'> ... </Else></While>",
1848                        "<While expression='type:string'> ... </While>", "<While isNull='type:property'> ... </While>",
1849                        "<While isNotNull='type:property'> ... </While>" })
1850        public void runCommandWhile(final Node aCurrentVar) throws Throwable {
1851                try {
1852                        for (; runIf(aCurrentVar);) {
1853                                if (this.breakFlag > 0) {
1854                                        break;
1855                                }
1856                        }
1857                } finally {
1858                        if (this.breakFlag > 0) {
1859                                breakFlag--;
1860                        }
1861                }
1862        }
1863
1864        @CommandExamples({ "<Pragma event='console-input' action='default'/>",
1865                        "<Pragma event='random-select' action='on'/>", "<Pragma event='log-window' action='single'/>" })
1866        public void runCommandPragma(final Node aCurrentVar) throws Throwable {
1867                final String theEventAttribut = aCurrentVar.getAttribute("event");
1868                final String theActionAttribut = aCurrentVar.getAttribute("action");
1869
1870                if ("console-input".equals(theEventAttribut)) {
1871                        boolean consoleDefaultInput = "default".equals(theActionAttribut);
1872                        getListener().getManager().setConsoleDefaultInput(consoleDefaultInput);
1873                } else if ("random-select".equals(theEventAttribut)) {
1874                        randomSelect = "on".equals(theActionAttribut);
1875                } else {
1876                        this.log.error("Pragma ignored. Event: " + theEventAttribut);
1877                }
1878        }
1879
1880        @SuppressWarnings("unchecked")
1881        @CommandDescription("The `name` attribute is a name of variable which provides Runnable or List<Runnable> value. "
1882                        + "To define this value, you should use the Runnable command tag.")
1883        @CommandExamples({
1884                        "<Threads name='type:property' threadLog='type:boolean' numbers='type:integer' multi='enum:true|false' />",
1885                        "<Threads numbers='type:integer' multi='enum:true|false' mode='enum:wait|nowait'><Out name='"
1886                                        + TaskProcessorThread.THREAD_ID + "'/></Threads>",
1887                        "<Threads multi='enum:true|false'><Out name='" + TaskProcessorThread.THREAD_ID + "'/></Threads>" })
1888        public void runCommandThreads(final Node aCurrentAction) throws InterruptedException, CommandException {
1889
1890                boolean threadLog = Boolean
1891                                .parseBoolean(StringUtils.defaultIfBlank(attr(aCurrentAction, "threadLog"), "false"));
1892                int numbers = Integer.parseInt(StringUtils.defaultIfBlank(attr(aCurrentAction, "numbers"), "1"));
1893                boolean multi = Boolean.parseBoolean(StringUtils.defaultIfBlank(attr(aCurrentAction, "multi"), "true"));
1894
1895                ThreadPoolExecutor threadPool;
1896                if (multi) {
1897                        if (numbers > 0) {
1898                                threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(numbers);
1899                        } else {
1900                                threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
1901                        }
1902                } else {
1903                        threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
1904                }
1905
1906                String callableListName = attr(aCurrentAction, "name");
1907                if (callableListName == null) {
1908                        final Iterator<?> theIterator = aCurrentAction.iterator();
1909
1910                        if (numbers == 0) {
1911                                while (theIterator.hasNext()) {
1912                                        final Node theNode = (Node) theIterator.next();
1913
1914                                        String theNameThreads = getTestName(theNode);
1915                                        if (theNameThreads == null) {
1916                                                theNameThreads = "Thread";
1917                                        }
1918
1919                                        ILogger theLog = this.log;
1920                                        if (threadLog) {
1921                                                theLog = this.recipeListener.createLog(theNameThreads, false);
1922                                        }
1923
1924                                        final TaskProcessorThread runnable = new TaskProcessorThread(this, theNode, getBaseDir(), theLog);
1925
1926                                        threadPool.execute(runnable);
1927
1928                                }
1929                        } else {
1930                                final Node theNode = (Node) aCurrentAction.clone();
1931                                theNode.setTag("Task");
1932                                theNode.removeAttribute(callableListName);
1933
1934                                for (int i = 0; i < numbers; i++) {
1935                                        String theNameThreads = getTestName(theNode);
1936                                        String logName = "Thread #" + i;
1937                                        if (theNameThreads != null) {
1938                                                logName = theNameThreads + " #" + i;
1939                                        }
1940
1941                                        ILogger theLog = this.log;
1942                                        if (threadLog) {
1943                                                theLog = this.recipeListener.createLog(logName, false);
1944                                        }
1945
1946                                        final TaskProcessorThread runnable = new TaskProcessorThread(this, theNode, getBaseDir(), theLog);
1947                                        runnable.getVaribleValue(TaskProcessorThread.THREAD_ID, String.valueOf(i));
1948
1949                                        threadPool.execute(runnable);
1950                                }
1951                        }
1952
1953                } else {
1954                        Object callableList = getVariableValue(callableListName);
1955                        if (callableList instanceof List) {
1956                                for (Runnable runnable : (List<Runnable>) callableList) {
1957                                        threadPool.execute(runnable);
1958                                }
1959                        } else if (callableList instanceof Runnable) {
1960                                for (int i = 0; i < numbers; i++) {
1961                                        if (callableList instanceof TaskProcessorThread) {
1962                                                threadPool.execute(((TaskProcessorThread) callableList).clone(i));
1963                                        } else {
1964                                                threadPool.execute((Runnable) callableList);
1965                                        }
1966                                }
1967                        } else {
1968                                throw new IllegalArgumentException(
1969                                                "Variable of type must be Runnable or List<Runnable>, actually: " + callableList);
1970                        }
1971                }
1972
1973                String description = replaceProperties(aCurrentAction.getAttribute("description"));
1974
1975                long completedTaskCount = 0;
1976                long size = threadPool.getTaskCount();
1977
1978                threadPoolList.add(threadPool);
1979                do {
1980                        completedTaskCount = threadPool.getCompletedTaskCount();
1981                        progress(size, completedTaskCount, description, false);
1982                        Thread.sleep(200);
1983
1984                } while (completedTaskCount < size);
1985                threadPoolList.remove(threadPool);
1986        }
1987
1988        @CommandExamples({ "Run recipe by name: <Task name='type:recipe' />",
1989                        "Run recipe by name in async mode: <Task name='type:recipe' mode='enum:sync|async' optional='enum:false|true'/>",
1990                        "Recipe command grouping: <Task> ... </Task>", "Run recipe by file path: <Task file='type:path' />" })
1991        public void runCommandTask(final Node action) throws Throwable {
1992
1993                final boolean optional = Boolean.valueOf(attr(action, "optional", "false"));
1994
1995                final String taskName = replaceProperties(action.getAttribute("name"));
1996                String taskFile = replaceProperties(action.getAttribute("file"));
1997
1998                if (taskFile == null) {
1999                        if (taskName != null) {
2000                                taskFile = getListener().getManager().getTestPath(taskName);
2001
2002                                if (taskFile == null) {
2003                                        if (optional) {
2004                                                return;
2005                                        } else {
2006                                                throw new Exception("Task with name: '" + taskName + "' not found.");
2007                                        }
2008                                }
2009                        }
2010                } else {
2011                        if (!(new File(taskFile)).exists()) {
2012                                if (optional) {
2013                                        return;
2014                                } else {
2015                                        throw new Exception("Task with name: '" + taskFile + "' not found.");
2016                                }
2017                        }
2018                }
2019
2020                final File theBaseDir = getBaseDir();
2021                try {
2022                        if (taskName != null) {
2023                                String mode = replaceProperties(action.getAttribute("mode"));
2024                                if ("async".equals(mode)) {
2025                                        getListener().getManager().runTask(taskName, true);
2026
2027                                } else {
2028                                        Properties taskParameters = MapUtils.toProperties(action.getAttributes());
2029                                        Set<Entry<Object, Object>> entrySet = taskParameters.entrySet();
2030
2031                                        String currentLogLevel = (String) this.variables.get(DEFAULT_LOG_LEVEL_NAME);
2032                                        String level = (String) taskParameters.get("level");
2033
2034                                        for (Entry<Object, Object> entry : entrySet) {
2035                                                String name = (String) entry.getKey();
2036                                                if (!"$ID".equals(name) && !"name".equals(name) && !"file".equals(name)
2037                                                                && !"level".equals(name)) {
2038                                                        String value = replaceProperties((String) entry.getValue());
2039                                                        this.variables.put(toUpperCaseName(name), value);
2040                                                }
2041                                        }
2042
2043                                        this.variables.put(DEFAULT_LOG_LEVEL_NAME, level);
2044                                        this.variables = processTesting(taskFile, this.variables, getBaseDir());
2045                                        this.variables.put(DEFAULT_LOG_LEVEL_NAME, currentLogLevel);
2046                                }
2047                        } else {
2048                                taskNode(action, false);
2049                        }
2050                } finally {
2051                        setBaseDir(theBaseDir);
2052                }
2053        }
2054
2055        @CommandExamples({ "<IterationRules name='type:property'><Out name='Iteration'/></IterationRules>",
2056                        "<IterationRules name='type:property' timeLimit='type:time' start='type:integer'> ... </IterationRules>",
2057                        "<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>"
2058                                        + "<IterationRules name='rules'><Out name='Iteration'/></IterationRules>", })
2059        public void runCommandIterationRules(final Node action) throws IOException, CommandException {
2060                final String name = (String) attrValue(action, "name");
2061                String theStartAttribut = action.getAttribute("start");
2062                long theLimit = attrTime(action, "timeLimit", "180000");
2063
2064                if (theStartAttribut == null) {
2065                        theStartAttribut = "0";
2066                }
2067
2068                final int theStart = Integer.parseInt(theStartAttribut);
2069                if (name == null) {
2070                        throw new CommandException("In tag <IterationRules> variable rules is not defined.", this);
2071                }
2072                try {
2073                        this.iteratorMode = true;
2074                        final TestIterator theTestIterator = new TestIterator(this, name);
2075                        final int theMax = theTestIterator.count();
2076                        progress(theMax, 0, "Iteration process", false);
2077                        debug("Iteration block. Total count: " + theMax);
2078                        final long theStertTime = System.currentTimeMillis();
2079                        for (int i = theStart; i < theMax; i++) {
2080                                if (isStopped()) {
2081                                        break;
2082                                }
2083                                setVariableValue("Iteration", Integer.toString(i + 1));
2084                                theTestIterator.nextIteration(this);
2085
2086                                taskNode(action, false);
2087
2088                                progress(theMax, i, "Iteration process", false);
2089                                if (i == 0) {
2090                                        long theTotalTime = ((System.currentTimeMillis() - theStertTime) * theMax) / 60000;
2091                                        debug("Total Iteration time: " + Long.toString(theTotalTime) + " min.");
2092                                        if (theTotalTime > theLimit) {
2093                                                int theConfirm = 0;
2094                                                if (this.recipeListener.getManager() != null) {
2095                                                        if (this.recipeListener.getManager().isConsoleDefaultInput(attr(action, "name"),
2096                                                                        attr(action, "description")) == false) {
2097                                                                if (theTotalTime < 60) {
2098                                                                        theConfirm = JOptionPane.showConfirmDialog(
2099                                                                                        JOptionPane.getRootFrame(), "Total Iteration time: "
2100                                                                                                        + Long.toString(theTotalTime) + " min. \nContinue?",
2101                                                                                        "Warning", JOptionPane.YES_NO_OPTION);
2102                                                                } else {
2103                                                                        theTotalTime /= 60;
2104                                                                        theConfirm = JOptionPane.showConfirmDialog(JOptionPane.getRootFrame(),
2105                                                                                        "Total Iteration time: " + Long.toString(theTotalTime) + " h. \nContinue?",
2106                                                                                        "Warning", JOptionPane.YES_NO_OPTION);
2107                                                                }
2108                                                        }
2109                                                        if (theConfirm != JOptionPane.YES_OPTION) {
2110                                                                break;
2111                                                        }
2112                                                }
2113                                        }
2114                                }
2115                        }
2116                } finally {
2117                        this.iteratorMode = false;
2118                }
2119        }
2120
2121        @CommandExamples({ "<Listdir path='type:path' name='type:property'/>",
2122                        "<Listdir name='type:property' path='type:path' filter='type:regex'/>",
2123                        "<Listdir name='type:property' path='type:path' filter='type:regex' dirFilter='type:regex' />",
2124                        "<Listdir path='type:path' name='type:property' />" })
2125        public void runCommandListdir(final Node aCurrentVar) throws Throwable {
2126                final String pathAttribut = replaceProperties(aCurrentVar.getAttribute("path"));
2127                final String theNameAttribut = replaceProperties(aCurrentVar.getAttribute("name"));
2128                final String fileFilterRegex = attr(aCurrentVar, "filter");
2129                final String dirFilterRegex = attr(aCurrentVar, "dirFilter");
2130                File path = getFile(pathAttribut);
2131
2132                if (isActiveInitFor(aCurrentVar, "console")) {
2133                        String pathStr = this.recipeListener.getManager().inputFile(theNameAttribut, null, path, log, this);
2134                        if (pathStr != null) {
2135                                path = new File(pathStr);
2136                        }
2137                }
2138
2139                if (path != null) {
2140                        debug("Listing of directory: " + path.getAbsolutePath());
2141
2142                        IOFileFilter fileFilter = TrueFileFilter.TRUE;
2143
2144                        if (fileFilterRegex != null) {
2145                                fileFilter = new RegexPathFilter(fileFilterRegex, dirFilterRegex);
2146                        }
2147
2148                        Collection<File> files = FileUtils.listFiles(path, fileFilter,
2149                                        dirFilterRegex != null ? TrueFileFilter.TRUE : null);
2150                        List<String> result = files.stream().map(f -> f.getAbsolutePath()).collect(Collectors.toList());
2151
2152                        if (!result.isEmpty()) {
2153                                Collections.sort(result);
2154                                setVariableValue(theNameAttribut, result);
2155                        }
2156                } else {
2157                        throw new TaskCancelingException();
2158                }
2159        }
2160
2161        @CommandExamples({ "<NetworkInterfaces name='type:property' />",
2162                        "<NetworkInterfaces name='type:property' host='type:string' filterFor='type:string'/>" })
2163        public void runCommandNetworkInterfaces(final Node aCurrentAction) throws SocketException, UnknownHostException {
2164                final String name = replaceProperties(aCurrentAction.getAttribute("name"));
2165                String host = replaceProperties(aCurrentAction.getAttribute("host"));
2166
2167                if (host == null) {
2168                        Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
2169                        Set<String> hostIps = new HashSet<>();
2170                        while (networkInterfaces.hasMoreElements()) {
2171                                NetworkInterface nextElement = networkInterfaces.nextElement();
2172                                Enumeration<InetAddress> inetAddresses = nextElement.getInetAddresses();
2173                                while (inetAddresses.hasMoreElements()) {
2174                                        InetAddress nextElement2 = inetAddresses.nextElement();
2175                                        if (nextElement2 instanceof Inet4Address) {
2176                                                Inet4Address address = (Inet4Address) nextElement2;
2177                                                String hostAddress = address.getHostAddress();
2178                                                hostIps.add(hostAddress);
2179                                        }
2180                                }
2181                        }
2182                        setVariableValue(name, new ArrayList<>(hostIps));
2183                } else {
2184                        try {
2185                                URL url = new URL(host);
2186                                host = url.getHost();
2187                        } catch (Exception e) {
2188
2189                        }
2190
2191                        InetAddress address1 = InetAddress.getByName(host);
2192                        setVariableValue(name, address1.getHostAddress());
2193                }
2194        }
2195
2196        @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. "
2197                        + "The `dir` attribute specifies the current directory for the command being executed. This attribute can use `~` as the directory for the current recipe.")
2198        @CommandExamples({ "<Command os='type:regex'>...</Command>",
2199                        "<Command os='type:regex' env='type:property'>...</Command>",
2200                        "<Command name='type:property' os='type:regex' exitValue='type:property' cmd='type:string' noCommandLog='type:boolean' dir='type:path'/>",
2201                        "<Command name='type:property' os='type:regex' exitValue='type:property' cmd='type:string' noCommandLog='type:boolean' dir='type:path'/>...</Command>" })
2202        public void runCommandCommand(final Node action) throws Throwable {
2203
2204                String command = attr(action, "cmd");
2205                final String name = attr(action, "name");
2206                final String dir = attr(action, "dir");
2207                final String os = attr(action, "os");
2208
2209                String osName = SystemUtils.OS_NAME;
2210
2211                if (os == null || Pattern.compile(os).matcher(osName).matches()) {
2212                        if (command == null) {
2213                                final Node[] theNodes = action.getTextNodes();
2214                                if (theNodes.length > 0) {
2215                                        command = replaceProperties(theNodes[0].getText());
2216                                }
2217                        }
2218
2219                        Object env = attrValue(action, "env");
2220                        if (env != null) {
2221                                if (!(env instanceof Map)) {
2222                                        throw new CommandException("'env' attribute shuld be Map", this);
2223                                }
2224                        }
2225                        @SuppressWarnings("unchecked")
2226                        Map<String, String> environment = (Map<String, String>) env;
2227                        runSystemCommand(command, name, action, dir, environment);
2228                }
2229        }
2230
2231        private void runSystemCommand(final String command, final String theNameAttribut, final Node aCurrentNode,
2232                        String dir, Map<? extends String, ? extends String> env) throws Throwable {
2233                final String prefix = "start ";
2234                if (command.startsWith(prefix) == false) {
2235
2236                        String regExp = "\"(\\\"|[^\"])*?\"|[^ ]+";
2237                        Pattern pattern = Pattern.compile(regExp, Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
2238                        Matcher matcher = pattern.matcher(command);
2239                        List<String> commandTokens = new ArrayList<String>();
2240
2241                        if (SystemUtils.IS_OS_WINDOWS) {
2242                                commandTokens.add("cmd.exe");
2243                                commandTokens.add("/c");
2244                        }
2245
2246                        while (matcher.find()) {
2247                                String trim = matcher.group().trim();
2248                                if (StringUtils.isNotBlank(trim)) {
2249                                        commandTokens.add(trim);
2250                                }
2251                        }
2252                        String[] parsedCommand = commandTokens.toArray(new String[] {});
2253
2254                        ProcessBuilder builder = new ProcessBuilder(parsedCommand);
2255
2256                        if (env != null) {
2257                                Map<String, String> environment = builder.environment();
2258                                environment.putAll(env);
2259                        }
2260
2261                        if (dir != null) {
2262                                File directory;
2263                                if (dir.startsWith("~/")) {
2264                                        dir = dir.substring(1);
2265                                        String recipeFile = this.recipeListener.getManager().getTestPath(testName);
2266                                        if (recipeFile != null) {
2267                                                directory = new File(new File(recipeFile).getParent(), dir);
2268                                                builder.directory(directory);
2269                                        }
2270                                } else {
2271                                        directory = new File(dir);
2272                                        builder.directory(directory);
2273                                }
2274                        }
2275
2276                        builder.redirectErrorStream(true);
2277                        Process process = builder.start();
2278
2279                        final String stdin = replaceProperties(aCurrentNode.getAttribute("stdin"));
2280                        if (stdin != null) {
2281                                process.getOutputStream().write(stdin.getBytes());
2282                                process.getOutputStream().close();
2283                        }
2284
2285                        processes.add(process);
2286
2287                        final BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getInputStream()));
2288                        boolean noCommandLog = Boolean.valueOf(attr(aCurrentNode, "noCommandLog"));
2289                        if (!noCommandLog) {
2290                                debug("Command: " + command);
2291                        } else {
2292                                debug("Command: ****** **** *****");
2293                        }
2294
2295                        try {
2296                                final BufferedReader theOutputStream = new BufferedReader(
2297                                                new InputStreamReader(process.getInputStream()));
2298                                String theLine;
2299
2300                                boolean line_output = aCurrentNode.size() == 0;
2301                                final StringBuffer theBuffer = new StringBuffer();
2302                                while ((theLine = theOutputStream.readLine()) != null && !isStoppedTest()) {
2303                                        if (breakFlag > 0) {
2304                                                break;
2305                                        }
2306
2307                                        if (line_output) {
2308                                                theBuffer.append(theLine);
2309                                                theBuffer.append('\n');
2310                                        } else {
2311                                                setVariableValue(theNameAttribut, theLine);
2312                                                taskNode(aCurrentNode, false);
2313                                        }
2314                                }
2315
2316                                if (this.breakFlag > 0) {
2317                                        breakFlag--;
2318                                }
2319
2320                                String exitValueName = attr(aCurrentNode, "exitValue");
2321                                if (exitValueName != null) {
2322                                        int exitValue = 0;
2323                                        try {
2324                                                exitValue = process.exitValue();
2325                                                if (exitValue > 0) {
2326                                                        String error = IOUtils.toString(errorStream);
2327                                                        if (StringUtils.isNotBlank(error)) {
2328                                                                throw new RuntimeException(error);
2329                                                        }
2330                                                }
2331                                        } catch (IllegalThreadStateException e) {
2332                                                //
2333                                        }
2334                                        setVariableValue(exitValueName, Integer.toString(exitValue));
2335                                }
2336
2337                                if (line_output) {
2338                                        if (theNameAttribut == null) {
2339                                                debug("System output:" + theBuffer.toString());
2340                                        } else {
2341                                                setVariableValue(theNameAttribut, theBuffer.toString());
2342                                        }
2343                                }
2344                        } finally {
2345                                processes.remove(process);
2346                                process.destroyForcibly();
2347                                try {
2348                                        if (!process.waitFor(5, TimeUnit.SECONDS)) {
2349                                                process.destroyForcibly();
2350                                        }
2351                                } catch (InterruptedException e) {
2352                                        Thread.currentThread().interrupt();
2353                                        process.destroyForcibly();
2354                                }
2355                        }
2356
2357                } else {
2358                        final Thread thread = new SystemCommandStart(command.substring(prefix.length()), this.log);
2359                        thread.start();
2360                }
2361        }
2362
2363        @CommandExamples({ "<ArraySize name = 'type:property' array = 'type:property'/>" })
2364        public void runCommandArraySize(final Node aCurrentVar) throws Throwable {
2365                final String theNameAttribut = replaceProperties(aCurrentVar.getAttribute("name"));
2366                final String theArrayNameAttribut = replaceProperties(aCurrentVar.getAttribute("array"));
2367                final Object theValue = getVariableValue(theArrayNameAttribut);
2368
2369                int theResult = 0;
2370                if (theValue instanceof String[]) {
2371                        theResult = ((String[]) theValue).length;
2372                } else if (theValue instanceof List) {
2373                        theResult = ((List<String>) theValue).size();
2374                } else if (theValue instanceof byte[]) {
2375                        theResult = ((byte[]) theValue).length;
2376                } else if (theValue instanceof String && StringUtils.isNotBlank((String) theValue)) {
2377                        theResult = 1;
2378                } else {
2379                        theResult = 0;
2380                }
2381
2382                setVariableValue(theNameAttribut, String.valueOf(theResult));
2383        }
2384
2385        @CommandExamples({ "Get string or array length: <Size name = 'type:property' source = 'type:property'/>" })
2386        public void runCommandSize(final Node aCurrentVar) throws Throwable {
2387                final String theNameAttribut = replaceProperties(aCurrentVar.getAttribute("name"));
2388                final String theArrayNameAttribut = replaceProperties(aCurrentVar.getAttribute("source"));
2389                final Object theValue = getVariableValue(theArrayNameAttribut);
2390
2391                int theResult = 0;
2392                if (theValue instanceof String[]) {
2393                        String[] theValue2 = (String[]) theValue;
2394                        for (int i = 0; i < theValue2.length; i++) {
2395                                theResult += theValue2[i].length();
2396                        }
2397                } else if (theValue instanceof byte[]) {
2398                        theResult = ((byte[]) theValue).length;
2399                } else if (theValue instanceof Collection) {
2400                        theResult = ((Collection) theValue).size();
2401                } else if (theValue instanceof String && StringUtils.isNotBlank((String) theValue)) {
2402                        theResult = ((String) theValue).length();
2403                } else {
2404                        theResult = 0;
2405                }
2406
2407                setVariableValue(theNameAttribut, String.valueOf(theResult));
2408        }
2409
2410        @CommandExamples({ "<Time name = 'type:property' action = 'enum:start|continue|pause|stop'/>",
2411                        "<Time name = 'type:property' action = 'format' format='mm:ss:SSS'/>",
2412                        "<Time name = 'type:property' action = 'duration' >...code whose execution time is to be measured...</Time>" })
2413        public void runCommandTime(final Node command) throws Throwable {
2414                final String theNameAttribut = replaceProperties(command.getAttribute("name"));
2415                final String format = replaceProperties(command.getAttribute("format"));
2416                final String theTimeCaption = "Time";
2417                String action = replaceProperties(command.getAttribute("action"));
2418
2419                if ("duration".equals(action)) {
2420                        long start = System.currentTimeMillis();
2421
2422                        taskNode(command, false);
2423
2424                        long stop = System.currentTimeMillis();
2425                        final String[] variableValue = new String[] { theTimeCaption, String.valueOf(start), String.valueOf(stop),
2426                                        "" };
2427
2428                        String result = formatTime(format, variableValue);
2429                        setVariableValue(theNameAttribut, result);
2430                        return;
2431                }
2432
2433                if ("start".equals(action)) {
2434                        final String[] theTime = new String[] { theTimeCaption, String.valueOf(System.currentTimeMillis()), "",
2435                                        "" };
2436                        setVariableValue(theNameAttribut, theTime);
2437                        return;
2438                }
2439
2440                if ("continue".equals(action)) {
2441                        String[] theTime = (String[]) getVariableValue(theNameAttribut);
2442                        if (theTime == null) {
2443                                theTime = new String[] { theTimeCaption, String.valueOf(System.currentTimeMillis()), "",
2444                                                String.valueOf(System.currentTimeMillis()) };
2445                        }
2446
2447                        if (theTime == null || theTime[3] == null || theTime[3].length() == 0) {
2448                                throw new Exception("Timer is not paused.");
2449                        }
2450
2451                        if (theTimeCaption.equals(theTime[0])) {
2452                                final long theStart = Long.parseLong(theTime[1]);
2453                                final long thePaused = Long.parseLong(theTime[3]);
2454                                theTime[1] = String.valueOf(theStart - (System.currentTimeMillis() - thePaused));
2455                                theTime[3] = "";
2456                                setVariableValue(theNameAttribut, theTime);
2457                        } else {
2458                                throw new Exception("Incorrect type.");
2459                        }
2460                        return;
2461                }
2462
2463                if ("pause".equals(action)) {
2464                        final String[] theTime = (String[]) getVariableValue(theNameAttribut);
2465
2466                        if (theTime[3] != null && theTime[3].length() > 0) {
2467                                throw new Exception("Timer is paused.");
2468                        }
2469                        if (theTimeCaption.equals(theTime[0])) {
2470                                theTime[3] = String.valueOf(System.currentTimeMillis());
2471                                setVariableValue(theNameAttribut, theTime);
2472                        } else {
2473                                throw new Exception("Incorrect type.");
2474                        }
2475                        return;
2476                }
2477
2478                if ("stop".equals(action)) {
2479                        final String[] theTime = (String[]) getVariableValue(theNameAttribut);
2480
2481                        final long theStart = Long.parseLong(theTime[1]);
2482
2483                        if (theTime[3] != null && theTime[3].length() > 0) {
2484                                final long thePaused = Long.parseLong(theTime[3]);
2485                                theTime[1] = String.valueOf(theStart + (System.currentTimeMillis() - thePaused));
2486                                theTime[3] = "";
2487                        }
2488
2489                        if (theTimeCaption.equals(theTime[0])) {
2490                                theTime[2] = String.valueOf(System.currentTimeMillis());
2491                                setVariableValue(theNameAttribut, theTime);
2492                        } else {
2493                                throw new Exception("Incorrect type.");
2494                        }
2495                        return;
2496                }
2497
2498                if ("format".equals(action)) {
2499                        Object variableValue = getVariableValue(theNameAttribut);
2500                        String theResult = formatTime(format, variableValue);
2501                        setVariableValue(theNameAttribut, theResult);
2502                        return;
2503                }
2504        }
2505
2506        private String formatTime(final String format, Object variableValue) throws Exception {
2507                final String[] theTime;
2508                if (variableValue instanceof String[]) {
2509                        theTime = (String[]) variableValue;
2510                } else {
2511                        theTime = new String[] { "", "0", (String) variableValue };
2512                }
2513
2514                if (ArrayUtils.getLength(theTime) < 2) {
2515                        throw new Exception("Timer is not defined.");
2516                }
2517
2518                if (ArrayUtils.getLength(theTime) < 3) {
2519                        throw new Exception("Timer is not stoped.");
2520                }
2521
2522                String theResult = null;
2523                if (format == null) {
2524                        theResult = String.valueOf(Long.parseLong(theTime[2]) - Long.parseLong(theTime[1]));
2525                } else {
2526                        final DateFormat dateFormat = new SimpleDateFormat(format);
2527                        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
2528                        long date = Long.parseLong(theTime[2]) - Long.parseLong(theTime[1]);
2529                        theResult = dateFormat.format(new Date(date));
2530                }
2531                return theResult;
2532        }
2533
2534        @CommandDescription("The ArrayElement command is used to get an element from an array by element ID (elementId).")
2535        @CommandExamples({ "<ArrayElement name='type:property' array='type:property' elementId='type:number'/>" })
2536        public void runCommandArrayElement(final Node aCurrentVar) throws Throwable {
2537                final String name = replaceProperties(aCurrentVar.getAttribute("name"));
2538                final String arrayName = replaceProperties(aCurrentVar.getAttribute("array"));
2539                if (arrayName == null) {
2540                        throw new IllegalArgumentException("'array' attribute required.");
2541                }
2542
2543                final String theElementAttribut = replaceProperties(aCurrentVar.getAttribute("elementId"));
2544                if (theElementAttribut == null) {
2545                        throw new IllegalArgumentException("'elementId' attribute required.");
2546                }
2547
2548                final String type = replaceProperties(aCurrentVar.getAttribute("type"));
2549                Object theValue = getVariableValue(arrayName);
2550
2551                final int theElementId = Integer.parseInt(theElementAttribut);
2552
2553                theValue = convert(theValue, type);
2554
2555                Object theResult = null;
2556                if (theValue instanceof JSONArray) {
2557                        JSONArray jsonArray = (JSONArray) theValue;
2558                        theResult = jsonArray.get(theElementId);
2559                } else if (theValue instanceof String[]) {
2560                        theResult = ((String[]) theValue)[theElementId];
2561                } else if (theValue instanceof List) {
2562                        theResult = ((List) theValue).get(theElementId);
2563                } else if (theValue instanceof String) {
2564                        if ("string".equalsIgnoreCase(type)) {
2565                                theResult = new String(new char[] { ((String) theValue).charAt(theElementId) });
2566                        } else if (theElementId == 0) {
2567                                theResult = theValue;
2568                        }
2569                } else if (theElementId == 0) {
2570                        theResult = theValue;
2571                }
2572
2573                setVariableValue(name, theResult);
2574        }
2575
2576        @CommandDescription("The Calculate command is implemented in the Java Expression Language (JEXL), "
2577                        + "for more information see: https://commons.apache.org/proper/commons-jexl. "
2578                        + "The value of the expression will be stored in the variable with the name specified in the `name` attribute.")
2579        @CommandExamples({ "<Calculate name='type:property' expressions='type:string'/>",
2580                        "<Calculate name='type:property'>...</Calculate>" })
2581        public void runCommandCalculate(final Node command) throws Throwable {
2582                final String name = replaceProperties(command.getAttribute("name"));
2583                String expressions = replaceProperties(command.getAttribute("expressions"));
2584                if (expressions == null) {
2585                        expressions = replaceProperties(command.getInnerText());
2586                }
2587
2588                Map<String, Object> namespaces = new HashMap<>();
2589                namespaces.put("Math", Math.class);
2590                JexlEngine jexl = new JexlBuilder().namespaces(namespaces).create();
2591
2592                JexlExpression expr_c = jexl.createExpression(expressions);
2593                JexlContext context = new MapContext();
2594
2595                Map<String, String> attributes = command.getAttributes();
2596                for (Map.Entry<String, String> entry : attributes.entrySet()) {
2597                        String key = entry.getKey();
2598                        String val = entry.getValue();
2599
2600                        Object variableValue = getVariableValue(val);
2601                        if (variableValue instanceof String) {
2602                                try {
2603                                        variableValue = Double.parseDouble((String) variableValue);
2604                                } catch (NumberFormatException e) {
2605                                        // DO NOTHING
2606                                }
2607                        }
2608
2609                        context.set(key, variableValue);
2610                }
2611
2612                if (expr_c != null) {
2613                        Object result = expr_c.evaluate(context);
2614
2615                        if (result != null) {
2616                                setVariableValue(name, result);
2617                        } else {
2618                                setVariableValue(name, null);
2619                        }
2620                }
2621        }
2622
2623        @CommandExamples({
2624                        "<Parse name='type:property' source='type:property' type='enum:array|json|csv|fixed-length-line'/>",
2625                        "<Parse name='type:property' source='type:property' type='fixed-length-line' length='type:integer'/>" })
2626        public void runCommandParse(final Node command) throws Throwable {
2627                Object source = attrValue(command, "source");
2628                final String theNameAttribut = replaceProperties(command.getAttribute("name"));
2629                final String type = replaceProperties(command.getAttribute("type"));
2630
2631                if ("json".equalsIgnoreCase(type)) {
2632                        String theValue = ObjectUtils.toString(source);
2633                        if (StringUtils.isNotBlank(theValue)) {
2634                                Object obj = new JSONObject(theValue);
2635                                if (obj instanceof JSONObject) {
2636                                        JSONObject result = new JSONObject(theValue);
2637                                        setVariableValue(theNameAttribut, result);
2638                                } else if (obj instanceof JSONArray) {
2639                                        JSONArray result = new JSONArray(theValue);
2640                                        String[] array = new String[result.length()];
2641                                        for (int i = 0; i < result.length(); i++) {
2642                                                array[i] = ObjectUtils.toString(result.get(i));
2643                                        }
2644                                        setVariableValue(theNameAttribut, array);
2645                                }
2646                        } else {
2647                                setVariableValue(theNameAttribut, theValue);
2648                        }
2649
2650                } else if ("array".equalsIgnoreCase(type)) {
2651                        String theValue = ObjectUtils.toString(source);
2652
2653                        String[] array = StringUtils.split(theValue, "\r\n");
2654                        setVariableValue(theNameAttribut, array);
2655                } else if ("csv".equalsIgnoreCase(type)) {
2656                        String theValue = ObjectUtils.toString(source);
2657
2658                        CSVReader reader = new CSVReader(new StringReader(theValue));
2659                        List<String[]> r = reader.readAll();
2660
2661                        setVariableValue(theNameAttribut, r);
2662                } else if ("fixed-length-line".equalsIgnoreCase(type)) {
2663                        if (source instanceof String) {
2664                                String input = (String) source;
2665                                int lineLength = Integer.parseInt(attr(command, "length"));
2666                                List<Object> result = IntStream.range(0, (input.length() + lineLength - 1) / lineLength)
2667                                                .mapToObj(i -> input.substring(i * lineLength, Math.min((i + 1) * lineLength, input.length())))
2668                                                .collect(Collectors.toList());
2669                                setVariableValue(theNameAttribut, result);
2670                        }
2671                }
2672        }
2673
2674        @CommandExamples({ "<FindObject name='type:property' source='type:property' withValue='type:string'/>" })
2675        public void runCommandFindObject(final Node aCurrentNode) throws Throwable {
2676                Object json = getVariableValue(replaceProperties(aCurrentNode.getAttribute("source")));
2677                String withValue = replaceProperties(aCurrentNode.getAttribute("withValue"));
2678
2679                if (json instanceof String) {
2680                        String jsonStr = (String) json;
2681                        if (StringUtils.startsWith(jsonStr, "{")) {
2682                                json = new JSONObject(jsonStr);
2683                        } else if (StringUtils.startsWith(jsonStr, "[")) {
2684                                json = new JSONArray(jsonStr);
2685                        }
2686                } else if (json instanceof String[]) {
2687                        String[] jsonStrArray = (String[]) json;
2688                        JSONArray array = new JSONArray();
2689                        for (int i = 0; i < jsonStrArray.length; i++) {
2690                                String jsonStr = jsonStrArray[i];
2691                                array.put(new JSONObject(jsonStr));
2692                        }
2693                        json = array;
2694                }
2695
2696                Object value = find(json, withValue);
2697
2698                final String name = replaceProperties(aCurrentNode.getAttribute("name"));
2699                applyResult(aCurrentNode, name, value);
2700        }
2701
2702        private JSONObject find(Object obj, String withValue) throws JSONException {
2703                if (obj instanceof JSONObject) {
2704                        JSONObject json = (JSONObject) obj;
2705
2706                        JSONArray names = json.names();
2707                        if (names != null) {
2708                                for (int i = 0; i < names.length(); i++) {
2709                                        String name = names.getString(i);
2710                                        Object object = json.get(name);
2711                                        if (object instanceof String) {
2712                                                if (ObjectUtils.equals(object, withValue)) {
2713                                                        return json;
2714                                                }
2715                                        } else {
2716                                                JSONObject find = find(object, withValue);
2717                                                if (find != null) {
2718                                                        return find;
2719                                                }
2720                                        }
2721                                }
2722                        }
2723                } else if (obj instanceof JSONArray) {
2724                        JSONArray array = (JSONArray) obj;
2725
2726                        for (int j = 0; j < array.length(); j++) {
2727                                Object item = array.get(j);
2728
2729                                JSONObject find = find(item, withValue);
2730                                if (find != null) {
2731                                        return find;
2732                                }
2733                        }
2734                }
2735                return null;
2736        }
2737
2738        @CommandDescription("`Var` command is used to define or update a variable.\r\n"
2739                        + "Attribute `init` defines how variable will be set. This attribute is optional; if it is not defined,\r\n"
2740                        + "the variable will be initialized by `value` attribute or inner data. It can be set to the following values:\r\n"
2741                        + "- `default` is used to initialize the variable's value if it is not defined,\r\n"
2742                        + "- `console` is used for input data by the user,\r\n"
2743                        + "- `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"
2744                        + "- `file` is used for loadind variables from property file.\r\n"
2745                        + "Attribute `type` defines a type of variable. It is used to define the variable type and special rules for the input dialog:\r\n"
2746                        + "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"
2747                        + "- `array` is used for create the value of the array type;\r\n"
2748                        + "- `map` is used for create the value of the map type;\r\n"
2749                        + "- `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"
2750                        + "- `password` the user dialog text field will be masked;\r\n"
2751                        + "- `path` a dialog for a dir opening will be shown; \r\n"
2752                        + "- `text` type is used only for large text values, and the value is not saved in the preferences.")
2753        @CommandExamples({
2754                        "<Var name='type:property' init='enum:console|mandatory|default' type='enum:text|password|path'/>",
2755                        "<Var name='type:property' type='enum:array|number|map|json|string' />",
2756                        "<Var name='type:property' source='type:property' start='type:string' end='type:string'/>",
2757                        "<Var name='type:property'><item>...</item></Var>",
2758                        "<Var name='type:property' type='map'><item key='type:string'>...</item></Var>",
2759                        "<Var name='type:property' type='enum:array|number' file='type:path'/>",
2760                        "<Var init='file' file='type:path'/>", "<Var name='type:property' source='type:property' />" })
2761        public void runCommandVar(final Node action) throws Throwable {
2762                final String name = attr(action, "name");
2763                String description = attr(action, "description");
2764                String theInit = attr(action, "init");
2765
2766                Object theOldValue = getVariableValue(name);
2767                if ("default".equals(theInit) && theOldValue != null) {
2768                        if (theOldValue instanceof String) {
2769                                if (StringUtils.isNotBlank((String) theOldValue)) {
2770                                        return;
2771                                }
2772                        } else if (theOldValue instanceof String[] && ((String[]) theOldValue).length > 0) {
2773                                return;
2774                        }
2775                }
2776
2777                String file = attr(action, "file");
2778                if ("file".equals(theInit)) {
2779                        File propertiesFile = getFile(file);
2780                        loadProperties(propertiesFile, this.variables, true);
2781                        return;
2782                }
2783
2784                String source = replaceProperties(action.getAttribute("source"), true);
2785                Object theValue = getVariableValue(name);
2786                if (source != null) {
2787                        String sourceVarName = replaceProperties(source);
2788                        theValue = getVariableValue(sourceVarName);
2789                        theOldValue = theValue;
2790                        setVariableValue(name, theValue);
2791                }
2792
2793                String value = action.getAttribute("value");
2794                if (value != null) {
2795                        theValue = replaceProperties(value);
2796                }
2797
2798                // Item setting
2799                String type = StringUtils.defaultIfEmpty(action.getAttribute("type"), "");
2800                if (action.size() > 0) {
2801                        if (Node.TEXT_TEAG_NAME.equals(action.getNode(0).getTag())) {
2802                                final Node theTextNode = action.getNode(0);
2803                                theValue = replaceProperties(theTextNode.getText());
2804
2805                        } else {
2806                                switch (type) {
2807                                case "map":
2808                                        Node[] nodes = action.getNodes("item");
2809                                        theValue = new LinkedHashMap<String, String>();
2810                                        for (Node node : nodes) {
2811                                                ((Map) theValue).put(node.getAttribute("key"), replaceProperties(node.getInnerText()));
2812                                        }
2813                                        break;
2814
2815                                default:
2816                                        List<Object> theItemArray = new ArrayList<>();
2817                                        for (int i = 0; i < action.size(); i++) {
2818                                                final Node theCurrentAction = (Node) action.get(i);
2819                                                Node[] theItemNodes = theCurrentAction.getTextNodes();
2820                                                if (theItemNodes != null && theItemNodes.length == 1) {
2821                                                        final String replaceProperties = replaceProperties(theItemNodes[0].getText(), true);
2822                                                        theItemArray.add(replaceProperties);
2823                                                } else {
2824                                                        if (theCurrentAction.size() == 1) {
2825                                                                Node node = theCurrentAction.get(0);
2826                                                                EasyUtils.removeAllAttributes(node, Node.TAG_ID);
2827                                                                theItemArray.add(replaceProperties(node.getXMLText(), true));
2828                                                        } else {
2829                                                                theItemArray = null;
2830                                                        }
2831                                                        break;
2832                                                }
2833                                        }
2834                                        theValue = theItemArray;
2835                                }
2836                        }
2837                }
2838
2839                boolean contains = StringUtils.contains(theInit, "mandatory");
2840                boolean isConsoleInput = StringUtils.contains(theInit, "console");
2841                boolean mandatory = StringUtils.contains(theInit, "mandatory");
2842                if (mandatory) {
2843                        if (!isEmpty(theOldValue)) {
2844                                setVariableValue(name, theValue);
2845                        } else {
2846                                isConsoleInput = true;
2847                        }
2848                }
2849
2850                final boolean theIntegerType = "number".equals(type);
2851                if (theIntegerType) {
2852                        String string = ObjectUtils.toString(theValue);
2853                        if (StringUtils.isNotBlank(string) && !NumberUtils.isNumber(string)) {
2854                                setVariableValue(name, null);
2855                                throw new NumberFormatException(string);
2856                        }
2857                }
2858
2859                final boolean theArrayType = "array".equals(type);
2860                if (name != null && !(theValue == null && theArrayType == false)) {
2861                        if (theArrayType) {
2862                                char separatorChar = ',';
2863                                if (theValue instanceof String) {
2864                                        String[] split = StringUtils.split((String) theValue, separatorChar);
2865                                        theValue = split != null ? Arrays.asList(split) : null;
2866                                }
2867                        }
2868                        if (theArrayType && theValue == null) {
2869                                theValue = new String[0];
2870                        }
2871
2872                        if (theArrayType && file != null) {
2873                                ArrayList<String> buffer = new ArrayList<String>();
2874
2875                                if (theValue instanceof String[]) {
2876                                        for (String string : (String[]) theValue) {
2877                                                buffer.add(string);
2878                                        }
2879                                } else if (theValue instanceof Collection) {
2880                                        buffer = new ArrayList<String>((Collection<String>) theValue);
2881                                }
2882
2883                                try (InputStream inputStream = AEUtils.getInputStream(file, getBaseDir())) {
2884                                        String encoding = replaceProperties(action.getAttribute("charset"));
2885                                        if (encoding == null) {
2886                                                encoding = "UTF-8";
2887                                        }
2888                                        final BufferedReader theFileReader = new BufferedReader(
2889                                                        new InputStreamReader(inputStream, encoding));
2890                                        String readLine;
2891                                        while ((readLine = theFileReader.readLine()) != null) {
2892                                                buffer.add(readLine);
2893                                        }
2894                                }
2895                                theValue = buffer;
2896                        }
2897
2898                        theValue = convert(theValue, type);
2899                        setVariableValue(name, theValue);
2900                }
2901
2902                if (isConsoleInput) {
2903
2904                        if (theOldValue instanceof List && CollectionUtils.size(theOldValue) == 1) {
2905                                theOldValue = ((List) theOldValue).get(0);
2906                        }
2907
2908                        if ((action.size() == 0 || !action.getInnerText().isEmpty())
2909                                        && (isEmpty(theOldValue) || theOldValue instanceof String)) {
2910                                Object theInitialSelectionValue = null;
2911                                final Object o = getVariableValue(name);
2912                                if (o instanceof String) {
2913                                        theInitialSelectionValue = o;
2914                                }
2915                                if (o instanceof String[] && ((String[]) o).length > 0) {
2916                                        if (this.random == null) {
2917                                                this.random = SecureRandom.getInstance("SHA1PRNG");
2918                                        }
2919                                        theInitialSelectionValue = ((String[]) o)[this.random.nextInt(((String[]) o).length)];
2920                                }
2921
2922                                String defaultValue = AEWorkspace.getInstance().getDefaultUserConfiguration(".inputValue." + name,
2923                                                (String) theInitialSelectionValue);
2924                                if (this.recipeListener.getManager().isConsoleDefaultInput(name, description)
2925                                                && !(mandatory && defaultValue == null)) {
2926
2927                                        if ((o instanceof String[] && ArrayUtils.contains((String[]) o, defaultValue)) || o == null) {
2928                                                theInitialSelectionValue = defaultValue;
2929                                        }
2930
2931                                        setVariableValue(name, theInitialSelectionValue);
2932                                } else {
2933                                        boolean notifyMe = this.recipeListener.isNotifyMe();
2934                                        Object inputValue;
2935
2936                                        inputValue = this.recipeListener.getManager().inputValue(name, description, defaultValue, log, type,
2937                                                        notifyMe, this);
2938
2939                                        setVariableValue(name, inputValue);
2940                                }
2941
2942                        } else {
2943                                List<Object> possibleValues = null;
2944                                if (theOldValue instanceof List) {
2945                                        @SuppressWarnings("unchecked")
2946                                        final List<Object> theStringArray = (List<Object>) theOldValue;
2947                                        possibleValues = theStringArray;
2948                                } else if (theOldValue instanceof String[]) {
2949                                        possibleValues = new ArrayList<>();
2950                                        String[] array = (String[]) theOldValue;
2951                                        for (String item : array) {
2952                                                possibleValues.add(item);
2953                                        }
2954                                } else {
2955                                        possibleValues = new ArrayList<>();
2956                                        for (int i = 0; i < action.size(); i++) {
2957                                                final Node theCurrentAction = (Node) action.get(i);
2958                                                possibleValues.add(theCurrentAction.getInnerText());
2959                                        }
2960                                }
2961
2962                                if (this.recipeListener.getManager().isConsoleDefaultInput(name, null)) {
2963                                        String theInitialSelectionValue = randomSelect(possibleValues);
2964                                        if (!randomSelect) {
2965                                                theInitialSelectionValue = AEWorkspace.getInstance()
2966                                                                .getDefaultUserConfiguration(".choiceValue." + name, theInitialSelectionValue);
2967
2968                                        }
2969                                        setVariableValue(name, theInitialSelectionValue);
2970                                } else {
2971                                        boolean notifyMe = this.recipeListener.isNotifyMe();
2972                                        final Object theValueOut = this.recipeListener.getManager().choiceValue(name, description,
2973                                                        possibleValues.toArray(), log, notifyMe, this);
2974                                        setVariableValue(name, theValueOut);
2975                                }
2976                        }
2977                }
2978
2979                theValue = getVariableValue(name);
2980
2981                String start = replaceProperties(action.getAttribute("start"));
2982                String end = replaceProperties(action.getAttribute("end"));
2983
2984                if (StringUtils.isNotEmpty(start)) {
2985                        if (theValue instanceof byte[]) {
2986                                theValue = new String((byte[]) theValue);
2987                        }
2988                        theValue = StringUtils.substringAfter(ObjectUtils.toString(theValue), start);
2989                        if (StringUtils.isBlank((String) theValue)) {
2990                                theValue = null;
2991                        }
2992                }
2993                if (StringUtils.isNotEmpty(end)) {
2994                        theValue = StringUtils.substringBefore((String) theValue, end);
2995                        if (StringUtils.isBlank((String) theValue)) {
2996                                theValue = null;
2997                        }
2998                }
2999
3000                applyResult(action, name, theValue);
3001
3002                if (contains && theValue == null) {
3003                        stop();
3004                }
3005        }
3006
3007        private Object convert(Object theValue, String type) throws JSONException {
3008                if ("json".equals(type)) {
3009                        Object parse = theValue;
3010                        if (theValue instanceof String[]) {
3011                                String[] array = (String[]) theValue;
3012                                List<String> asList = Arrays.asList(array);
3013                                theValue = new JSONArray(asList);
3014                        } else if (theValue instanceof String) {
3015                                if (StringUtils.startsWith((String) theValue, "{")) {
3016                                        parse = new JSONObject(theValue.toString());
3017                                } else if (StringUtils.startsWith((String) theValue, "[")) {
3018                                        parse = new JSONArray(theValue.toString());
3019                                }
3020                        }
3021                        theValue = parse;
3022                } else if ("string".equals(type) && theValue instanceof String[]) {
3023                        theValue = StringUtils.join((String[]) theValue);
3024                }
3025
3026                return theValue;
3027        }
3028
3029        private String randomSelect(List possibleValues) throws NoSuchAlgorithmException {
3030                final String theInitialSelectionValue;
3031                if (this.random == null) {
3032                        this.random = SecureRandom.getInstance("SHA1PRNG");
3033                }
3034                theInitialSelectionValue = (String) possibleValues.get(this.random.nextInt(possibleValues.size()));
3035                return theInitialSelectionValue;
3036        }
3037
3038        @CommandExamples({ "<Note name='type:string' connection='type:string'>...</Note>" })
3039        public void runCommandNote(final Node aCurrentAction) throws Throwable {
3040        }
3041
3042        @CommandExamples({ "<!-- ... -->" })
3043        public void runCommandComment(final Node aCurrentAction) throws Throwable {
3044        }
3045
3046        @CommandExamples({ "<Extern class='type:processor'>\n...\n</Extern>" })
3047        public void runCommandExtern(final Node command) throws Throwable {
3048                final Processor newInstance = makeProcessor(command);
3049                boolean success = false;
3050                try {
3051                        this.externProcessor = newInstance;
3052                        externProcessor.setTestName(getTestName());
3053                        newInstance.init(this, command);
3054                        boolean rootRecipe = isRootRecipe();
3055                        newInstance.setRootContext(rootRecipe);
3056                        newInstance.stackTask.addAll(this.stackTask);
3057                        newInstance.taskNode(command, false);
3058
3059                        this.variables = newInstance.variables;
3060                        success = true;
3061                } finally {
3062                        externProcessor.complete(success);
3063                        externProcessor = null;
3064                }
3065        }
3066
3067        @CommandDescription("The Confirm command is used to confirm the user's decision to continue a "
3068                        + "task or execute internal code. The name attribute is used as the dialog box title "
3069                        + "and as a variable name if confirmation is required without displaying a dialog box "
3070                        + "to the user. If the variable exists, confirmation is automatically applied if the "
3071                        + "value is true, or rejected if the value is not true.")
3072        @CommandExamples({ "<Confirm message='type:string' name='type:string'/>",
3073                        "<Confirm message='type:string' name='type:string'>...</Confirm>" })
3074        public void runCommandConfirm(final Node aCurrentAction) throws Throwable {
3075                final String message = attr(aCurrentAction, "message");
3076                final String name = attr(aCurrentAction, "name");
3077                final Object nameVar = getVariableValue(name);
3078
3079                boolean confirmed = false;
3080                if (nameVar != null && nameVar instanceof String) {
3081                        confirmed = BooleanUtils.toBoolean((String) nameVar);
3082                } else {
3083                        AEManager manager = this.recipeListener.getManager();
3084                        confirmed = manager.confirmation(name, message, this, this.recipeListener.isNotifyMe());
3085                }
3086
3087                if (aCurrentAction.size() > 0) {
3088                        if (confirmed) {
3089                                taskNode(aCurrentAction, false);
3090                        }
3091                } else {
3092                        if (!confirmed) {
3093                                stop();
3094                        }
3095                }
3096        }
3097
3098        @CommandDescription("The WhileRun command is used to notify the user that a process has started. "
3099                        + "The user can close the WhileRun dialog box without affecting the process's execution. "
3100                        + "The dialog box closes after the code within the WhileRun tag completes execution. "
3101                        + "The name attribute is used as the dialog box title and variable name. "
3102                        + "If the dialog box should not be opened, a variable with a name corresponding to "
3103                        + "the dialog box name should be assigned a value other than `true`.")
3104        @CommandExamples({ "<WhileRun name='type:string' message='type:string'>...</WhileRun>" })
3105        public void runCommandWhileRun(final Node aCurrentAction) throws Throwable {
3106                final String message = attr(aCurrentAction, "message");
3107                final String name = attr(aCurrentAction, "name");
3108                final Object nameVar = getVariableValue(name);
3109
3110                if (!(nameVar instanceof String) || BooleanUtils.toBoolean((String) nameVar)) {
3111                        AEManager manager = this.recipeListener.getManager();
3112                        MessageHandler handler = manager.message(this, name, message, this.recipeListener.isNotifyMe());
3113                        taskNode(aCurrentAction, false);
3114                        handler.close();
3115                }
3116        };
3117
3118        @CommandDescription("The `Server` command initializes and starts a server socket listener, "
3119                        + "operating as a blocking command that prevents the execution of subsequent commands until it completes.")
3120        @CommandExamples({ "<Server port='type:integer' request='type:property' response='type:property' > ... </Server>",
3121                        "<Server port='type:integer' numbers='type:integer' request='type:property' response='type:property' > ... </Server>" })
3122        public void runCommandServer(final Node aCurrentAction) throws Throwable {
3123
3124                String encoding = replaceProperties(aCurrentAction.getAttribute("charset"));
3125                if (encoding == null) {
3126                        encoding = "UTF-8";
3127                }
3128
3129                final int thePort = Integer.parseInt(replaceProperties(aCurrentAction.getAttribute("port")));
3130                int maxNumber = 0;
3131
3132                final String theMaxNumbers = replaceProperties(aCurrentAction.getAttribute("numbers"));
3133                if (theMaxNumbers != null) {
3134                        maxNumber = Integer.parseInt(theMaxNumbers);
3135                }
3136                final String request = attr(aCurrentAction, "request");
3137                final String response = attr(aCurrentAction, "response");
3138
3139                ServerAction server = new ServerAction(this, aCurrentAction, thePort, request, response, encoding, maxNumber);
3140                server.perform();
3141        }
3142
3143        @CommandExamples({ "<Restore/>", "<Restore name='type:property'> ... </Restore>",
3144                        "<Restore except='type:property'> ... </Restore>" })
3145        public void runCommandRestore(final Node command) throws Throwable {
3146                String name = attr(command, "name");
3147                String except = attr(command, "except");
3148
3149                if (CollectionUtils.isEmpty(command)) {
3150                        final Map<String, Object> systemVariables = getListener().getManager().getSystemVariables();
3151                        this.variables.clear();
3152                        this.variables.putAll(systemVariables);
3153                        debug("Task variables has been restored.");
3154                } else if (name != null) {
3155                        Object savedVariable = getVariableValue(name);
3156                        taskNode(command, false);
3157                        setVariableValue(name, savedVariable);
3158
3159                } else if (except != null) {
3160                        Map<String, Object> savedVariables = this.variables;
3161                        this.variables = new HashMap<String, Object>(this.variables);
3162
3163                        taskNode(command, false);
3164
3165                        Object object = getVariableValue(except);
3166                        this.variables.clear();
3167                        this.variables = savedVariables;
3168                        setVariableValue(except, object);
3169                }
3170
3171        }
3172
3173        @CommandDescription("Use the Finally command tag before protected code to ensure it is executed "
3174                        + "before exiting the current parent tag, as usual Finally is defined as a first executable command.\n"
3175                        + "Example: `<Recipe><Finally> ...finally code... </Finally> ...some code... </Recipe>`")
3176        @CommandExamples({ "<Finally> ... </Finally>" })
3177        public void runCommandFinally(final Node aCurrentAction) throws Throwable {
3178        }
3179
3180        @CommandExamples({ "<Break/>" })
3181        public void runCommandBreak(final Node aCurrentAction) throws Throwable {
3182        }
3183
3184        @CommandExamples({ "<Stop/>", "<Stop ifNull='type:property'/>" })
3185        public void runCommandStop(final Node aCurrentAction) throws Throwable {
3186        }
3187
3188        private boolean isEmpty(Object theOldValue) {
3189                boolean result = false;
3190                if (theOldValue == null) {
3191                        result = true;
3192                } else if (theOldValue instanceof String[] && ((String[]) theOldValue).length == 0) {
3193                        result = true;
3194                } else if (theOldValue instanceof Map && ((Map) theOldValue).size() == 0) {
3195                        result = true;
3196                }
3197                return result;
3198        }
3199
3200        public void setVariableValue(String name, final Object value) {
3201                if (name != null) {
3202                        super.setVariableValue(name, value);
3203                        if (this.recipeListener != null && name.startsWith("!") == false) {
3204                                this.recipeListener.changeVariable(name, value);
3205                        }
3206                }
3207        }
3208
3209}