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