1 /**
2  * UGENE - Integrated Bioinformatics Tools.
3  * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4  * http://ugene.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21 
22 #include "WorkflowUtils.h"
23 
24 #include <QListWidgetItem>
25 
26 #include <U2Core/AnnotationTableObject.h>
27 #include <U2Core/AppContext.h>
28 #include <U2Core/BaseDocumentFormats.h>
29 #include <U2Core/CredentialsAsker.h>
30 #include <U2Core/DocumentUtils.h>
31 #include <U2Core/ExternalToolRegistry.h>
32 #include <U2Core/ExternalToolRunTask.h>
33 #include <U2Core/FileAndDirectoryUtils.h>
34 #include <U2Core/Folder.h>
35 #include <U2Core/GObject.h>
36 #include <U2Core/L10n.h>
37 #include <U2Core/MultipleSequenceAlignmentObject.h>
38 #include <U2Core/PasswordStorage.h>
39 #include <U2Core/Settings.h>
40 #include <U2Core/StringAdapter.h>
41 #include <U2Core/U2OpStatusUtils.h>
42 #include <U2Core/U2SafePoints.h>
43 
44 #include <U2Lang/BaseSlots.h>
45 #include <U2Lang/BaseTypes.h>
46 #include <U2Lang/CoreLibConstants.h>
47 #include <U2Lang/HRSchemaSerializer.h>
48 #include <U2Lang/IntegralBus.h>
49 #include <U2Lang/IntegralBusModel.h>
50 #include <U2Lang/IntegralBusType.h>
51 #include <U2Lang/SharedDbUrlUtils.h>
52 #include <U2Lang/URLAttribute.h>
53 #include <U2Lang/WorkflowIOTasks.h>
54 #include <U2Lang/WorkflowSettings.h>
55 
56 namespace U2 {
57 /*****************************
58  * WorkflowUtils
59  *****************************/
60 const QStringList WorkflowUtils::WD_FILE_EXTENSIONS = initExtensions();
61 const QString WorkflowUtils::WD_XML_FORMAT_EXTENSION("uws");
62 const QString WorkflowUtils::HREF_PARAM_ID("param");
63 
initExtensions()64 QStringList WorkflowUtils::initExtensions() {
65     QStringList exts;
66     exts << "uwl";
67     return exts;
68 }
69 
getRichDoc(const Descriptor & d)70 QString WorkflowUtils::getRichDoc(const Descriptor &d) {
71     QString result = QString();
72     if (d.getDisplayName().isEmpty()) {
73         if (!d.getDocumentation().isEmpty()) {
74             result = QString("%1").arg(d.getDocumentation());
75         }
76     } else {
77         if (d.getDocumentation().isEmpty()) {
78             result = QString("<b>%1</b>").arg(d.getDisplayName());
79         } else {
80             result = QString("<b>%1</b>: %2").arg(d.getDisplayName()).arg(d.getDocumentation());
81         }
82     }
83     result.replace("\n", "<br>");
84     return result;
85 }
86 
getDropUrl(QList<DocumentFormat * > & fs,const QMimeData * md)87 QString WorkflowUtils::getDropUrl(QList<DocumentFormat *> &fs, const QMimeData *md) {
88     QString url;
89     const GObjectMimeData *gomd = qobject_cast<const GObjectMimeData *>(md);
90     const DocumentMimeData *domd = qobject_cast<const DocumentMimeData *>(md);
91     if (gomd) {
92         GObject *obj = gomd->objPtr.data();
93         if (obj) {
94             fs << obj->getDocument()->getDocumentFormat();
95             url = obj->getDocument()->getURLString();
96         }
97     } else if (domd) {
98         Document *doc = domd->objPtr.data();
99         if (doc) {
100             fs << doc->getDocumentFormat();
101             url = doc->getURLString();
102         }
103     } else if (md->hasUrls()) {
104         QList<QUrl> urls = md->urls();
105         if (urls.size() == 1) {
106             url = urls.first().toLocalFile();
107             QList<FormatDetectionResult> formats = DocumentUtils::detectFormat(url);
108             foreach (const FormatDetectionResult &di, formats) {
109                 fs << di.format;
110             }
111         }
112     }
113     return url;
114 }
115 
setQObjectProperties(QObject & o,const QVariantMap & params)116 void WorkflowUtils::setQObjectProperties(QObject &o, const QVariantMap &params) {
117     QMapIterator<QString, QVariant> i(params);
118     while (i.hasNext()) {
119         i.next();
120         // log.debug("set param " + i.key() + "="+i.value().toString());
121         o.setProperty(i.key().toLatin1(), i.value());
122     }
123 }
124 
expandToUrls(const QString & s)125 QStringList WorkflowUtils::expandToUrls(const QString &s) {
126     QStringList urls = s.split(";");
127     QStringList result;
128     QRegExp wcard("[*?\\[\\]]");
129     foreach (QString url, urls) {
130         int idx = url.indexOf(wcard);
131         if (idx >= 0) {
132             int dirIdx = url.lastIndexOf('/', idx);
133             QDir dir;
134             if (dirIdx >= 0) {
135                 dir = QDir(url.left(dirIdx));
136                 url = url.right(url.length() - dirIdx - 1);
137             }
138 
139             foreach (QFileInfo fi, dir.entryInfoList((QStringList() << url), QDir::Files | QDir::NoSymLinks)) {
140                 result << fi.absoluteFilePath();
141             }
142         } else {
143             // if (QFile::exists(url))
144             {
145                 result << url;
146             }
147         }
148     }
149     return result;
150 }
151 
152 namespace {
153 
validateParameters(const Schema & schema,NotificationsList & infoList)154 bool validateParameters(const Schema &schema, NotificationsList &infoList) {
155     bool good = true;
156     foreach (Actor *a, schema.getProcesses()) {
157         const int notificationCountBefore = infoList.size();
158         good = a->validate(infoList) && good;
159         for (int i = notificationCountBefore; i < infoList.size(); ++i) {
160             infoList[i].actorId = a->getId();
161         }
162     }
163     return good;
164 }
165 
validateExternalTools(Actor * actor,NotificationsList & infoList)166 bool validateExternalTools(Actor *actor, NotificationsList &infoList) {
167     bool isValid = true;
168     StrStrMap tools = actor->getProto()->getExternalTools();
169     foreach (const QString &toolId, tools.keys()) {
170         Attribute *attr = actor->getParameter(tools[toolId]);
171         ExternalTool *tool = AppContext::getExternalToolRegistry()->getById(toolId);
172         if (tool == nullptr) {
173             isValid = false;
174             infoList << WorkflowNotification(WorkflowUtils::externalToolIsAbsentError(toolId),
175                                              actor->getId(),
176                                              WorkflowNotification::U2_ERROR);
177             continue;
178         }
179 
180         bool isToolFromAttribute = attr != nullptr && !attr->isDefaultValue();
181         isValid = isToolFromAttribute ? !attr->isEmpty() : !tool->getPath().isEmpty();
182         if (!isValid) {
183             infoList << WorkflowNotification(WorkflowUtils::externalToolError(tool->getName()),
184                                              actor->getId(),
185                                              WorkflowNotification::U2_ERROR);
186         } else if (!isToolFromAttribute && !tool->isValid()) {
187             if (tool->isCustom()) {
188                 infoList << WorkflowNotification(WorkflowUtils::customExternalToolInvalidError(tool->getName(), actor->getLabel()),
189                                                  actor->getProto()->getId(),
190                                                  WorkflowNotification::U2_ERROR);
191                 isValid = false;
192             } else {
193                 infoList << WorkflowNotification(WorkflowUtils::externalToolInvalidError(tool->getName()),
194                                                  actor->getProto()->getId(),
195                                                  WorkflowNotification::U2_WARNING);
196             }
197         }
198     }
199     return isValid;
200 }
201 
validatePorts(Actor * a,NotificationsList & infoList)202 bool validatePorts(Actor *a, NotificationsList &infoList) {
203     bool good = true;
204     foreach (Port *p, a->getEnabledPorts()) {
205         NotificationsList notificationList;
206         good = p->validate(notificationList) && good;
207         if (!notificationList.isEmpty()) {
208             foreach (WorkflowNotification notification, notificationList) {
209                 WorkflowNotification item;
210                 item.message = notification.message;
211                 item.port = p->getId();
212                 item.actorId = a->getId();
213                 item.type = notification.type;
214                 infoList << item;
215             }
216         }
217     }
218     return good;
219 }
220 
graphDepthFirstSearch(Actor * vertex,QList<Actor * > & visitedVertices)221 bool graphDepthFirstSearch(Actor *vertex, QList<Actor *> &visitedVertices) {
222     visitedVertices.append(vertex);
223     const QList<Port *> outputPorts = vertex->getOutputPorts();
224     QList<Actor *> receivingVertices;
225     foreach (Port *outputPort, outputPorts) {
226         foreach (Port *receivingPort, outputPort->getLinks().keys()) {
227             receivingVertices.append(receivingPort->owner());
228         }
229     }
230     foreach (Actor *receivingVertex, receivingVertices) {
231         if (visitedVertices.contains(receivingVertex)) {
232             return false;
233         } else {
234             return graphDepthFirstSearch(receivingVertex, visitedVertices);
235         }
236     }
237     return true;
238 }
239 
240 // the returning values signals about cycles existence in the scheme
hasSchemeCycles(const Schema & scheme)241 bool hasSchemeCycles(const Schema &scheme) {
242     foreach (Actor *vertex, scheme.getProcesses()) {
243         QList<Actor *> visitedVertices;
244         if (!graphDepthFirstSearch(vertex, visitedVertices)) {
245             return false;
246         }
247     }
248     return true;
249 }
250 
validateScript(Actor * a,NotificationsList & infoList)251 bool validateScript(Actor *a, NotificationsList &infoList) {
252     SAFE_POINT(nullptr != a, "NULL actor", false);
253     SAFE_POINT(nullptr != a->getScript(), "NULL script", false);
254     const QString scriptText = a->getScript()->getScriptText();
255     if (scriptText.simplified().isEmpty()) {
256         infoList << WorkflowNotification(QObject::tr("Empty script text"), a->getId());
257         return false;
258     }
259     QScopedPointer<WorkflowScriptEngine> engine(new WorkflowScriptEngine(nullptr));
260     QScriptSyntaxCheckResult syntaxResult = engine->checkSyntax(scriptText);
261 
262     if (syntaxResult.state() != QScriptSyntaxCheckResult::Valid) {
263         WorkflowNotification notification;
264         notification.message = QObject::tr("Script syntax check failed! Line: %1, error: %2")
265                                    .arg(syntaxResult.errorLineNumber())
266                                    .arg(syntaxResult.errorMessage());
267         notification.actorId = a->getId();
268         notification.type = WorkflowNotification::U2_ERROR;
269         infoList << notification;
270         return false;
271     }
272     return true;
273 }
274 
275 }  // namespace
276 
validate(const Schema & schema,NotificationsList & notificationList)277 bool WorkflowUtils::validate(const Schema &schema, NotificationsList &notificationList) {
278     bool isValid = validateOutputDir(WorkflowSettings::getWorkflowOutputDirectory(), notificationList);
279     foreach (Actor *actor, schema.getProcesses()) {
280         isValid = validatePorts(actor, notificationList) && isValid;
281         if (actor->getProto()->isScriptFlagSet()) {
282             isValid = validateScript(actor, notificationList) && isValid;
283         }
284         isValid = validateExternalTools(actor, notificationList) && isValid;
285     }
286     if (!hasSchemeCycles(schema)) {
287         isValid = false;
288         notificationList << WorkflowNotification(tr("The schema contains loops"));
289     }
290 
291     isValid = validateParameters(schema, notificationList) && isValid;
292     return isValid;
293 }
294 
295 // used in GUI schema validating
validate(const Schema & schema,QList<QListWidgetItem * > & infoList)296 bool WorkflowUtils::validate(const Schema &schema, QList<QListWidgetItem *> &infoList) {
297     NotificationsList notifications;
298     bool good = validate(schema, notifications);
299 
300     foreach (const WorkflowNotification &notification, notifications) {
301         QListWidgetItem *item = nullptr;
302         Actor *a = nullptr;
303         if (notification.actorId.isEmpty()) {
304             item = new QListWidgetItem(notification.message);
305         } else {
306             a = schema.actorById(notification.actorId);
307             item = new QListWidgetItem(QString("%1: %2").arg(a->getLabel()).arg(notification.message));
308         }
309         if (notification.type == WorkflowNotification::U2_ERROR) {
310             item->setIcon(QIcon(":U2Lang/images/error.png"));
311         } else if (notification.type == WorkflowNotification::U2_WARNING) {
312             item->setIcon(QIcon(":U2Lang/images/warning.png"));
313         } else if (a != nullptr) {
314             item->setIcon(a->getProto()->getIcon());
315         }
316 
317         item->setData(ACTOR_ID_REF, notification.actorId);
318         item->setData(PORT_REF, notification.port);
319         item->setData(TEXT_REF, notification.message);
320         item->setData(TYPE_REF, notification.type);
321 
322         infoList << item;
323     }
324 
325     return good;
326 }
327 
328 // used in cmdline schema validating
validate(const Workflow::Schema & schema,QStringList & errs)329 bool WorkflowUtils::validate(const Workflow::Schema &schema, QStringList &errs) {
330     NotificationsList notifications;
331     bool good = validate(schema, notifications);
332 
333     foreach (const WorkflowNotification &notification, notifications) {
334         QString res = QString();
335         Actor *a = schema.actorById(notification.actorId);
336         if (notification.actorId.isEmpty() || a == nullptr) {
337             res = notification.message;
338         } else {
339             QString message = notification.message;
340             res = QString("%1: %2").arg(a->getLabel()).arg(message);
341 
342             QString option;
343             foreach (const Attribute *attr, a->getAttributes()) {
344                 if (message.contains(attr->getDisplayName())) {
345                     option = a->getParamAliases().value(attr->getId());
346                 }
347             }
348             if (!option.isEmpty()) {
349                 res += tr(" (use --%1 option)").arg(option);
350             }
351         }
352         errs << res;
353     }
354 
355     return good;
356 }
357 
findMatchingTypes(DataTypePtr set,DataTypePtr elementDataType)358 QList<Descriptor> WorkflowUtils::findMatchingTypes(DataTypePtr set, DataTypePtr elementDataType) {
359     QList<Descriptor> result;
360     foreach (const Descriptor &d, set->getAllDescriptors()) {
361         if (set->getDatatypeByDescriptor(d) == elementDataType) {
362             result.append(d);
363         }
364     }
365     return result;
366 }
367 
candidatesAsStringList(const QList<Descriptor> & descList)368 QStringList WorkflowUtils::candidatesAsStringList(const QList<Descriptor> &descList) {
369     QStringList res;
370     foreach (const Descriptor &desc, descList) {
371         res << desc.getId();
372     }
373     return res;
374 }
375 
findMatchingTypesAsStringList(DataTypePtr set,DataTypePtr elementDatatype)376 QStringList WorkflowUtils::findMatchingTypesAsStringList(DataTypePtr set, DataTypePtr elementDatatype) {
377     QList<Descriptor> descList = findMatchingTypes(set, elementDatatype);
378     return candidatesAsStringList(descList);
379 }
380 
newEmptyValuesDesc()381 Descriptor newEmptyValuesDesc() {
382     return Descriptor("", QObject::tr("<empty>"), QObject::tr("Default value"));
383 }
384 
findMatchingCandidates(DataTypePtr from,DataTypePtr elementDatatype)385 QList<Descriptor> WorkflowUtils::findMatchingCandidates(DataTypePtr from, DataTypePtr elementDatatype) {
386     QList<Descriptor> candidates = findMatchingTypes(from, elementDatatype);
387     if (elementDatatype->isList()) {
388         candidates += findMatchingTypes(from, elementDatatype->getDatatypeByDescriptor());
389     } else {
390         candidates.append(newEmptyValuesDesc());
391     }
392     return candidates;
393 }
394 
findMatchingCandidates(DataTypePtr from,DataTypePtr to,const Descriptor & key)395 QList<Descriptor> WorkflowUtils::findMatchingCandidates(DataTypePtr from, DataTypePtr to, const Descriptor &key) {
396     return findMatchingCandidates(from, to->getDatatypeByDescriptor(key));
397 }
398 
getCurrentMatchingDescriptor(const QList<Descriptor> & candidates,DataTypePtr to,const Descriptor & key,const StrStrMap & bindings)399 Descriptor WorkflowUtils::getCurrentMatchingDescriptor(const QList<Descriptor> &candidates, DataTypePtr to, const Descriptor &key, const StrStrMap &bindings) {
400     DataTypePtr elementDatatype = to->getDatatypeByDescriptor(key);
401     if (elementDatatype->isList()) {
402         QString currentVal = bindings.value(key.getId());
403         if (!currentVal.isEmpty()) {
404             return Descriptor(currentVal, tr("<List of values>"), tr("List of values"));
405         } else {
406             return newEmptyValuesDesc();
407         }
408     } else {
409         int idx = bindings.contains(key.getId()) ? candidates.indexOf(bindings.value(key.getId())) : 0;
410         return idx >= 0 ? candidates.at(idx) : newEmptyValuesDesc();
411     }
412 }
413 
getToDatatypeForBusport(IntegralBusPort * p)414 DataTypePtr WorkflowUtils::getToDatatypeForBusport(IntegralBusPort *p) {
415     assert(p != nullptr);
416     DataTypePtr to;
417     DataTypePtr t = to = p->getType();
418     if (!t->isMap()) {
419         QMap<Descriptor, DataTypePtr> map;
420         map.insert(*p, t);
421         to = new MapDataType(Descriptor(), map);
422         // IntegralBusType* bt = new IntegralBusType(Descriptor(), QMap<Descriptor, DataTypePtr>());
423         // bt->addOutput(t, p);
424     }
425     return to;
426 }
427 
getFromDatatypeForBusport(IntegralBusPort * p,DataTypePtr to)428 DataTypePtr WorkflowUtils::getFromDatatypeForBusport(IntegralBusPort *p, DataTypePtr to) {
429     assert(p != nullptr);
430 
431     DataTypePtr from;
432     if (p->isOutput() || p->getWidth() == 0) {
433         // nothing to edit, go info mode
434         from = to;
435     } else {
436         // port is input and has links, go editing mode
437         IntegralBusType *bt = new IntegralBusType(Descriptor(), QMap<Descriptor, DataTypePtr>());
438         bt->addInputs(p, false);
439         from = bt;
440     }
441     return from;
442 }
443 
findPathToSchemaFile(const QString & name)444 QString WorkflowUtils::findPathToSchemaFile(const QString &name) {
445     // full path given
446     if (QFile::exists(name)) {
447         return name;
448     }
449     // search schema in data dir
450     QString filenameWithDataPrefix = QString(PATH_PREFIX_DATA) + ":" + "cmdline/" + name;
451     if (QFile::exists(filenameWithDataPrefix)) {
452         return filenameWithDataPrefix;
453     }
454     foreach (const QString &ext, WorkflowUtils::WD_FILE_EXTENSIONS) {
455         QString filenameWithDataPrefixAndExt = QString(PATH_PREFIX_DATA) + ":" + "cmdline/" + name + "." + ext;
456         if (QFile::exists(filenameWithDataPrefixAndExt)) {
457             return filenameWithDataPrefixAndExt;
458         }
459     }
460 
461     // if no such file found -> search name in settings. user saved schemas
462     Settings *settings = AppContext::getSettings();
463     assert(settings != nullptr);
464 
465     // FIXME: same as WorkflowSceneIOTasks::SCHEMA_PATHS_SETTINGS_TAG
466     QVariantMap pathsMap = settings->getValue("workflow_settings/schema_paths").toMap();
467     QString path = pathsMap.value(name).toString();
468     if (QFile::exists(path)) {
469         return path;
470     }
471     return QString();
472 }
473 
getLinkedActorsId(Actor * a,QList<QString> & linkedActors)474 void WorkflowUtils::getLinkedActorsId(Actor *a, QList<QString> &linkedActors) {
475     if (!linkedActors.contains(a->getId())) {
476         linkedActors.append(a->getId());
477         foreach (Port *p, a->getPorts()) {
478             foreach (Port *pp, p->getLinks().keys()) {
479                 getLinkedActorsId(pp->owner(), linkedActors);
480             }
481         }
482     } else {
483         return;
484     }
485 }
486 
isPathExist(const Port * src,const Port * dest)487 bool WorkflowUtils::isPathExist(const Port *src, const Port *dest) {
488     SAFE_POINT((src->isInput() ^ dest->isInput()), "The ports have the same direction", true);
489     if (!src->isOutput() && !dest->isInput()) {
490         const Port *tmp = dest;
491         dest = src;
492         src = tmp;
493     }
494     const Actor *destElement = dest->owner();
495 
496     foreach (const Port *port, src->owner()->getPorts()) {
497         if (src == port) {
498             continue;
499         }
500         foreach (const Port *p, port->getLinks().keys()) {
501             if (destElement == p->owner()) {
502                 return true;
503             }
504             if (isPathExist(p, dest)) {
505                 return true;
506             }
507         }
508     }
509     return false;
510 }
511 
getSlotDescOfDatatype(const DataTypePtr & dt)512 Descriptor WorkflowUtils::getSlotDescOfDatatype(const DataTypePtr &dt) {
513     QString dtId = dt->getId();
514     if (dtId == BaseTypes::DNA_SEQUENCE_TYPE()->getId()) {
515         return BaseSlots::DNA_SEQUENCE_SLOT();
516     }
517     if (dtId == BaseTypes::ANNOTATION_TABLE_TYPE()->getId()) {
518         return BaseSlots::ANNOTATION_TABLE_SLOT();
519     }
520     if (dtId == BaseTypes::MULTIPLE_ALIGNMENT_TYPE()->getId()) {
521         return BaseSlots::MULTIPLE_ALIGNMENT_SLOT();
522     }
523     if (dtId == BaseTypes::STRING_TYPE()->getId()) {
524         return BaseSlots::TEXT_SLOT();
525     }
526     SAFE_POINT(false, "Unexpected slot type", Descriptor());
527     return Descriptor();
528 }
529 
initLowerToUpperList()530 static QStringList initLowerToUpperList() {
531     QStringList res;
532     res << "true";
533     res << "false";
534     return res;
535 }
536 static const QStringList lowerToUpperList = initLowerToUpperList();
537 
getStringForParameterDisplayRole(const QVariant & value)538 QString WorkflowUtils::getStringForParameterDisplayRole(const QVariant &value) {
539     if (value.canConvert<QList<Dataset>>()) {
540         QString res;
541         foreach (const Dataset &dSet, value.value<QList<Dataset>>()) {
542             res += dSet.getName() + "; ";
543         }
544         return res;
545     }
546     QString str = value.toString();
547     if (lowerToUpperList.contains(str)) {
548         return str.at(0).toUpper() + str.mid(1);
549     }
550     return str;
551 }
552 
findActorByParamAlias(const QList<Actor * > & procs,const QString & alias,QString & attrName,bool writeLog)553 Actor *WorkflowUtils::findActorByParamAlias(const QList<Actor *> &procs, const QString &alias, QString &attrName, bool writeLog) {
554     QList<Actor *> actors;
555     foreach (Actor *actor, procs) {
556         assert(actor != nullptr);
557         if (actor->getParamAliases().values().contains(alias)) {
558             actors << actor;
559         }
560     }
561 
562     if (actors.isEmpty()) {
563         return nullptr;
564     } else if (actors.size() > 1) {
565         if (writeLog) {
566             coreLog.error(WorkflowUtils::tr("%1 actors in workflow have '%2' alias").arg(actors.size()).arg(alias));
567         }
568     }
569 
570     Actor *ret = actors.first();
571     attrName = ret->getParamAliases().key(alias);
572     return ret;
573 }
574 
getParamIdFromHref(const QString & href)575 QString WorkflowUtils::getParamIdFromHref(const QString &href) {
576     QStringList args = href.split('&');
577     const QString &prefix = QString("%1:").arg(HREF_PARAM_ID);
578     QString id;
579     foreach (QString arg, args) {
580         if (arg.startsWith(prefix)) {
581             id = arg.mid(prefix.length());
582             break;
583         }
584     }
585     return id;
586 }
587 
generateIdFromName(const QString & name)588 QString WorkflowUtils::generateIdFromName(const QString &name) {
589     QString id = name;
590     id.replace(QRegularExpression("\\s"), "-").replace(WorkflowEntityValidator::INACCEPTABLE_SYMBOLS_IN_ID, "_");
591     return id;
592 }
593 
data2text(WorkflowContext * context,DocumentFormatId formatId,GObject * obj,QString & text)594 static void data2text(WorkflowContext *context, DocumentFormatId formatId, GObject *obj, QString &text) {
595     QList<GObject *> objList;
596     objList << obj;
597 
598     IOAdapterFactory *iof = AppContext::getIOAdapterRegistry()->getIOAdapterFactoryById(BaseIOAdapters::STRING);
599     DocumentFormat *df = AppContext::getDocumentFormatRegistry()->getFormatById(formatId);
600     QScopedPointer<Document> d(new Document(df, iof, GUrl(), context->getDataStorage()->getDbiRef(), objList));
601     d->setDocumentOwnsDbiResources(false);
602     StringAdapter *io = dynamic_cast<StringAdapter *>(iof->createIOAdapter());
603     io->open(GUrl(), IOAdapterMode_Write);
604     U2OpStatusImpl os;
605 
606     df->storeDocument(d.data(), io, os);
607 
608     text += io->getBuffer();
609     io->close();
610 }
611 
612 #define STRING_TYPE QVariant::String
613 #define SEQUENCE_TYPE QVariant::ByteArray
614 #define MSA_TYPE QVariant::UserType
615 #define ANNOTATIONS_TYPE QVariant::List
616 
print(const QString & slotString,const QVariant & data,DataTypePtr type,WorkflowContext * context)617 void WorkflowUtils::print(const QString &slotString, const QVariant &data, DataTypePtr type, WorkflowContext *context) {
618     QString text = slotString + ":\n";
619     Workflow::DbiDataStorage *storage = context->getDataStorage();
620     if ("string" == type->getId() || BaseTypes::STRING_LIST_TYPE() == type) {
621         text += data.toString();
622     } else if (BaseTypes::DNA_SEQUENCE_TYPE() == type) {
623         QScopedPointer<U2SequenceObject> obj(StorageUtils::getSequenceObject(storage, data.value<SharedDbiDataHandler>()));
624         CHECK(nullptr != obj.data(), );
625         data2text(context, BaseDocumentFormats::FASTA, obj.data(), text);
626     } else if (BaseTypes::MULTIPLE_ALIGNMENT_TYPE() == type) {
627         QScopedPointer<MultipleSequenceAlignmentObject> obj(StorageUtils::getMsaObject(storage, data.value<SharedDbiDataHandler>()));
628         CHECK(nullptr != obj.data(), );
629         data2text(context, BaseDocumentFormats::CLUSTAL_ALN, obj.data(), text);
630     } else if (BaseTypes::ANNOTATION_TABLE_TYPE() == type || BaseTypes::ANNOTATION_TABLE_LIST_TYPE() == type) {
631         QList<SharedAnnotationData> annotationList = StorageUtils::getAnnotationTable(storage, data);
632         AnnotationTableObject obj("Annotations", storage->getDbiRef());
633         obj.addAnnotations(annotationList);
634         data2text(context, BaseDocumentFormats::PLAIN_GENBANK, &obj, text);
635     } else {
636         text += "Can not print data of this type: " + type->getDisplayName();
637     }
638     printf("\n%s\n", text.toLatin1().data());
639 }
640 
validateSchemaForIncluding(const Schema & s,QString & error)641 bool WorkflowUtils::validateSchemaForIncluding(const Schema &s, QString &error) {
642     // TEMPORARY disallow filter and grouper elements in includes
643     static QString errorStr = tr("The %1 element is a %2. Sorry, but current version of "
644                                  "UGENE doesn't support of filters and groupers in the includes.");
645     foreach (Actor *actor, s.getProcesses()) {
646         ActorPrototype *proto = actor->getProto();
647         if (proto->getInfluenceOnPathFlag() || CoreLibConstants::GROUPER_ID == proto->getId()) {
648             error = errorStr;
649             error = error.arg(actor->getLabel());
650             if (proto->getInfluenceOnPathFlag()) {
651                 error = error.arg(tr("filter"));
652             } else {
653                 error = error.arg(tr("grouper"));
654             }
655             return false;
656         }
657     }
658 
659     const QList<PortAlias> &portAliases = s.getPortAliases();
660     if (portAliases.isEmpty()) {
661         error = tr("The workflow has not any aliased ports");
662         return false;
663     }
664 
665     foreach (Actor *actor, s.getProcesses()) {
666         // check that free input ports are aliased
667         foreach (Port *port, actor->getPorts()) {
668             if (!port->isInput()) {
669                 continue;
670             }
671             if (!port->getLinks().isEmpty()) {
672                 continue;
673             }
674             bool aliased = false;
675             foreach (const PortAlias &alias, portAliases) {
676                 if (alias.getSourcePort() == port) {
677                     if (alias.getSlotAliases().isEmpty()) {
678                         error = tr("The aliased port %1.%2 has no aliased slots").arg(actor->getLabel()).arg(port->getDisplayName());
679                         return false;
680                     } else {
681                         aliased = true;
682                         break;
683                     }
684                 }
685             }
686             if (!aliased) {
687                 error = tr("The free port %1.%2 is not aliased").arg(actor->getLabel()).arg(port->getId());
688                 return false;
689             }
690         }
691 
692         // check that every required attribute is aliased or has set value
693         const QMap<QString, QString> &paramAliases = actor->getParamAliases();
694         foreach (const QString &attrName, actor->getParameters().keys()) {
695             Attribute *attr = actor->getParameters().value(attrName);
696             if (attr->isRequiredAttribute() && !attr->canBeEmpty()) {
697                 if (!paramAliases.contains(attr->getId())) {
698                     QVariant val = attr->getAttributeValueWithoutScript<QVariant>();
699                     if (val.isNull()) {
700                         error = tr("The required parameter %1.%2 is empty and not aliased").arg(actor->getLabel()).arg(attr->getDisplayName());
701                         return false;
702                     }
703                 }
704             }
705         }
706     }
707 
708     return true;
709 }
710 
extractPathsFromBindings(StrStrMap & busMap,SlotPathMap & pathMap)711 void WorkflowUtils::extractPathsFromBindings(StrStrMap &busMap, SlotPathMap &pathMap) {
712     QString srcId;
713     QStringList path;
714     foreach (const QString &dest, busMap.keys()) {
715         QStringList srcs = busMap.value(dest).split(";");
716         foreach (const QString &src, srcs) {
717             BusMap::parseSource(src, srcId, path);
718             if (!path.isEmpty()) {
719                 QPair<QString, QString> slotPair(dest, srcId);
720                 busMap[dest] = srcId;
721                 pathMap.insertMulti(slotPair, path);
722             }
723         }
724     }
725 }
726 
applyPathsToBusMap(StrStrMap & busMap,const SlotPathMap & pathMap)727 void WorkflowUtils::applyPathsToBusMap(StrStrMap &busMap, const SlotPathMap &pathMap) {
728     foreach (const QString &dest, busMap.keys()) {
729         QStringList newSrcs;
730 
731         QStringList srcs = busMap.value(dest).split(";");
732         QStringList uniqList;
733         foreach (QString src, srcs) {
734             if (!uniqList.contains(src)) {
735                 uniqList << src;
736             }
737         }
738 
739         foreach (const QString &src, uniqList) {
740             QPair<QString, QString> slotPair(dest, src);
741             if (pathMap.contains(slotPair)) {
742                 QList<QStringList> paths = pathMap.values(slotPair);
743                 if (!paths.isEmpty()) {
744                     foreach (const QStringList &path, paths) {
745                         QString newSrc = src + ">" + path.join(",");
746                         newSrcs << newSrc;
747                     }
748                 }
749             } else {
750                 newSrcs << src;
751             }
752         }
753         busMap[dest] = newSrcs.join(";");
754     }
755 }
756 
startExternalProcess(QProcess * process,const QString & program,const QStringList & arguments)757 bool WorkflowUtils::startExternalProcess(QProcess *process, const QString &program, const QStringList &arguments) {
758     return ExternalToolSupportUtils::startExternalProcess(process, program, arguments);
759 }
760 
getDatasetsUrls(const QList<Dataset> & sets)761 QStringList WorkflowUtils::getDatasetsUrls(const QList<Dataset> &sets) {
762     QStringList result;
763     foreach (const Dataset &dSet, sets) {
764         foreach (URLContainer *url, dSet.getUrls()) {
765             result << url->getUrl();
766         }
767     }
768     return result;
769 }
770 
getAttributeUrls(Attribute * attribute)771 QStringList WorkflowUtils::getAttributeUrls(Attribute *attribute) {
772     QStringList urlList;
773     QVariant var = attribute->getAttributePureValue();
774     if (var.canConvert<QList<Dataset>>()) {
775         urlList = WorkflowUtils::getDatasetsUrls(var.value<QList<Dataset>>());
776     } else if (var.canConvert(QVariant::String)) {
777         urlList = var.toString().split(";");
778     }
779     return urlList;
780 }
781 
actorById(const QList<Actor * > & actors,const ActorId & id)782 Actor *WorkflowUtils::actorById(const QList<Actor *> &actors, const ActorId &id) {
783     foreach (Actor *a, actors) {
784         if (a->getId() == id) {
785             return a;
786         }
787     }
788     return nullptr;
789 }
790 
getBusType(Port * inPort)791 QMap<Descriptor, DataTypePtr> WorkflowUtils::getBusType(Port *inPort) {
792     QMap<Port *, Link *> links = inPort->getLinks();
793     if (links.size() == 1) {
794         Port *src = links.keys().first();
795         assert(src->isOutput());
796         IntegralBusPort *bus = dynamic_cast<IntegralBusPort *>(src);
797         assert(nullptr != bus);
798         DataTypePtr type = bus->getType();
799         return type->getDatatypesMap();
800     }
801     return QMap<Descriptor, DataTypePtr>();
802 }
803 
isBindingValid(const QString & srcSlotId,const QMap<Descriptor,DataTypePtr> & srcBus,const QString & dstSlotId,const QMap<Descriptor,DataTypePtr> & dstBus)804 bool WorkflowUtils::isBindingValid(const QString &srcSlotId, const QMap<Descriptor, DataTypePtr> &srcBus, const QString &dstSlotId, const QMap<Descriptor, DataTypePtr> &dstBus) {
805     DataTypePtr srcType;
806     // Check that incoming bus contains source slot
807     bool found = false;
808     foreach (const Descriptor &d, srcBus.keys()) {
809         if (d.getId() == srcSlotId) {
810             srcType = srcBus.value(d);
811             found = true;
812             break;
813         }
814     }
815     if (!found) {
816         return false;
817     }
818 
819     // Check that source and destination slots have equal types
820     foreach (const Descriptor &d, dstBus.keys()) {
821         if (d.getId() == dstSlotId) {
822             DataTypePtr destType = dstBus.value(d);
823             QString stringTypeId("string");
824             if (destType == srcType) {
825                 return true;
826             } else if (destType == BaseTypes::ANNOTATION_TABLE_TYPE()) {
827                 return (srcType == BaseTypes::ANNOTATION_TABLE_LIST_TYPE());
828             } else if (destType == BaseTypes::ANNOTATION_TABLE_LIST_TYPE()) {
829                 return (srcType == BaseTypes::ANNOTATION_TABLE_TYPE());
830             } else if (destType->getId() == stringTypeId) {
831                 return (srcType == BaseTypes::STRING_LIST_TYPE());
832             } else if (destType == BaseTypes::STRING_LIST_TYPE()) {
833                 return (srcType->getId() == stringTypeId);
834             }
835             break;
836         }
837     }
838 
839     return false;
840 }
841 
createUniqueString(const QString & str,const QString & sep,const QStringList & uniqueStrs)842 QString WorkflowUtils::createUniqueString(const QString &str, const QString &sep, const QStringList &uniqueStrs) {
843     QString result = str;
844     int number = 0;
845     bool found = false;
846     foreach (const QString &uniq, uniqueStrs) {
847         if (uniq == str) {
848             found = true;
849             number = qMax(number, 1);
850         } else {
851             int idx = uniq.lastIndexOf(sep);
852             if (-1 != idx) {
853                 QString left = uniq.left(idx);
854                 if (str == left) {
855                     QString right = uniq.mid(idx + 1);
856                     bool ok = false;
857                     int num = right.toInt(&ok);
858                     if (ok) {
859                         found = true;
860                         number = qMax(number, num + 1);
861                     }
862                 }
863             }
864         }
865     }
866 
867     if (found) {
868         result += sep + QString::number(number);
869     }
870     return result;
871 }
872 
updateExternalToolPath(const QString & id,const QString & path)873 QString WorkflowUtils::updateExternalToolPath(const QString &id, const QString &path) {
874     ExternalToolRegistry *registry = AppContext::getExternalToolRegistry();
875     SAFE_POINT(nullptr != registry, "NULL external tool registry", "");
876     ExternalTool *tool = registry->getById(id);
877     SAFE_POINT(nullptr != tool, QString("Unknown tool: %1").arg(id), "");
878 
879     if (QString::compare(path, "default", Qt::CaseInsensitive) != 0) {
880         tool->setPath(path);
881     }
882     return tool->getPath();
883 }
884 
getExternalToolPath(const QString & toolId)885 QString WorkflowUtils::getExternalToolPath(const QString &toolId) {
886     ExternalToolRegistry *registry = AppContext::getExternalToolRegistry();
887     SAFE_POINT(nullptr != registry, "NULL external tool registry", "");
888 
889     ExternalTool *tool = registry->getById(toolId);
890     SAFE_POINT(nullptr != tool, QString("Unknown tool (id): %1").arg(toolId), "");
891 
892     return tool->getPath();
893 }
894 
externalToolIsAbsentError(const QString & toolName)895 QString WorkflowUtils::externalToolIsAbsentError(const QString &toolName) {
896     return tr("Specified variable \"%%1%\" does not exist, please check the command again.").arg(toolName);
897 }
898 
externalToolError(const QString & toolName)899 QString WorkflowUtils::externalToolError(const QString &toolName) {
900     return tr("External tool \"%1\" is not set. You can set it in Settings -> Preferences -> External Tools").arg(toolName);
901 }
902 
externalToolInvalidError(const QString & toolName)903 QString WorkflowUtils::externalToolInvalidError(const QString &toolName) {
904     return tr("External tool \"%1\" is invalid. UGENE may not support this version of the tool or a wrong path to the tools is selected").arg(toolName);
905 }
906 
customExternalToolInvalidError(const QString & toolName,const QString & elementName)907 QString WorkflowUtils::customExternalToolInvalidError(const QString &toolName, const QString &elementName) {
908     return tr("Custom tool \"%1\", specified for the \"%2\" element, didn't pass validation.").arg(toolName).arg(elementName);
909 }
910 
schemaFromFile(const QString & url,Schema * schema,Metadata * meta,U2OpStatus & os)911 void WorkflowUtils::schemaFromFile(const QString &url, Schema *schema, Metadata *meta, U2OpStatus &os) {
912     QFile file(url);
913     if (!file.open(QIODevice::ReadOnly)) {
914         os.setError(L10N::errorOpeningFileRead(url));
915         return;
916     }
917     QTextStream in(&file);
918     in.setCodec("UTF-8");
919     QString rawData = in.readAll();
920     file.close();
921 
922     QString error = HRSchemaSerializer::string2Schema(rawData, schema, meta);
923     if (!error.isEmpty()) {
924         os.setError(error);
925     }
926 }
927 
isDatasetsAttr(Attribute * attr)928 static bool isDatasetsAttr(Attribute *attr) {
929     URLAttribute *dsa = dynamic_cast<URLAttribute *>(attr);
930     return (nullptr != dsa);
931 }
932 
isUrlAttribute(Attribute * attr,const Actor * actor)933 UrlAttributeType WorkflowUtils::isUrlAttribute(Attribute *attr, const Actor *actor) {
934     SAFE_POINT(nullptr != attr, "NULL attribute!", NotAnUrl);
935     SAFE_POINT(nullptr != actor, "NULL actor!", NotAnUrl);
936 
937     if (isDatasetsAttr(attr)) {
938         return DatasetAttr;
939     }
940 
941     ConfigurationEditor *editor = actor->getEditor();
942     CHECK(nullptr != editor, NotAnUrl);
943     PropertyDelegate *delegate = editor->getDelegate(attr->getId());
944     CHECK(nullptr != delegate, NotAnUrl);
945 
946     if (PropertyDelegate::INPUT_FILE == delegate->type()) {
947         return InputFile;
948     }
949     if (PropertyDelegate::INPUT_DIR == delegate->type()) {
950         return InputDir;
951     }
952     if (PropertyDelegate::OUTPUT_FILE == delegate->type()) {
953         return OutputFile;
954     }
955     if (PropertyDelegate::OUTPUT_DIR == delegate->type()) {
956         return OutputDir;
957     }
958 
959     return NotAnUrl;
960 }
961 
962 /** Truncate the last ';' character */
normalizeUrls(QString & urls)963 static void normalizeUrls(QString &urls) {
964     if (!urls.isEmpty() && (1 != urls.size()) && (urls[urls.size() - 1] == ';')) {
965         urls.truncate(urls.size() - 1);
966     }
967 }
968 
validateInputFiles(QString urls,NotificationsList & notificationList)969 bool WorkflowUtils::validateInputFiles(QString urls, NotificationsList &notificationList) {
970     normalizeUrls(urls);
971     if (urls.isEmpty()) {
972         return true;
973     }
974 
975     // Verify each URL
976     QStringList urlsList = urls.split(';');
977     bool res = true;
978     foreach (const QString &url, urlsList) {
979         QFileInfo fi(url);
980         if (!fi.exists()) {
981             notificationList << WorkflowNotification(L10N::errorFileNotFound(url));
982             res = false;
983         } else if (!fi.isFile()) {
984             notificationList << WorkflowNotification(L10N::errorIsNotAFile(url));
985             res = false;
986         } else {
987             QFile testReadAccess(url);
988             if (testReadAccess.open(QIODevice::ReadOnly)) {
989                 testReadAccess.close();
990             } else {
991                 notificationList << WorkflowNotification(L10N::errorOpeningFileRead(url));
992                 res = false;
993             }
994         }
995     }
996     return res;
997 }
998 
validateInputDirs(QString urls,NotificationsList & notificationList)999 bool WorkflowUtils::validateInputDirs(QString urls, NotificationsList &notificationList) {
1000     normalizeUrls(urls);
1001     if (urls.isEmpty()) {
1002         return true;
1003     }
1004 
1005     QStringList urlsList = urls.split(';');
1006     bool res = true;
1007     foreach (const QString &url, urlsList) {
1008         QFileInfo fi(url);
1009         if (!fi.exists()) {
1010             notificationList << WorkflowNotification(L10N::errorDirNotFound(url));
1011             res = false;
1012         } else if (!fi.isDir()) {
1013             notificationList << WorkflowNotification(L10N::errorIsNotADir(url));
1014             res = false;
1015         }
1016     }
1017     return res;
1018 }
1019 
1020 namespace {
1021 
url2Ref(const QString & url)1022 U2DbiRef url2Ref(const QString &url) {
1023     const QStringList urlParts = url.split(SharedDbUrlUtils::DB_PROVIDER_SEP);
1024     CHECK(urlParts.size() == 2, U2DbiRef());
1025 
1026     return U2DbiRef(urlParts[0], urlParts[1]);
1027 }
1028 
checkDbCredentials(const QString & dbUrl)1029 bool checkDbCredentials(const QString &dbUrl) {
1030     QString userName;
1031     const QString shortDbiUrl = U2DbiUtils::full2shortDbiUrl(dbUrl, userName);
1032     CHECK(!userName.isEmpty(), false);
1033 
1034     if (!AppContext::getPasswordStorage()->contains(dbUrl)) {
1035         return AppContext::getCredentialsAsker()->askWithFixedLogin(dbUrl);
1036     } else {
1037         return true;
1038     }
1039 }
1040 
checkObjectInDb(const QString & url)1041 bool checkObjectInDb(const QString &url) {
1042     const QStringList urlParts = url.split(",");
1043     SAFE_POINT(urlParts.size() == 2, "Invalid DB object URL", false);
1044     const QString dbUrl = urlParts[0];
1045 
1046     U2OpStatusImpl os;
1047     const U2DbiRef dbRef = url2Ref(dbUrl);
1048     CHECK(dbRef.isValid(), false);
1049 
1050     const U2DataId realId = SharedDbUrlUtils::getObjectIdByUrl(url);
1051     CHECK(!realId.isEmpty(), false);
1052 
1053     DbiConnection connection(dbRef, os);
1054     CHECK_OP(os, false);
1055     CHECK(nullptr != connection.dbi, false);
1056 
1057     U2ObjectDbi *oDbi = connection.dbi->getObjectDbi();
1058     CHECK(nullptr != oDbi, false);
1059     U2Object testObject;
1060     oDbi->getObject(testObject, realId, os);
1061     CHECK_OP(os, false);
1062 
1063     return testObject.hasValidId();
1064 }
1065 
checkFolderInDb(const QString & dbUrl,const QString & folderPath)1066 bool checkFolderInDb(const QString &dbUrl, const QString &folderPath) {
1067     U2OpStatusImpl os;
1068     const U2DbiRef dbRef = url2Ref(dbUrl);
1069     CHECK(dbRef.isValid(), false);
1070 
1071     CHECK(!folderPath.isEmpty() && folderPath.startsWith(U2ObjectDbi::ROOT_FOLDER), false);
1072 
1073     DbiConnection connection(dbRef, os);
1074     CHECK_OP(os, false);
1075     CHECK(nullptr != connection.dbi, false);
1076 
1077     U2ObjectDbi *oDbi = connection.dbi->getObjectDbi();
1078     CHECK(nullptr != oDbi, false);
1079     const qint64 folderVersion = oDbi->getFolderLocalVersion(folderPath, os);
1080     CHECK_OP(os, false);
1081 
1082     return -1 != folderVersion;
1083 }
1084 
checkWritePermissionsForDb(const QString & fullDbUrl)1085 bool checkWritePermissionsForDb(const QString &fullDbUrl) {
1086     U2OpStatusImpl os;
1087     const U2DbiRef dbRef = SharedDbUrlUtils::getDbRefFromEntityUrl(fullDbUrl);
1088     CHECK(dbRef.isValid(), false);
1089 
1090     DbiConnection connection(dbRef, os);
1091     CHECK_OP(os, false);
1092     return !connection.dbi->getFeatures().contains(U2DbiFeature_GlobalReadOnly);
1093 }
1094 
1095 // If a database was unavailable for some reasons during previous validation procedures
1096 // and now has become available, it is needed to remove previous error messages regarding this from a notification list.
checkDbConnectionAndFixProblems(const QString & dbUrl,NotificationsList & notificationList,const WorkflowNotification & notificationMsg)1097 bool checkDbConnectionAndFixProblems(const QString &dbUrl, NotificationsList &notificationList, const WorkflowNotification &notificationMsg) {
1098     if (!WorkflowUtils::checkSharedDbConnection(dbUrl)) {
1099         notificationList << notificationMsg;
1100         return false;
1101     } else {
1102         foreach (WorkflowNotification notification, notificationList) {
1103             if (notification.message == notificationMsg.message && notification.type == notificationMsg.type) {
1104                 notificationList.removeAll(notification);
1105             }
1106         }
1107         return true;
1108     }
1109 }
1110 
1111 }  // namespace
1112 
checkSharedDbConnection(const QString & fullDbUrl)1113 bool WorkflowUtils::checkSharedDbConnection(const QString &fullDbUrl) {
1114     U2OpStatusImpl os;
1115     const U2DbiRef dbRef = SharedDbUrlUtils::getDbRefFromEntityUrl(fullDbUrl);
1116     CHECK(dbRef.isValid(), false);
1117     CHECK(checkDbCredentials(dbRef.dbiId), false);
1118 
1119     DbiConnection connection(dbRef, os);
1120     CHECK_OP_EXT(os, AppContext::getPasswordStorage()->removeEntry(dbRef.dbiId), false);
1121     return connection.isOpen();
1122 }
1123 
validateInputDbObject(const QString & url,NotificationsList & notificationList)1124 bool WorkflowUtils::validateInputDbObject(const QString &url, NotificationsList &notificationList) {
1125     const QString dbUrl = SharedDbUrlUtils::getDbUrlFromEntityUrl(url);
1126     const U2DataId objId = SharedDbUrlUtils::getObjectIdByUrl(url);
1127     const QString objName = SharedDbUrlUtils::getDbObjectNameByUrl(url);
1128     const QString shortDbName = SharedDbUrlUtils::getDbShortNameFromEntityUrl(url);
1129     if (dbUrl.isEmpty() || objId.isEmpty() || objName.isEmpty()) {
1130         notificationList << WorkflowNotification(L10N::errorWrongDbObjUrlFormat(url));
1131         return false;
1132     } else if (!checkDbConnectionAndFixProblems(dbUrl, notificationList, WorkflowNotification(L10N::errorDbInacsessible(shortDbName)))) {
1133         return false;
1134     } else if (!checkObjectInDb(url)) {
1135         notificationList << WorkflowNotification(L10N::errorDbObjectInaccessible(shortDbName, objName));
1136         return false;
1137     }
1138     return true;
1139 }
1140 
validateInputDbFolders(QString urls,NotificationsList & notificationList)1141 bool WorkflowUtils::validateInputDbFolders(QString urls, NotificationsList &notificationList) {
1142     normalizeUrls(urls);
1143     if (urls.isEmpty()) {
1144         return true;
1145     }
1146 
1147     QStringList urlsList = urls.split(';');
1148     bool res = true;
1149     foreach (const QString &url, urlsList) {
1150         const QString dbUrl = SharedDbUrlUtils::getDbUrlFromEntityUrl(url);
1151         const QString folderPath = SharedDbUrlUtils::getDbFolderPathByUrl(url);
1152         const U2DataType dataType = SharedDbUrlUtils::getDbFolderDataTypeByUrl(url);
1153         const QString shortDbName = SharedDbUrlUtils::getDbShortNameFromEntityUrl(url);
1154         if (dbUrl.isEmpty() || folderPath.isEmpty() || U2Type::Unknown == dataType) {
1155             notificationList << WorkflowNotification(L10N::errorWrongDbFolderUrlFormat(url));
1156             res = false;
1157         } else if (!checkDbConnectionAndFixProblems(dbUrl, notificationList, WorkflowNotification(L10N::errorDbInacsessible(shortDbName)))) {
1158             res = false;
1159         } else if (!checkFolderInDb(dbUrl, folderPath)) {
1160             notificationList << WorkflowNotification(L10N::errorDbFolderInacsessible(shortDbName, folderPath));
1161             res = false;
1162         }
1163     }
1164     return res;
1165 }
1166 
validateOutputFile(const QString & url,NotificationsList & notificationList)1167 bool WorkflowUtils::validateOutputFile(const QString &url, NotificationsList &notificationList) {
1168     if (url.isEmpty()) {
1169         return true;
1170     }
1171 
1172     QFileInfo fi(url);
1173     if (fi.isRelative()) {
1174         fi.setFile(QDir(WorkflowSettings::getWorkflowOutputDirectory()), url);
1175     }
1176 
1177     if (FileAndDirectoryUtils::canWriteToPath(fi.absolutePath())) {
1178         return true;
1179     }
1180     notificationList << WorkflowNotification(tr("Can't access output file path: '%1'").arg(fi.absoluteFilePath()));
1181     return false;
1182 }
1183 
validateOutputDir(const QString & url,NotificationsList & notificationList)1184 bool WorkflowUtils::validateOutputDir(const QString &url, NotificationsList &notificationList) {
1185     if (url.isEmpty()) {
1186         return true;
1187     }
1188 
1189     QFileInfo fi(url);
1190     if (fi.isRelative()) {
1191         fi.setFile(QDir(WorkflowSettings::getWorkflowOutputDirectory()), url);
1192     }
1193 
1194     if (FileAndDirectoryUtils::canWriteToPath(fi.absoluteFilePath())) {
1195         return true;
1196     }
1197     notificationList << WorkflowNotification(tr("Workflow output folder '%1' can't be accessed. Check that the folder exists and you have"
1198                                                 " enough permissions to write to it, or choose another folder in the UGENE Application Settings.")
1199                                                  .arg(url),
1200                                              "",
1201                                              WorkflowNotification::U2_ERROR);
1202     return false;
1203 }
1204 
isSharedDbUrlAttribute(const Attribute * attr,const Actor * actor)1205 bool WorkflowUtils::isSharedDbUrlAttribute(const Attribute *attr, const Actor *actor) {
1206     SAFE_POINT(nullptr != attr, "Invalid attribute supplied", false);
1207     SAFE_POINT(nullptr != actor, "Invalid actor supplied", false);
1208 
1209     ConfigurationEditor *editor = actor->getEditor();
1210     CHECK(nullptr != editor, false);
1211     PropertyDelegate *delegate = editor->getDelegate(attr->getId());
1212     CHECK(nullptr != delegate, false);
1213 
1214     return PropertyDelegate::SHARED_DB_URL == delegate->type();
1215 }
1216 
validateSharedDbUrl(const QString & url,NotificationsList & notificationList)1217 bool WorkflowUtils::validateSharedDbUrl(const QString &url, NotificationsList &notificationList) {
1218     if (url.isEmpty()) {
1219         notificationList << WorkflowNotification(tr("Empty shared database URL specified"));
1220         return false;
1221     }
1222 
1223     const U2DbiRef dbRef = SharedDbUrlUtils::getDbRefFromEntityUrl(url);
1224     const QString shortDbName = SharedDbUrlUtils::getDbShortNameFromEntityUrl(url);
1225     if (!dbRef.isValid()) {
1226         notificationList << WorkflowNotification(L10N::errorWrongDbFolderUrlFormat(url));
1227         return false;
1228     } else if (!checkDbConnectionAndFixProblems(url, notificationList, WorkflowNotification(L10N::errorDbInacsessible(shortDbName)))) {
1229         return false;
1230     } else if (!checkWritePermissionsForDb(url)) {
1231         notificationList << WorkflowNotification(L10N::errorDbWritePermissons(shortDbName));
1232         return false;
1233     }
1234 
1235     return true;
1236 }
1237 
validateDatasets(const QList<Dataset> & sets,NotificationsList & notificationList)1238 bool WorkflowUtils::validateDatasets(const QList<Dataset> &sets, NotificationsList &notificationList) {
1239     bool res = true;
1240     foreach (const Dataset &set, sets) {
1241         foreach (URLContainer *urlContainer, set.getUrls()) {
1242             SAFE_POINT(nullptr != urlContainer, "NULL URLContainer!", false);
1243             bool urlIsValid = urlContainer->validateUrl(notificationList);
1244             res = res && urlIsValid;
1245         }
1246     }
1247     return res;
1248 }
1249 
datasetsToScript(const QList<Dataset> & sets,QScriptEngine & engine)1250 QScriptValue WorkflowUtils::datasetsToScript(const QList<Dataset> &sets, QScriptEngine &engine) {
1251     QScriptValue setsArray = engine.newArray(sets.size());
1252 
1253     for (int setIdx = 0; setIdx < sets.size(); setIdx++) {
1254         Dataset set = sets[setIdx];
1255         QScriptValue urls = engine.newArray(set.getUrls().size());
1256         for (int urlIdx = 0; urlIdx < set.getUrls().size(); urlIdx++) {
1257             QString url = set.getUrls()[urlIdx]->getUrl();
1258             urls.setProperty(urlIdx, engine.newVariant(url));
1259         }
1260         setsArray.setProperty(setIdx, urls);
1261     }
1262 
1263     return setsArray;
1264 }
1265 
getDatasetSplitter(const QString & filePaths)1266 QString WorkflowUtils::getDatasetSplitter(const QString &filePaths) {
1267     static const QString defaultSplitter = ";";
1268     static const QString additionalSplitter = ",";
1269 
1270     if (filePaths.contains(defaultSplitter)) {
1271         return defaultSplitter;
1272     }
1273     return additionalSplitter;
1274 }
1275 
packSamples(const QList<TophatSample> & samples)1276 QString WorkflowUtils::packSamples(const QList<TophatSample> &samples) {
1277     QStringList result;
1278     foreach (const TophatSample &sample, samples) {
1279         result << sample.name + ":" + sample.datasets.join(";");
1280     }
1281     return result.join(";;");
1282 }
1283 
unpackSamples(const QString & samplesStr,U2OpStatus & os)1284 QList<TophatSample> WorkflowUtils::unpackSamples(const QString &samplesStr, U2OpStatus &os) {
1285     QList<TophatSample> result;
1286 
1287     QStringList pairs = samplesStr.split(";;", QString::SkipEmptyParts);
1288     foreach (const QString &pairStr, pairs) {
1289         QStringList pair = pairStr.split(":", QString::KeepEmptyParts);
1290         if (2 != pair.size()) {
1291             os.setError(tr("Wrong samples map string"));
1292             return result;
1293         }
1294         result << TophatSample(pair[0], pair[1].split(";", QString::SkipEmptyParts));
1295     }
1296     return result;
1297 }
1298 
1299 const QString WorkflowEntityValidator::NAME_INACCEPTABLE_SYMBOLS_TEMPLATE = "=\\\"";
1300 const QString WorkflowEntityValidator::ID_ACCEPTABLE_SYMBOLS_TEMPLATE = "a-zA-Z0-9\\-_";
1301 
1302 const QRegularExpression WorkflowEntityValidator::ACCEPTABLE_NAME("[^" + NAME_INACCEPTABLE_SYMBOLS_TEMPLATE + "]*");
1303 const QRegularExpression WorkflowEntityValidator::INACCEPTABLE_SYMBOL_IN_NAME("[" + NAME_INACCEPTABLE_SYMBOLS_TEMPLATE + "]");
1304 const QRegularExpression WorkflowEntityValidator::ACCEPTABLE_ID("[" + ID_ACCEPTABLE_SYMBOLS_TEMPLATE + "]*");
1305 const QRegularExpression WorkflowEntityValidator::INACCEPTABLE_SYMBOLS_IN_ID("[^" + ID_ACCEPTABLE_SYMBOLS_TEMPLATE + "]");
1306 
1307 /*****************************
1308  * PrompterBaseImpl
1309  *****************************/
getParameter(const QString & id)1310 QVariant PrompterBaseImpl::getParameter(const QString &id) {
1311     if (map.contains(id)) {
1312         return map.value(id);
1313     } else {
1314         return target->getParameter(id)->getAttributePureValue();
1315     }
1316 }
1317 
getURL(const QString & id,bool * empty,const QString & onEmpty,const QString & defaultValue)1318 QString PrompterBaseImpl::getURL(const QString &id, bool *empty, const QString &onEmpty, const QString &defaultValue) {
1319     QVariant urlVar = getParameter(id);
1320     QString url;
1321     if (urlVar.canConvert<QList<Dataset>>()) {
1322         QStringList urls = WorkflowUtils::getDatasetsUrls(urlVar.value<QList<Dataset>>());
1323         url = urls.join(";");
1324     } else {
1325         url = getParameter(id).toString();
1326     }
1327     if (empty != nullptr) {
1328         *empty = false;
1329     }
1330     if (!target->getParameter(id)->getAttributeScript().isEmpty()) {
1331         url = "got from user script";
1332     } else if (url.isEmpty()) {
1333         if (!onEmpty.isEmpty()) {
1334             return onEmpty;
1335         }
1336         if (!defaultValue.isEmpty()) {
1337             url = defaultValue;
1338         } else {
1339             url = "<font color='red'>" + tr("unset") + "</font>";
1340         }
1341         if (empty != nullptr) {
1342             *empty = true;
1343         }
1344     } else if (url.indexOf(";") != -1) {
1345         url = tr("the list of files");
1346     } else if (SharedDbUrlUtils::isDbObjectUrl(url)) {
1347         url = SharedDbUrlUtils::getDbObjectNameByUrl(url);
1348     } else if (SharedDbUrlUtils::isDbFolderUrl(url)) {
1349         url = Folder::getFolderName(SharedDbUrlUtils::getDbFolderPathByUrl(url));
1350     } else {
1351         QString name = QFileInfo(url).fileName();
1352         if (!name.isEmpty()) {
1353             url = name;
1354         }
1355     }
1356     return url;
1357 }
1358 
getRequiredParam(const QString & id)1359 QString PrompterBaseImpl::getRequiredParam(const QString &id) {
1360     QString url = getParameter(id).toString();
1361     if (url.isEmpty()) {
1362         url = "<font color='red'>" + tr("unset") + "</font>";
1363     }
1364     return url;
1365 }
1366 
getScreenedURL(IntegralBusPort * input,const QString & id,const QString & slot,const QString & onEmpty)1367 QString PrompterBaseImpl::getScreenedURL(IntegralBusPort *input, const QString &id, const QString &slot, const QString &onEmpty) {
1368     bool empty = false;
1369     QString attrUrl = QString("<u>%1</u>").arg(getURL(id, &empty, onEmpty));
1370     if (!empty) {
1371         return attrUrl;
1372     }
1373 
1374     Actor *origin = input->getProducer(slot);
1375     QString slotUrl;
1376     if (origin != nullptr) {
1377         slotUrl = tr("file(s) alongside of input sources of <u>%1</u>").arg(origin->getLabel());
1378         return slotUrl;
1379     }
1380 
1381     assert(!attrUrl.isEmpty());
1382     return attrUrl;
1383 }
1384 
getProducers(const QString & port,const QString & slot)1385 QString PrompterBaseImpl::getProducers(const QString &port, const QString &slot) {
1386     IntegralBusPort *input = qobject_cast<IntegralBusPort *>(target->getPort(port));
1387     CHECK(nullptr != input, "");
1388     QList<Actor *> producers = input->getProducers(slot);
1389 
1390     QStringList labels;
1391     foreach (Actor *a, producers) {
1392         labels << a->getLabel();
1393     }
1394     return labels.join(", ");
1395 }
1396 
getProducersOrUnset(const QString & port,const QString & slot)1397 QString PrompterBaseImpl::getProducersOrUnset(const QString &port, const QString &slot) {
1398     static const QString unsetStr = "<font color='red'>" + tr("unset") + "</font>";
1399     QString prods = getProducers(port, slot);
1400     return prods.isEmpty() ? unsetStr : prods;
1401 }
1402 
getHyperlink(const QString & id,const QString & val)1403 QString PrompterBaseImpl::getHyperlink(const QString &id, const QString &val) {
1404     return QString("<a href=%1:%2>%3</a>").arg(WorkflowUtils::HREF_PARAM_ID).arg(id).arg(val);
1405 }
1406 
getHyperlink(const QString & id,int val)1407 QString PrompterBaseImpl::getHyperlink(const QString &id, int val) {
1408     return getHyperlink(id, QString::number(val));
1409 }
1410 
getHyperlink(const QString & id,qreal val)1411 QString PrompterBaseImpl::getHyperlink(const QString &id, qreal val) {
1412     return getHyperlink(id, QString::number(val));
1413 }
1414 
sl_actorModified()1415 void PrompterBaseImpl::sl_actorModified() {
1416     if (AppContext::isGUIMode()) {
1417         setHtml(QString("<center><b>%1</b></center><hr>%2").arg(target->getLabel()).arg(composeRichDoc()));
1418     }
1419 }
1420 
1421 }  // namespace U2
1422