otsdaq-utilities  3.10.00
ConfigurationGUISupervisor.cc
1 #include "otsdaq-utilities/ConfigurationGUI/ConfigurationGUISupervisor.h"
2 
3 #include "otsdaq/CgiDataUtilities/CgiDataUtilities.h"
4 #include "otsdaq/Macros/CoutMacros.h"
5 #include "otsdaq/Macros/StringMacros.h"
6 #include "otsdaq/MessageFacility/MessageFacility.h"
7 #include "otsdaq/TablePlugins/IterateTable.h"
8 #include "otsdaq/XmlUtilities/HttpXmlDocument.h"
9 
10 #include <boost/stacktrace.hpp>
11 
12 #include "otsdaq/GatewaySupervisor/GatewaySupervisor.h" //for saveModifiedVersionXML()
13 #include "otsdaq/TablePlugins/ARTDAQTableBase/ARTDAQTableBase.h" //for artdaq extraction
14 #include "otsdaq/TablePlugins/XDAQContextTable/XDAQContextTable.h" //for context relaunch
15 
16 #include <xdaq/NamespaceURI.h>
17 
18 #include <chrono>
19 #include <fstream>
20 #include <iostream>
21 #include <map>
22 #include <utility>
23 
24 using namespace ots;
25 
26 #undef __MF_SUBJECT__
27 #define __MF_SUBJECT__ "CfgGUI"
28 
29 #define TABLE_INFO_PATH std::string(__ENV__("TABLE_INFO_PATH")) + "/"
30 #define TABLE_INFO_EXT std::string("Info.xml")
31 
34 xdaq::Application* ConfigurationGUISupervisor::instantiate(xdaq::ApplicationStub* stub)
35 {
36  return new ConfigurationGUISupervisor(stub);
37 }
38 
39 //==============================================================================
44  : CoreSupervisorBase(stub)
45 {
46  __SUP_COUT__ << "Constructor started." << __E__;
47 
48  INIT_MF("." /*directory used is USER_DATA/LOG/.*/);
49 
50  // make macro directories in case they don't exist
51  mkdir(((std::string)ARTDAQTableBase::ARTDAQ_CONFIG_LAYOUTS_PATH).c_str(), 0755);
52 
53  init();
54  __SUP_COUT__ << "Constructor complete." << __E__;
55 } // end constructor()
56 
57 //==============================================================================
58 ConfigurationGUISupervisor::~ConfigurationGUISupervisor(void) { destroy(); }
59 
60 //==============================================================================
61 void ConfigurationGUISupervisor::init(void)
62 {
63  __SUP_COUT__ << "Initializing..." << __E__;
64 
65  try
66  {
67  __SUP_COUT__ << "Activating saved context, which may prepare for normal mode..."
68  << __E__;
69 
70  testXDAQContext(); // test context group activation
71 
72  __SUP_COUT__ << "Done with test context." << __E__;
73  }
74  catch(...)
75  {
76  __COUT_WARN__ << "Failed test context group activation. otsdaq, in Normal mode, "
77  "will not launch when this test fails. "
78  << "Check the active context group from within Wizard Mode."
79  << __E__;
80  }
81 
82  //initialize first config manager (pre-load for first user)
83  refreshUserSession("" /* userInfo.username_ */, 1); // (refresh == "1"));
84  //after this call, empty username : index=0 is in map userConfigurationManagers_[:0]
85 
86  if(CorePropertySupervisorBase::allSupervisorInfo_.isWizardMode())
87  {
88  __SUP_COUT_INFO__
89  << "After successful Config GUI init, marking alive for wiz mode!" << __E__;
90  CorePropertySupervisorBase::
91  indicateOtsAlive(); // no parameters for wiz mode indication
92  }
93 } // end init()
94 
95 //==============================================================================
96 void ConfigurationGUISupervisor::destroy(void)
97 {
98  __SUP_COUT__ << "Destructing..." << __E__;
99 
100  // called by destructor
101  for(std::map<std::string, ConfigurationManagerRW*>::iterator it =
102  userConfigurationManagers_.begin();
103  it != userConfigurationManagers_.end();
104  ++it)
105  {
106  delete it->second;
107  it->second = 0;
108  }
109  userConfigurationManagers_.clear();
110 
111  if(ConfigurationInterface::getInstance() != nullptr)
112  delete ConfigurationInterface::getInstance();
113 
114 } // end destroy()
115 
116 //==============================================================================
117 void ConfigurationGUISupervisor::defaultPage(xgi::Input* in, xgi::Output* out)
118 {
119  cgicc::Cgicc cgiIn(in);
120  std::string configWindowName =
121  CgiDataUtilities::getData(cgiIn, "configWindowName"); // from GET
122  if(configWindowName == "tableEditor")
123  *out << "<!DOCTYPE HTML><html lang='en'><frameset col='100%' row='100%'><frame "
124  "src='/WebPath/html/ConfigurationTableEditor.html?urn="
125  << this->getApplicationDescriptor()->getLocalId() << "'></frameset></html>";
126  if(configWindowName == "iterate")
127  *out << "<!DOCTYPE HTML><html lang='en'><frameset col='100%' row='100%'><frame "
128  "src='/WebPath/html/Iterate.html?urn="
129  << this->getApplicationDescriptor()->getLocalId() << "'></frameset></html>";
130  else
131  *out << "<!DOCTYPE HTML><html lang='en'><frameset col='100%' row='100%'><frame "
132  "src='/WebPath/html/ConfigurationGUI.html?urn="
133  << this->getApplicationDescriptor()->getLocalId() << "'></frameset></html>";
134 } // end defaultPage()
135 
136 //==============================================================================
140 {
141  CorePropertySupervisorBase::setSupervisorProperty(
142  CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.UserPermissionsThreshold,
143  "*=10 | deleteTreeNodeRecords=255 | saveTableInfo=255 | "
144  "deleteTableInfo=255"); // experienced users to edit, admins to delete
145 
146  CorePropertySupervisorBase::setSupervisorProperty(
147  CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.RequireUserLockRequestTypes,
148  "*"); // all
149 
150  //Allow get* AutomatedRequestTypes to enable read-only access to the Configuration Tree:
151  CorePropertySupervisorBase::setSupervisorProperty(
152  CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.AutomatedRequestTypes, "get*");
153 } // end setSupervisorPropertyDefaults()
154 
155 //==============================================================================
159 {
160  CorePropertySupervisorBase::addSupervisorProperty(
161  CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.AutomatedRequestTypes,
162  "getActiveTableGroups");
163 
164  //Allow get* to not require lock to enable read-only access to the Configuration Tree:
165  CorePropertySupervisorBase::setSupervisorProperty(
166  CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.CheckUserLockRequestTypes,
167  "!get*"); // all except read-only requests
168 } // end forceSupervisorPropertyValues()
169 
170 //==============================================================================
171 void ConfigurationGUISupervisor::request(const std::string& requestType,
172  cgicc::Cgicc& cgiIn,
173  HttpXmlDocument& xmlOut,
174  const WebUsers::RequestUserInfo& userInfo)
175 try
176 {
177  // Commands
178 
179  __COUTTV__(requestType);
180 
181  // gatewayLaunchOTS -- and other StartOTS commands
182 
183  // saveTableInfo
184  // deleteTableInfo
185  // flattenToSystemAliases
186  // versionTracking
187  // getColumnTypes
188  // getGroupAliases
189  // setGroupAliasInActiveBackbone
190  // setTableAliasInActiveBackbone
191  // setAliasOfGroupMembers
192  // getVersionAliases
193  // getTableGroups
194  // getTableGroupType
195  // getTables
196  // getContextMemberNames
197  // getBackboneMemberNames
198  // getIterateMemberNames
199  // getSpecificTableGroup
200  // saveNewTableGroup
201  // getSpecificTable
202  // saveSpecificTable
203  // clearTableTemporaryVersions
204  // clearTableCachedVersions
205  // getGroupHistory
206  //
207  // ---- associated with JavaScript Table API
208  // getTreeView
209  // getTreeNodeCommonFields
210  // getUniqueFieldValuesForRecords
211  // getTreeNodeFieldValues
212  // setTreeNodeFieldValues
213  // addTreeNodeRecords
214  // deleteTreeNodeRecords
215  // renameTreeNodeRecords
216  // copyTreeNodeRecords
217  // getTableStructureStatusAsJSON
218  // ---- end associated with JavaScript Table API
219  //
220  // ---- associated with JavaScript artdaq API
221  // getArtdaqNodes
222  // saveArtdaqNodes
223  // getArtdaqNodeLayout
224  // saveArtdaqNodeLayout
225  // ---- end associated with JavaScript artdaq API
226  //
227  // activateTableGroup
228  // getActiveTableGroups
229  // copyViewToCurrentColumns
230  // saveTreeNodeEdit
231  // checkAffectedActiveGroups (formerly 'getAffectedActiveGroups' but then was considered automated req, when it is more correctly associated with write tasks)
232  // getLinkToChoices
233  // getLastTableGroups
234  // getSubsytemTableGroups
235  // diffWithActiveGroup
236  // diffWithGroupKey
237  // diffTableVersions
238  // findGroupsWithTable
239  // SearchFieldInGroup
240  // SearchFieldInTableVersions
241  // mergeGroups
242  //
243  // ---- associated with JavaScript Iterate App
244  // savePlanCommandSequence
245  // ---- end associated with JavaScript Iterate App
246 
247  // acquire user's configuration manager based on username& activeSessionIndex
248  std::string refresh = CgiDataUtilities::getData(cgiIn, "refresh"); // from GET
249 
250  // refresh to reload from info files and db (maintains temporary views!)
251  ConfigurationManagerRW* cfgMgr =
252  refreshUserSession(userInfo.username_, (refresh == "1"));
253  __COUTTV__(userInfo.username_);
254  __COUTTV__(cfgMgr->getUsername());
255 
256  if(0) //for debugging/optimizing cache resets!
257  {
258  const GroupInfo& groupInfo = cfgMgr->getGroupInfo("MC2TriggerContext");
259  const std::set<TableGroupKey>& sortedKeys = groupInfo.getKeys(); // rename
260  __COUTTV__(sortedKeys.size());
261  }
262 
263  if(requestType == "saveTableInfo")
264  {
265  std::string tableName =
266  CgiDataUtilities::getData(cgiIn, "tableName"); // from GET
267  std::string columnCSV =
268  CgiDataUtilities::postData(cgiIn, "columnCSV"); // from POST
269  std::string allowOverwrite =
270  CgiDataUtilities::getData(cgiIn, "allowOverwrite"); // from GET
271  std::string tableDescription =
272  CgiDataUtilities::postData(cgiIn, "tableDescription"); // from POST
273  std::string columnChoicesCSV =
274  CgiDataUtilities::postData(cgiIn, "columnChoicesCSV"); // from POST
275 
276  __SUP_COUT__ << "tableName: " << tableName << __E__;
277  __SUP_COUT__ << "columnCSV: " << columnCSV << __E__;
278  __SUP_COUT__ << "tableDescription: " << tableDescription << __E__;
279  __SUP_COUT__ << "columnChoicesCSV: " << columnChoicesCSV << __E__;
280  __SUP_COUT__ << "allowOverwrite: " << allowOverwrite << __E__;
281 
282  if(!allSupervisorInfo_.isWizardMode())
283  {
284  __SUP_SS__ << "Improper permissions for saving table info." << __E__;
285  xmlOut.addTextElementToData("Error", ss.str());
286  }
287  else
288  handleSaveTableInfoXML(xmlOut,
289  cfgMgr,
290  tableName,
291  columnCSV,
292  tableDescription,
293  columnChoicesCSV,
294  allowOverwrite == "1");
295  }
296  else if(requestType == "deleteTableInfo")
297  {
298  std::string tableName =
299  CgiDataUtilities::getData(cgiIn, "tableName"); // from GET
300  __SUP_COUT__ << "tableName: " << tableName << __E__;
301  handleDeleteTableInfoXML(xmlOut, cfgMgr, tableName);
302  }
303  else if(requestType == "gatewayLaunchOTS" || requestType == "gatewayLaunchWiz" ||
304  requestType == "flattenToSystemAliases")
305  {
306  // NOTE: similar to Supervisor version but does not keep active sessions
307  __SUP_COUT_WARN__ << requestType << " command received! " << __E__;
308  __COUT_WARN__ << requestType << " command received! " << __E__;
309 
310  // now launch
311  __SUP_COUT_INFO__ << "Launching " << requestType << "... " << __E__;
312 
313  __SUP_COUT__ << "Extracting target context hostnames... " << __E__;
314  std::vector<std::string> hostnames;
315 
316  // flattenToSystemAliases should always work in wiz mode!
317  if(requestType == "flattenToSystemAliases" &&
318  CorePropertySupervisorBase::allSupervisorInfo_.isWizardMode())
319  {
320  hostnames.push_back(__ENV__("OTS_CONFIGURATION_WIZARD_SUPERVISOR_SERVER"));
321  __SUP_COUT__ << "hostname = " << hostnames.back() << __E__;
322  }
323  else
324  {
325  try
326  {
327  cfgMgr->init(); // completely reset to re-align with any changes
328 
329  const XDAQContextTable* contextTable =
330  cfgMgr->__GET_CONFIG__(XDAQContextTable);
331 
332  auto contexts = contextTable->getContexts();
333  unsigned int i, j;
334  for(const auto& context : contexts)
335  {
336  if(!context.status_)
337  continue;
338 
339  // find last slash
340  j = 0; // default to whole string
341  for(i = 0; i < context.address_.size(); ++i)
342  if(context.address_[i] == '/')
343  j = i + 1;
344  hostnames.push_back(context.address_.substr(j));
345  __SUP_COUT__ << "hostname = " << hostnames.back() << __E__;
346  }
347  }
348  catch(...)
349  {
350  __SUP_SS__ << "The Configuration Manager could not be initialized to "
351  "extract contexts."
352  << __E__;
353  try
354  {
355  throw;
356  } //one more try to printout extra info
357  catch(const std::exception& e)
358  {
359  ss << "Exception message: " << e.what();
360  }
361  catch(...)
362  {
363  }
364 
365  __SUP_COUT_ERR__ << "\n" << ss.str();
366  return;
367  }
368  }
369 
370  if(hostnames.size() == 0)
371  {
372  __SUP_SS__ << "No hostnames found to launch command '" + requestType +
373  "'... Is there a valid Context group activated?"
374  << __E__;
375  __SUP_COUT_ERR__ << "\n" << ss.str();
376 
377  xmlOut.addTextElementToData("Error", ss.str());
378  }
379 
380  for(const auto& hostname : hostnames)
381  {
382  std::string fn = (std::string(__ENV__("SERVICE_DATA_PATH")) +
383  "/StartOTS_action_" + hostname + ".cmd");
384  FILE* fp = fopen(fn.c_str(), "w");
385  if(fp)
386  {
387  if(requestType == "gatewayLaunchOTS")
388  fprintf(fp, "LAUNCH_OTS");
389  else if(requestType == "gatewayLaunchWiz")
390  fprintf(fp, "LAUNCH_WIZ");
391  else if(requestType == "flattenToSystemAliases")
392  {
393  fprintf(fp, "FLATTEN_TO_SYSTEM_ALIASES");
394  fclose(fp);
395  break; // only do at one host
396  }
397 
398  fclose(fp);
399  }
400  else
401  __SUP_COUT_ERR__ << "Unable to open command file: " << fn << __E__;
402  }
403  }
404  else if(requestType == "versionTracking" || requestType == "getVersionTracking")
405  {
406  std::string type;
407  if(requestType == "getVersionTracking")
408  type = "Get";
409  else
410  type = CgiDataUtilities::getData(cgiIn, "Type"); // from GET
411  __SUP_COUT__ << "type: " << type << __E__;
412 
413  if(type == "Get")
414  xmlOut.addTextElementToData(
415  "versionTrackingStatus",
416  ConfigurationInterface::isVersionTrackingEnabled() ? "ON" : "OFF");
417  else if(type == "ON")
418  {
419  ConfigurationInterface::setVersionTrackingEnabled(true);
420  xmlOut.addTextElementToData(
421  "versionTrackingStatus",
422  ConfigurationInterface::isVersionTrackingEnabled() ? "ON" : "OFF");
423  }
424  else if(type == "OFF")
425  {
426  ConfigurationInterface::setVersionTrackingEnabled(false);
427  xmlOut.addTextElementToData(
428  "versionTrackingStatus",
429  ConfigurationInterface::isVersionTrackingEnabled() ? "ON" : "OFF");
430  }
431  }
432  else if(requestType == "getColumnTypes")
433  {
434  // return the possible column types and their defaults
435  std::vector<std::string> allTypes = TableViewColumnInfo::getAllTypesForGUI();
436  std::vector<std::string> allDataTypes =
437  TableViewColumnInfo::getAllDataTypesForGUI();
438  std::map<std::pair<std::string, std::string>, std::string> allDefaults =
440  // TODO maybe here a new function will be needed to get allminmaxforGUI
441  for(const auto& type : allTypes)
442  xmlOut.addTextElementToData("columnTypeForGUI", type);
443  for(const auto& dataType : allDataTypes)
444  xmlOut.addTextElementToData("columnDataTypeForGUI", dataType);
445 
446  for(const auto& colDefault : allDefaults)
447  {
448  xmlOut.addTextElementToData("columnDefaultDataType", colDefault.first.first);
449  xmlOut.addTextElementToData("columnDefaultTypeFilter",
450  colDefault.first.second);
451  xmlOut.addTextElementToData("columnDefaultValue", colDefault.second);
452  }
453  // TODO add min and max responses.
454  }
455  else if(requestType == "getGroupAliases")
456  {
457  // Since this is called from setting up System View in the table GUI
458  // give option for reloading "persistent" active configurations
459  bool reloadActive =
460  1 == CgiDataUtilities::getDataAsInt(cgiIn, "reloadActiveGroups"); // from GET
461 
462  __SUP_COUT__ << "reloadActive: " << reloadActive << __E__;
463  if(reloadActive)
464  {
465  try
466  {
467  cfgMgr->clearAllCachedVersions();
468  cfgMgr->restoreActiveTableGroups(true);
469  }
470  catch(std::runtime_error& e)
471  {
472  __SUP_SS__ << ("Error loading active groups!\n\n" + std::string(e.what()))
473  << __E__;
474  __SUP_COUT_ERR__ << "\n" << ss.str();
475  xmlOut.addTextElementToData("Error", ss.str());
476  }
477  catch(...)
478  {
479  __SUP_SS__ << ("Error loading active groups!\n\n") << __E__;
480  try
481  {
482  throw;
483  } //one more try to printout extra info
484  catch(const std::exception& e)
485  {
486  ss << "Exception message: " << e.what();
487  }
488  catch(...)
489  {
490  }
491  __SUP_COUT_ERR__ << "\n" << ss.str();
492  xmlOut.addTextElementToData("Error", ss.str());
493  }
494  }
495 
496  handleGroupAliasesXML(xmlOut, cfgMgr);
497  }
498  else if(requestType == "setGroupAliasInActiveBackbone")
499  {
500  std::string groupAliasCSV =
501  CgiDataUtilities::getData(cgiIn, "groupAlias"); // from GET
502  std::string groupNameCSV =
503  CgiDataUtilities::getData(cgiIn, "groupName"); // from GET
504  std::string groupKeyCSV =
505  CgiDataUtilities::getData(cgiIn, "groupKey"); // from GET
506 
507  __SUP_COUTV__(groupAliasCSV);
508  __SUP_COUTV__(groupNameCSV);
509  __SUP_COUTV__(groupKeyCSV);
510 
511  handleSetGroupAliasInBackboneXML(
512  xmlOut, cfgMgr, groupAliasCSV, groupNameCSV, groupKeyCSV, userInfo.username_);
513  }
514  else if(requestType == "setTableAliasInActiveBackbone")
515  {
516  std::string tableAlias =
517  CgiDataUtilities::getData(cgiIn, "tableAlias"); // from GET
518  std::string tableName =
519  CgiDataUtilities::getData(cgiIn, "tableName"); // from GET
520  std::string version = CgiDataUtilities::getData(cgiIn, "version"); // from GET
521 
522  __SUP_COUT__ << "tableAlias: " << tableAlias << __E__;
523  __SUP_COUT__ << "tableName: " << tableName << __E__;
524  __SUP_COUT__ << "version: " << version << __E__;
525 
526  handleSetTableAliasInBackboneXML(xmlOut,
527  cfgMgr,
528  tableAlias,
529  tableName,
530  TableVersion(version),
531  userInfo.username_);
532  }
533  else if(requestType == "setAliasOfGroupMembers")
534  {
535  std::string versionAlias =
536  CgiDataUtilities::getData(cgiIn, "versionAlias"); // from GET
537  std::string groupName =
538  CgiDataUtilities::getData(cgiIn, "groupName"); // from GET
539  std::string groupKey = CgiDataUtilities::getData(cgiIn, "groupKey"); // from GET
540 
541  __SUP_COUT__ << "versionAlias: " << versionAlias << __E__;
542  __SUP_COUT__ << "groupName: " << groupName << __E__;
543  __SUP_COUT__ << "groupKey: " << groupKey << __E__;
544 
545  handleAliasGroupMembersInBackboneXML(xmlOut,
546  cfgMgr,
547  versionAlias,
548  groupName,
549  TableGroupKey(groupKey),
550  userInfo.username_);
551  }
552  else if(requestType == "getVersionAliases")
553  {
554  handleVersionAliasesXML(xmlOut, cfgMgr);
555  }
556  else if(requestType == "getTableGroups")
557  {
558  bool doNotReturnMembers =
559  CgiDataUtilities::getDataAsInt(cgiIn, "doNotReturnMembers") == 1
560  ? true
561  : false; // from GET
562 
563  __SUP_COUT__ << "doNotReturnMembers: " << doNotReturnMembers << __E__;
564  handleTableGroupsXML(xmlOut, cfgMgr, !doNotReturnMembers);
565  }
566  else if(requestType == "getTableGroupType")
567  {
568  std::string tableList =
569  CgiDataUtilities::postData(cgiIn, "tableList"); // from POST
570  __SUP_COUT__ << "tableList: " << tableList << __E__;
571 
572  handleGetTableGroupTypeXML(xmlOut, cfgMgr, tableList);
573  }
574  else if(requestType == "getTables")
575  {
576  std::string filterStartTime = CgiDataUtilities::getData(cgiIn, "startTime");
577  std::string filterEndTime = CgiDataUtilities::getData(cgiIn, "endTime");
578  std::string filterMode = CgiDataUtilities::getData(cgiIn, "filterMode");
579  if(filterMode == "")
580  filterMode = "created";
581  __COUT__ << "startTime: " << filterStartTime << __E__;
582  __COUT__ << "endTime: " << filterEndTime << __E__;
583  __COUT__ << "filterMode: " << filterMode << __E__;
584 
585  handleTablesXML(xmlOut, cfgMgr, filterStartTime, filterEndTime, filterMode);
586  }
587  else if(requestType == "getContextMemberNames")
588  {
589  std::set<std::string> members = cfgMgr->getFixedContextMemberNames();
590 
591  for(auto& member : members)
592  xmlOut.addTextElementToData("ContextMember", member);
593  }
594  else if(requestType == "getBackboneMemberNames")
595  {
596  std::set<std::string> members = cfgMgr->getBackboneMemberNames();
597 
598  for(auto& member : members)
599  xmlOut.addTextElementToData("BackboneMember", member);
600  }
601  else if(requestType == "getIterateMemberNames")
602  {
603  std::set<std::string> members = cfgMgr->getIterateMemberNames();
604 
605  for(auto& member : members)
606  xmlOut.addTextElementToData("IterateMember", member);
607  }
608  else if(requestType == "getSpecificTableGroup")
609  {
610  std::string groupName =
611  CgiDataUtilities::getData(cgiIn, "groupName"); // from GET
612  std::string groupKey = CgiDataUtilities::getData(cgiIn, "groupKey"); // from GET
613 
614  __SUP_COUT__ << "groupName: " << groupName << __E__;
615  __SUP_COUT__ << "groupKey: " << groupKey << __E__;
616 
618  xmlOut, cfgMgr, groupName, TableGroupKey(groupKey));
619  }
620  else if(requestType == "saveNewTableGroup")
621  {
622  std::string groupName =
623  CgiDataUtilities::getData(cgiIn, "groupName"); // from GET
624  bool ignoreWarnings =
625  CgiDataUtilities::getDataAsInt(cgiIn, "ignoreWarnings"); // from GET
626  bool allowDuplicates =
627  CgiDataUtilities::getDataAsInt(cgiIn, "allowDuplicates"); // from GET
628  bool lookForEquivalent =
629  CgiDataUtilities::getDataAsInt(cgiIn, "lookForEquivalent"); // from GET
630  std::string tableList =
631  CgiDataUtilities::postData(cgiIn, "tableList"); // from POST
632  std::string comment =
633  CgiDataUtilities::getData(cgiIn, "groupComment"); // from GET
634 
635  __SUP_COUT__ << "saveNewTableGroup: " << groupName << __E__;
636  __SUP_COUT__ << "tableList: " << tableList << __E__;
637  __SUP_COUT__ << "ignoreWarnings: " << ignoreWarnings << __E__;
638  __SUP_COUT__ << "allowDuplicates: " << allowDuplicates << __E__;
639  __SUP_COUT__ << "lookForEquivalent: " << lookForEquivalent << __E__;
640  __SUP_COUT__ << "comment: " << comment << __E__;
641 
643  cfgMgr,
644  groupName,
645  tableList,
646  allowDuplicates,
647  ignoreWarnings,
648  comment,
649  lookForEquivalent);
650  }
651  else if(requestType == "getSpecificTable")
652  {
653  std::string tableName =
654  CgiDataUtilities::getData(cgiIn, "tableName"); // from GET
655  std::string versionStr = CgiDataUtilities::getData(cgiIn, "version"); // from GET
656  int dataOffset = CgiDataUtilities::getDataAsInt(cgiIn, "dataOffset"); // from GET
657  int chunkSize = CgiDataUtilities::getDataAsInt(
658  cgiIn,
659  "chunkSize"); // from GET (chunkSize is currently ignored, could use to get a few rows at a time)
660  bool descriptionOnly = CgiDataUtilities::getDataAsInt(
661  cgiIn, "descriptionOnly"); // from GET (used to get tooltip info)
662  bool allowIllegalColumns =
663  CgiDataUtilities::getDataAsInt(cgiIn, "allowIllegalColumns"); // from GET
664  bool rawData = CgiDataUtilities::getDataAsInt(cgiIn, "rawData"); // from GET
665 
666  __SUP_COUT__ << "getSpecificTable: " << tableName << " versionStr: " << versionStr
667  << " chunkSize: " << chunkSize << " dataOffset: " << dataOffset
668  << " descriptionOnly: " << descriptionOnly
669  << " allowIllegalColumns: " << allowIllegalColumns
670  << " rawData: " << rawData << __E__;
671 
672  TableVersion version;
673  const std::map<std::string, TableInfo>& allTableInfo = cfgMgr->getAllTableInfo();
674 
675  if(allTableInfo.find(tableName) != allTableInfo.end())
676  {
677  if(versionStr == "" && // take latest version if no version specified
678  allTableInfo.at(tableName).versions_.size())
679  {
680  // Start from the last element
681  auto it = allTableInfo.at(tableName).versions_.rbegin();
682  if(it->isScratchVersion()) //do not allow SCRATCH_VERSION as default selection
683  ++it; // Move to the second-to-last element
684  version = *it;
685  }
686  else if(versionStr.find(ConfigurationManager::ALIAS_VERSION_PREAMBLE) == 0)
687  {
688  // convert alias to version
689  std::map<std::string /*table*/,
690  std::map<std::string /*alias*/, TableVersion>>
691  versionAliases = cfgMgr->getVersionAliases();
692 
693  std::string versionAlias;
694  versionAlias = versionStr.substr(
695  ConfigurationManager::ALIAS_VERSION_PREAMBLE.size());
696  // if(versionAlias ==
697  // ConfigurationManager::SCRATCH_VERSION_ALIAS)
699  // {
700  // version = TableVersion::SCRATCH;
701  // __SUP_COUT__ << "version alias translated to: " << version
702  //<<
703  //__E__;
704  // }
705  // else
706  if(versionAliases.find(tableName) != versionAliases.end() &&
707  versionAliases[tableName].find(versionAlias) !=
708  versionAliases[tableName].end())
709  {
710  version = versionAliases[tableName][versionAlias];
711  __SUP_COUT__ << "version alias translated to: " << version << __E__;
712  }
713  else
714  __SUP_COUT_WARN__
715  << "version alias '"
716  << versionStr.substr(
717  ConfigurationManager::ALIAS_VERSION_PREAMBLE.size())
718  << "'was not found in active version aliases!" << __E__;
719  }
720  else // else take specified version
721  version = atoi(versionStr.c_str());
722  }
723 
724  __SUP_COUT__ << "version: " << version << __E__;
725 
726  handleGetTableXML(xmlOut,
727  cfgMgr,
728  tableName,
729  TableVersion(version),
730  allowIllegalColumns,
731  rawData,
732  descriptionOnly);
733  // append author column default value
734  xmlOut.addTextElementToData("DefaultRowValue", userInfo.username_);
735  }
736  else if(requestType == "saveSpecificTable")
737  {
738  std::string tableName =
739  CgiDataUtilities::getData(cgiIn, "tableName"); // from GET
740  int version = CgiDataUtilities::getDataAsInt(cgiIn, "version"); // from GET
741  int dataOffset = CgiDataUtilities::getDataAsInt(cgiIn, "dataOffset"); // from GET
742  bool sourceTableAsIs =
743  CgiDataUtilities::getDataAsInt(cgiIn, "sourceTableAsIs"); // from GET
744  bool lookForEquivalent =
745  CgiDataUtilities::getDataAsInt(cgiIn, "lookForEquivalent"); // from GET
746  int temporary = CgiDataUtilities::getDataAsInt(cgiIn, "temporary"); // from GET
747  std::string comment =
748  CgiDataUtilities::getData(cgiIn, "tableComment"); // from GET
749 
750  std::string data = CgiDataUtilities::postData(cgiIn, "data"); // from POST
751  // data format: commas and semi-colons indicate new row
752  // r0c0,r0c1,...,r0cN,;r1c0,...
753 
754  __SUP_COUT__ << "tableName: " << tableName << " version: " << version
755  << " temporary: " << temporary << " dataOffset: " << dataOffset
756  << __E__;
757  __SUP_COUT__ << "comment: " << comment << __E__;
758  __SUP_COUT__ << "data: " << data << __E__;
759  __SUP_COUT__ << "sourceTableAsIs: " << sourceTableAsIs << __E__;
760  __SUP_COUT__ << "lookForEquivalent: " << lookForEquivalent << __E__;
761 
763  cfgMgr,
764  tableName,
765  TableVersion(version),
766  temporary,
767  data,
768  dataOffset,
769  userInfo.username_,
770  comment,
771  sourceTableAsIs,
772  lookForEquivalent);
773  }
774  else if(requestType == "clearTableTemporaryVersions")
775  {
776  std::string tableName =
777  CgiDataUtilities::getData(cgiIn, "tableName"); // from GET
778  __SUP_COUT__ << "tableName: " << tableName << __E__;
779 
780  try
781  {
782  cfgMgr->eraseTemporaryVersion(tableName);
783  }
784  catch(std::runtime_error& e)
785  {
786  __SUP_COUT__ << "Error detected!\n\n " << e.what() << __E__;
787  xmlOut.addTextElementToData(
788  "Error", "Error clearing temporary views!\n " + std::string(e.what()));
789  }
790  catch(...)
791  {
792  __SUP_COUT__ << "Error detected!\n\n " << __E__;
793  xmlOut.addTextElementToData("Error", "Error clearing temporary views! ");
794  }
795  }
796  else if(requestType == "clearTableCachedVersions")
797  {
798  std::string tableName =
799  CgiDataUtilities::getData(cgiIn, "tableName"); // from GET
800  __SUP_COUT__ << "tableName: " << tableName << __E__;
801 
802  try
803  {
804  if(tableName == "*")
805  cfgMgr->clearAllCachedVersions();
806  else
807  cfgMgr->clearCachedVersions(tableName);
808 
809  // Force manual reload... not cfgMgr->getAllTableInfo(true /*refresh*/);
810  }
811  catch(std::runtime_error& e)
812  {
813  __SUP_COUT__ << "Error detected!\n\n " << e.what() << __E__;
814  xmlOut.addTextElementToData(
815  "Error", "Error clearing cached views!\n " + std::string(e.what()));
816  }
817  catch(...)
818  {
819  __SUP_COUT__ << "Error detected!\n\n " << __E__;
820  xmlOut.addTextElementToData("Error", "Error clearing cached views! ");
821  }
822  }
823  else if(requestType == "getGroupHistory")
824  {
825  std::string groupAction = StringMacros::decodeURIComponent(
826  CgiDataUtilities::getData(cgiIn, "groupAction")); // from GET
827  std::string groupType = StringMacros::decodeURIComponent(
828  CgiDataUtilities::getData(cgiIn, "groupType")); // from GET
829 
830  __SUP_COUTV__(groupAction);
831  __SUP_COUTV__(groupType);
832 
833  std::vector<std::map<std::string /* group field key */,
834  std::string /* group field value */>>
836  groupAction, groupType, true /* formatTime */);
837 
838  for(const auto& group : groups)
839  {
840  auto parentEl = xmlOut.addTextElementToData(
841  "GroupHistoryEntry", ""); // create parent element for each entry
842  for(const auto& field : group)
843  xmlOut.addTextElementToParent(field.first, field.second, parentEl);
844  }
845  }
846  else if(requestType == "getTreeView")
847  {
848  std::string tableGroup = CgiDataUtilities::getData(cgiIn, "tableGroup");
849  std::string tableGroupKey = CgiDataUtilities::getData(cgiIn, "tableGroupKey");
850  std::string startPath = CgiDataUtilities::postData(cgiIn, "startPath");
851  std::string modifiedTables = CgiDataUtilities::postData(cgiIn, "modifiedTables");
852  std::string filterList = CgiDataUtilities::postData(cgiIn, "filterList");
853  int depth = CgiDataUtilities::getDataAsInt(cgiIn, "depth");
854  bool hideStatusFalse = CgiDataUtilities::getDataAsInt(cgiIn, "hideStatusFalse");
855  std::string diffGroup = CgiDataUtilities::getData(cgiIn, "diffGroup");
856  std::string diffGroupKey = CgiDataUtilities::getData(cgiIn, "diffGroupKey");
857 
858  __SUP_COUTT__ << "tableGroup: " << tableGroup << __E__;
859  __SUP_COUTT__ << "tableGroupKey: " << tableGroupKey << __E__;
860  __SUP_COUTT__ << "startPath: " << startPath << __E__;
861  __SUP_COUTT__ << "depth: " << depth << __E__;
862  __SUP_COUTT__ << "hideStatusFalse: " << hideStatusFalse << __E__;
863  __SUP_COUTT__ << "modifiedTables: " << modifiedTables << __E__;
864  __SUP_COUTT__ << "filterList: " << filterList << __E__;
865 
866  handleFillTreeViewXML(xmlOut,
867  cfgMgr,
868  tableGroup,
869  TableGroupKey(tableGroupKey),
870  startPath,
871  depth,
872  hideStatusFalse,
873  modifiedTables,
874  filterList,
875  diffGroup,
876  TableGroupKey(diffGroupKey));
877  }
878  else if(requestType == "getTreeNodeCommonFields")
879  {
880  std::string tableGroup = CgiDataUtilities::getData(cgiIn, "tableGroup");
881  std::string tableGroupKey = CgiDataUtilities::getData(cgiIn, "tableGroupKey");
882  std::string startPath = CgiDataUtilities::postData(cgiIn, "startPath");
883  std::string modifiedTables = CgiDataUtilities::postData(cgiIn, "modifiedTables");
884  std::string fieldList = CgiDataUtilities::postData(cgiIn, "fieldList");
885  std::string recordList = CgiDataUtilities::postData(cgiIn, "recordList");
886  int depth = CgiDataUtilities::getDataAsInt(cgiIn, "depth");
887 
888  __SUP_COUT__ << "tableGroup: " << tableGroup << __E__;
889  __SUP_COUT__ << "tableGroupKey: " << tableGroupKey << __E__;
890  __SUP_COUT__ << "startPath: " << startPath << __E__;
891  __SUP_COUT__ << "depth: " << depth << __E__;
892  if(depth == -1)
893  depth = 10; // protect users who probably do not actually mean -1
894  __SUP_COUT__ << "fieldList: " << fieldList << __E__;
895  __SUP_COUT__ << "recordList: " << recordList << __E__;
896  __SUP_COUT__ << "modifiedTables: " << modifiedTables << __E__;
897 
898  handleFillTreeNodeCommonFieldsXML(xmlOut,
899  cfgMgr,
900  tableGroup,
901  TableGroupKey(tableGroupKey),
902  startPath,
903  depth,
904  modifiedTables,
905  recordList,
906  fieldList);
907  }
908  else if(requestType == "getUniqueFieldValuesForRecords")
909  {
910  std::string tableGroup = CgiDataUtilities::getData(cgiIn, "tableGroup");
911  std::string tableGroupKey = CgiDataUtilities::getData(cgiIn, "tableGroupKey");
912  std::string startPath = CgiDataUtilities::postData(cgiIn, "startPath");
913  std::string modifiedTables = CgiDataUtilities::postData(cgiIn, "modifiedTables");
914  std::string fieldList = CgiDataUtilities::postData(cgiIn, "fieldList");
915  std::string recordList = CgiDataUtilities::postData(cgiIn, "recordList");
916 
917  __SUP_COUT__ << "tableGroup: " << tableGroup << __E__;
918  __SUP_COUT__ << "tableGroupKey: " << tableGroupKey << __E__;
919  __SUP_COUT__ << "startPath: " << startPath << __E__;
920  __SUP_COUT__ << "fieldList: " << fieldList << __E__;
921  __SUP_COUT__ << "recordList: " << recordList << __E__;
922  __SUP_COUT__ << "modifiedTables: " << modifiedTables << __E__;
923 
924  handleFillUniqueFieldValuesForRecordsXML(xmlOut,
925  cfgMgr,
926  tableGroup,
927  TableGroupKey(tableGroupKey),
928  startPath,
929  modifiedTables,
930  recordList,
931  fieldList);
932  }
933  else if(requestType == "getTreeNodeFieldValues")
934  {
935  std::string tableGroup = CgiDataUtilities::getData(cgiIn, "tableGroup");
936  std::string tableGroupKey = CgiDataUtilities::getData(cgiIn, "tableGroupKey");
937  std::string startPath = CgiDataUtilities::postData(cgiIn, "startPath");
938  std::string modifiedTables = CgiDataUtilities::postData(cgiIn, "modifiedTables");
939  std::string fieldList = CgiDataUtilities::postData(cgiIn, "fieldList");
940  std::string recordList = CgiDataUtilities::postData(cgiIn, "recordList");
941 
942  __SUP_COUT__ << "tableGroup: " << tableGroup << __E__;
943  __SUP_COUT__ << "tableGroupKey: " << tableGroupKey << __E__;
944  __SUP_COUT__ << "startPath: " << startPath << __E__;
945  __SUP_COUT__ << "fieldList: " << fieldList << __E__;
946  __SUP_COUT__ << "recordList: " << recordList << __E__;
947  __SUP_COUT__ << "modifiedTables: " << modifiedTables << __E__;
948 
949  handleFillGetTreeNodeFieldValuesXML(xmlOut,
950  cfgMgr,
951  tableGroup,
952  TableGroupKey(tableGroupKey),
953  startPath,
954  modifiedTables,
955  recordList,
956  fieldList);
957  }
958  else if(requestType == "setTreeNodeFieldValues")
959  {
960  std::string tableGroup = CgiDataUtilities::getData(cgiIn, "tableGroup");
961  std::string tableGroupKey = CgiDataUtilities::getData(cgiIn, "tableGroupKey");
962  std::string startPath = CgiDataUtilities::postData(cgiIn, "startPath");
963  std::string modifiedTables = CgiDataUtilities::postData(cgiIn, "modifiedTables");
964  std::string fieldList = CgiDataUtilities::postData(cgiIn, "fieldList");
965  std::string recordList = CgiDataUtilities::postData(cgiIn, "recordList");
966  std::string valueList = CgiDataUtilities::postData(cgiIn, "valueList");
967 
968  __SUP_COUT__ << "tableGroup: " << tableGroup << __E__;
969  __SUP_COUT__ << "tableGroupKey: " << tableGroupKey << __E__;
970  __SUP_COUT__ << "startPath: " << startPath << __E__;
971  __SUP_COUT__ << "fieldList: " << fieldList << __E__;
972  __SUP_COUT__ << "valueList: " << valueList << __E__;
973  __SUP_COUT__ << "recordList: " << recordList << __E__;
974  __SUP_COUT__ << "modifiedTables: " << modifiedTables << __E__;
975 
976  handleFillSetTreeNodeFieldValuesXML(xmlOut,
977  cfgMgr,
978  tableGroup,
979  TableGroupKey(tableGroupKey),
980  startPath,
981  modifiedTables,
982  recordList,
983  fieldList,
984  valueList,
985  userInfo.username_);
986  }
987  else if(requestType == "addTreeNodeRecords")
988  {
989  std::string tableGroup = CgiDataUtilities::getData(cgiIn, "tableGroup");
990  std::string tableGroupKey = CgiDataUtilities::getData(cgiIn, "tableGroupKey");
991  std::string startPath = CgiDataUtilities::postData(cgiIn, "startPath");
992  std::string modifiedTables = CgiDataUtilities::postData(cgiIn, "modifiedTables");
993  std::string recordList = CgiDataUtilities::postData(cgiIn, "recordList");
994 
995  __SUP_COUT__ << "tableGroup: " << tableGroup << __E__;
996  __SUP_COUT__ << "tableGroupKey: " << tableGroupKey << __E__;
997  __SUP_COUT__ << "startPath: " << startPath << __E__;
998  __SUP_COUT__ << "recordList: " << recordList << __E__;
999  __SUP_COUT__ << "modifiedTables: " << modifiedTables << __E__;
1000 
1001  handleFillCreateTreeNodeRecordsXML(xmlOut,
1002  cfgMgr,
1003  tableGroup,
1004  TableGroupKey(tableGroupKey),
1005  startPath,
1006  modifiedTables,
1007  recordList,
1008  userInfo.username_);
1009  }
1010  else if(requestType == "deleteTreeNodeRecords")
1011  {
1012  std::string tableGroup = CgiDataUtilities::getData(cgiIn, "tableGroup");
1013  std::string tableGroupKey = CgiDataUtilities::getData(cgiIn, "tableGroupKey");
1014  std::string startPath = CgiDataUtilities::postData(cgiIn, "startPath");
1015  std::string modifiedTables = CgiDataUtilities::postData(cgiIn, "modifiedTables");
1016  std::string recordList = CgiDataUtilities::postData(cgiIn, "recordList");
1017 
1018  __SUP_COUT__ << "tableGroup: " << tableGroup << __E__;
1019  __SUP_COUT__ << "tableGroupKey: " << tableGroupKey << __E__;
1020  __SUP_COUT__ << "startPath: " << startPath << __E__;
1021  __SUP_COUT__ << "recordList: " << recordList << __E__;
1022  __SUP_COUT__ << "modifiedTables: " << modifiedTables << __E__;
1023 
1024  handleFillDeleteTreeNodeRecordsXML(xmlOut,
1025  cfgMgr,
1026  tableGroup,
1027  TableGroupKey(tableGroupKey),
1028  startPath,
1029  modifiedTables,
1030  recordList);
1031  }
1032  else if(requestType == "renameTreeNodeRecords")
1033  {
1034  std::string tableGroup = CgiDataUtilities::getData(cgiIn, "tableGroup");
1035  std::string tableGroupKey = CgiDataUtilities::getData(cgiIn, "tableGroupKey");
1036  std::string startPath = CgiDataUtilities::postData(cgiIn, "startPath");
1037  std::string modifiedTables = CgiDataUtilities::postData(cgiIn, "modifiedTables");
1038  std::string recordList = CgiDataUtilities::postData(cgiIn, "recordList");
1039  std::string newRecordList = CgiDataUtilities::postData(cgiIn, "newRecordList");
1040 
1041  __SUP_COUT__ << "tableGroup: " << tableGroup << __E__;
1042  __SUP_COUT__ << "tableGroupKey: " << tableGroupKey << __E__;
1043  __SUP_COUT__ << "startPath: " << startPath << __E__;
1044  __SUP_COUT__ << "recordList: " << recordList << __E__;
1045  __SUP_COUT__ << "modifiedTables: " << modifiedTables << __E__;
1046  __SUP_COUTV__(newRecordList);
1047 
1048  handleFillRenameTreeNodeRecordsXML(xmlOut,
1049  cfgMgr,
1050  tableGroup,
1051  TableGroupKey(tableGroupKey),
1052  startPath,
1053  modifiedTables,
1054  recordList,
1055  newRecordList);
1056  }
1057  else if(requestType == "copyTreeNodeRecords")
1058  {
1059  std::string tableGroup = CgiDataUtilities::getData(cgiIn, "tableGroup");
1060  std::string tableGroupKey = CgiDataUtilities::getData(cgiIn, "tableGroupKey");
1061  std::string startPath = CgiDataUtilities::postData(cgiIn, "startPath");
1062  std::string modifiedTables = CgiDataUtilities::postData(cgiIn, "modifiedTables");
1063  std::string recordList = CgiDataUtilities::postData(cgiIn, "recordList");
1064  unsigned int numberOfCopies =
1065  CgiDataUtilities::getDataAsInt(cgiIn, "numberOfCopies");
1066  if(!numberOfCopies)
1067  numberOfCopies = 1; // default to 1
1068 
1069  __SUP_COUT__ << "tableGroup: " << tableGroup << __E__;
1070  __SUP_COUT__ << "tableGroupKey: " << tableGroupKey << __E__;
1071  __SUP_COUT__ << "startPath: " << startPath << __E__;
1072  __SUP_COUT__ << "recordList: " << recordList << __E__;
1073  __SUP_COUT__ << "modifiedTables: " << modifiedTables << __E__;
1074  __SUP_COUTV__(numberOfCopies);
1075 
1076  handleFillCopyTreeNodeRecordsXML(xmlOut,
1077  cfgMgr,
1078  tableGroup,
1079  TableGroupKey(tableGroupKey),
1080  startPath,
1081  modifiedTables,
1082  recordList,
1083  numberOfCopies);
1084  }
1085  else if(requestType == "getTableStructureStatusAsJSON")
1086  {
1087  std::string tableGroup = CgiDataUtilities::getData(cgiIn, "tableGroup");
1088  std::string tableGroupKey = CgiDataUtilities::getData(cgiIn, "tableGroupKey");
1089  std::string tableName = CgiDataUtilities::getData(cgiIn, "tableName");
1090  std::string modifiedTables = CgiDataUtilities::postData(cgiIn, "modifiedTables");
1091 
1092  __SUP_COUT__ << "tableGroup: " << tableGroup << __E__;
1093  __SUP_COUT__ << "tableGroupKey: " << tableGroupKey << __E__;
1094  __SUP_COUT__ << "tableName: " << tableName << __E__;
1095  __SUP_COUT__ << "modifiedTables: " << modifiedTables << __E__;
1096 
1097  // setup active tables based on active groups and modified tables
1098  setupActiveTablesXML(xmlOut,
1099  cfgMgr,
1100  tableGroup,
1101  TableGroupKey(tableGroupKey),
1102  modifiedTables,
1103  false /* refreshAll */);
1104 
1105  try
1106  {
1107  xmlOut.addTextElementToData(
1108  "StructureStatusAsJSON",
1109  cfgMgr->getTableByName(tableName)->getStructureAsJSON(cfgMgr));
1110  }
1111  catch(const std::runtime_error& e)
1112  {
1113  __SUP_SS__ << "The table plugin feature getStructureStatusAsJSON(), does not "
1114  "seem to be supported for the table '"
1115  << tableName
1116  << ".' Make sure you have the expected table plugin in your path, "
1117  "or contact system admins."
1118  << __E__;
1119  ss << "Here is the error: " << e.what() << __E__;
1120  __SUP_SS_THROW__;
1121  }
1122  }
1123  else if(requestType == "getArtdaqNodes")
1124  {
1125  std::string modifiedTables = CgiDataUtilities::postData(cgiIn, "modifiedTables");
1126  std::string tableGroup = CgiDataUtilities::getData(cgiIn, "tableGroup");
1127  std::string tableGroupKey = CgiDataUtilities::getData(cgiIn, "tableGroupKey");
1128  std::string contextGroup = CgiDataUtilities::getData(cgiIn, "contextGroup");
1129  std::string contextGroupKey = CgiDataUtilities::getData(cgiIn, "contextGroupKey");
1130  bool suppressMultiNode =
1131  1 == CgiDataUtilities::getDataAsInt(cgiIn, "suppressMultiNode");
1132 
1133  __SUP_COUTV__(modifiedTables);
1134  __SUP_COUTT__ << "tableGroup: " << tableGroup << __E__;
1135  __SUP_COUTT__ << "tableGroupKey: " << tableGroupKey << __E__;
1136  __SUP_COUTT__ << "contextGroup: " << contextGroup << __E__;
1137  __SUP_COUTT__ << "contextGroupKey: " << contextGroupKey << __E__;
1138  __SUP_COUTT__ << "suppressMultiNode: " << suppressMultiNode << __E__;
1139 
1140  handleGetArtdaqNodeRecordsXML(xmlOut,
1141  cfgMgr,
1142  modifiedTables,
1143  tableGroup,
1144  TableGroupKey(tableGroupKey),
1145  contextGroup,
1146  TableGroupKey(contextGroupKey),
1147  suppressMultiNode);
1148  }
1149  else if(requestType == "saveArtdaqNodes")
1150  {
1151  std::string modifiedTables = CgiDataUtilities::postData(cgiIn, "modifiedTables");
1152  std::string nodeString = CgiDataUtilities::postData(cgiIn, "nodeString");
1153  std::string subsystemString =
1154  CgiDataUtilities::postData(cgiIn, "subsystemString");
1155 
1156  __SUP_COUTV__(modifiedTables);
1157  __SUP_COUTV__(nodeString);
1158  __SUP_COUTV__(subsystemString);
1159 
1160  handleSaveArtdaqNodeRecordsXML(
1161  nodeString, subsystemString, xmlOut, cfgMgr, modifiedTables);
1162  }
1163  else if(requestType == "getArtdaqNodeLayout")
1164  {
1165  std::string contextGroupName =
1166  CgiDataUtilities::getData(cgiIn, "contextGroupName");
1167  std::string contextGroupKey = CgiDataUtilities::getData(cgiIn, "contextGroupKey");
1168 
1169  __SUP_COUTV__(contextGroupName);
1170  __SUP_COUTV__(contextGroupKey);
1171 
1172  handleLoadArtdaqNodeLayoutXML(
1173  xmlOut, cfgMgr, contextGroupName, TableGroupKey(contextGroupKey));
1174  }
1175  else if(requestType == "saveArtdaqNodeLayout")
1176  {
1177  std::string layout = CgiDataUtilities::postData(cgiIn, "layout");
1178  std::string contextGroupName =
1179  CgiDataUtilities::getData(cgiIn, "contextGroupName");
1180  std::string contextGroupKey = CgiDataUtilities::getData(cgiIn, "contextGroupKey");
1181 
1182  __SUP_COUTV__(layout);
1183  __SUP_COUTV__(contextGroupName);
1184  __SUP_COUTV__(contextGroupKey);
1185 
1186  handleSaveArtdaqNodeLayoutXML(
1187  xmlOut, cfgMgr, layout, contextGroupName, TableGroupKey(contextGroupKey));
1188  }
1189  else if(requestType == "checkAffectedActiveGroups")
1190  {
1191  std::string groupName = CgiDataUtilities::getData(cgiIn, "groupName");
1192  std::string groupKey = CgiDataUtilities::getData(cgiIn, "groupKey");
1193  std::string modifiedTables = CgiDataUtilities::postData(cgiIn, "modifiedTables");
1194  __SUP_COUT__ << "modifiedTables: " << modifiedTables << __E__;
1195  __SUP_COUT__ << "groupName: " << groupName << __E__;
1196  __SUP_COUT__ << "groupKey: " << groupKey << __E__;
1197 
1198  handleGetAffectedGroupsXML(
1199  xmlOut, cfgMgr, groupName, TableGroupKey(groupKey), modifiedTables);
1200  }
1201  else if(requestType == "SearchFieldInGroup")
1202  {
1203  std::string searchText = CgiDataUtilities::getData(cgiIn, "searchText");
1204  std::string filterValue = CgiDataUtilities::getData(cgiIn, "filterValue");
1205  std::string groupType = CgiDataUtilities::getData(cgiIn, "groupType");
1206  std::string optionGroups = CgiDataUtilities::getData(cgiIn, "optionGroups");
1207  std::string versionsToCheck = CgiDataUtilities::getData(cgiIn, "versionsToCheck");
1208 
1209  searchText = StringMacros::decodeURIComponent(searchText);
1210  filterValue = StringMacros::decodeURIComponent(filterValue);
1211  groupType = StringMacros::decodeURIComponent(groupType);
1212  optionGroups = StringMacros::decodeURIComponent(optionGroups);
1213  versionsToCheck = StringMacros::decodeURIComponent(versionsToCheck);
1214 
1215  __SUP_COUT__ << "searchText: " << searchText << __E__;
1216  __SUP_COUT__ << "filterValue: " << filterValue << __E__;
1217  __SUP_COUT__ << "groupType: " << groupType << __E__;
1218  __SUP_COUT__ << "optionGroups: " << optionGroups << __E__;
1219  __SUP_COUT__ << "versionsToCheck: " << versionsToCheck << __E__;
1220 
1221  handleSearchFieldInGroupXML(xmlOut,
1222  cfgMgr,
1223  searchText,
1224  (filterValue == "true"),
1225  groupType,
1226  optionGroups,
1227  versionsToCheck);
1228  }
1229  else if(requestType == "SearchFieldInTableVersions")
1230  {
1231  std::string searchText = CgiDataUtilities::getData(cgiIn, "searchText");
1232  std::string tableName = CgiDataUtilities::getData(cgiIn, "tableName");
1233  std::string searchVersionToCheck =
1234  CgiDataUtilities::getData(cgiIn, "searchVersionToCheck");
1235  std::string activeTablesOnly = CgiDataUtilities::getData(cgiIn, "activeOnly");
1236 
1237  searchText = StringMacros::decodeURIComponent(searchText);
1238  tableName = StringMacros::decodeURIComponent(tableName);
1239  searchVersionToCheck = StringMacros::decodeURIComponent(searchVersionToCheck);
1240  activeTablesOnly = StringMacros::decodeURIComponent(activeTablesOnly);
1241 
1242  __SUP_COUT__ << "searchText: " << searchText << __E__;
1243  __SUP_COUT__ << "tableName: " << tableName << __E__;
1244  __SUP_COUT__ << "searchVersionToCheck: " << searchVersionToCheck << __E__;
1245  __SUP_COUT__ << "activeTablesOnly: " << activeTablesOnly << __E__;
1246 
1247  handleSearchFieldInTableXML(xmlOut,
1248  cfgMgr,
1249  searchText,
1250  tableName,
1251  searchVersionToCheck,
1252  (activeTablesOnly == "true"));
1253  }
1254  else if(requestType == "saveTreeNodeEdit")
1255  {
1256  std::string editNodeType = CgiDataUtilities::getData(cgiIn, "editNodeType");
1257  std::string targetTable = CgiDataUtilities::getData(cgiIn, "targetTable");
1258  std::string targetTableVersion =
1259  CgiDataUtilities::getData(cgiIn, "targetTableVersion");
1260  std::string targetUID = CgiDataUtilities::getData(cgiIn, "targetUID");
1261  std::string targetColumn = CgiDataUtilities::getData(cgiIn, "targetColumn");
1262  std::string newValue = CgiDataUtilities::postData(cgiIn, "newValue");
1263 
1264  __SUP_COUT__ << "editNodeType: " << editNodeType << __E__;
1265  __SUP_COUT__ << "targetTable: " << targetTable << __E__;
1266  __SUP_COUT__ << "targetTableVersion: " << targetTableVersion << __E__;
1267  __SUP_COUT__ << "targetUID: " << targetUID << __E__;
1268  __SUP_COUT__ << "targetColumn: " << targetColumn << __E__;
1269  __SUP_COUT__ << "newValue: " << newValue << __E__;
1270 
1271  handleSaveTreeNodeEditXML(xmlOut,
1272  cfgMgr,
1273  targetTable,
1274  TableVersion(targetTableVersion),
1275  editNodeType,
1277  StringMacros::decodeURIComponent(targetColumn),
1278  newValue,
1279  userInfo.username_);
1280  }
1281  else if(requestType == "getLinkToChoices")
1282  {
1283  std::string linkToTableName = CgiDataUtilities::getData(cgiIn, "linkToTableName");
1284  std::string linkToTableVersion =
1285  CgiDataUtilities::getData(cgiIn, "linkToTableVersion");
1286  std::string linkIdType = CgiDataUtilities::getData(cgiIn, "linkIdType");
1287  std::string linkIndex = StringMacros::decodeURIComponent(
1288  CgiDataUtilities::getData(cgiIn, "linkIndex"));
1289  std::string linkInitId = CgiDataUtilities::getData(cgiIn, "linkInitId");
1290 
1291  __SUP_COUT__ << "linkToTableName: " << linkToTableName << __E__;
1292  __SUP_COUT__ << "linkToTableVersion: " << linkToTableVersion << __E__;
1293  __SUP_COUT__ << "linkIdType: " << linkIdType << __E__;
1294  __SUP_COUT__ << "linkIndex: " << linkIndex << __E__;
1295  __SUP_COUT__ << "linkInitId: " << linkInitId << __E__;
1296 
1297  handleGetLinkToChoicesXML(xmlOut,
1298  cfgMgr,
1299  linkToTableName,
1300  TableVersion(linkToTableVersion),
1301  linkIdType,
1302  linkIndex,
1303  linkInitId);
1304  }
1305  else if(requestType == "activateTableGroup")
1306  {
1307  std::string groupName = CgiDataUtilities::getData(cgiIn, "groupName");
1308  std::string groupKey = CgiDataUtilities::getData(cgiIn, "groupKey");
1309  bool ignoreWarnings = CgiDataUtilities::getDataAsInt(cgiIn, "ignoreWarnings");
1310 
1311  __SUP_COUT__ << "Activating group: " << groupName << "(" << groupKey << ")"
1312  << __E__;
1313  __SUP_COUTV__(ignoreWarnings);
1314 
1315  // add flag for GUI handling
1316  xmlOut.addTextElementToData("AttemptedGroupActivation", "1");
1317  xmlOut.addTextElementToData("AttemptedGroupActivationName", groupName);
1318  xmlOut.addTextElementToData("AttemptedGroupActivationKey", groupKey);
1319 
1320  try
1321  {
1322  std::string accumulatedErrors, groupTypeString;
1323 
1324  // if ignore warnings,
1325  // then only print errors, do not add to xml
1326 
1327  __COUTTV__(StringMacros::mapToString(cfgMgr->getActiveVersions()));
1328 
1329  cfgMgr->activateTableGroup(
1330  groupName, TableGroupKey(groupKey), &accumulatedErrors, &groupTypeString);
1331 
1332  if(accumulatedErrors != "")
1333  {
1334  if(!ignoreWarnings)
1335  {
1336  __SS__ << "Throwing exception on accumulated errors: "
1337  << accumulatedErrors << __E__;
1338  __SS_ONLY_THROW__;
1339  }
1340  // else just print
1341  __COUT_WARN__ << "Ignoring warnings so ignoring this error:"
1342  << accumulatedErrors << __E__;
1343  __COUT_WARN__ << "Done ignoring the above error(s)." << __E__;
1344  }
1345  xmlOut.addTextElementToData("AttemptedGroupActivationType", groupTypeString);
1346  }
1347  catch(std::runtime_error& e)
1348  {
1349  // NOTE it is critical for flimsy error parsing in JS GUI to leave
1350  // single quotes around the groupName and groupKey and have them be
1351  // the first single quotes encountered in the error mesage!
1352  __SUP_COUT__ << "Error detected!\n\n " << e.what() << __E__;
1353  xmlOut.addTextElementToData(
1354  "Error",
1355  "Error activating table group '" + groupName + "(" + groupKey + ")" +
1356  ".' Please see details below:\n\n" + std::string(e.what()));
1357  __SUP_COUT_ERR__ << "Errors detected so de-activating group: " << groupName
1358  << " (" << groupKey << ")" << __E__;
1359  try // just in case any lingering pieces, lets deactivate
1360  {
1361  cfgMgr->destroyTableGroup(groupName, true);
1362  }
1363  catch(...)
1364  {
1365  }
1366  }
1367  catch(cet::exception& e)
1368  {
1369  // NOTE it is critical for flimsy error parsing in JS GUI to leave
1370  // single quotes around the groupName and groupKey and have them be
1371  // the first single quotes encountered in the error mesage!
1372 
1373  __SUP_COUT__ << "Error detected!\n\n " << e.what() << __E__;
1374  xmlOut.addTextElementToData("Error",
1375  "Error activating table group '" + groupName +
1376  "(" + groupKey + ")" + "!'\n\n" +
1377  std::string(e.what()));
1378  __SUP_COUT_ERR__ << "Errors detected so de-activating group: " << groupName
1379  << " (" << groupKey << ")" << __E__;
1380  try // just in case any lingering pieces, lets deactivate
1381  {
1382  cfgMgr->destroyTableGroup(groupName, true);
1383  }
1384  catch(...)
1385  {
1386  }
1387  }
1388  catch(...)
1389  {
1390  __SUP_COUT__ << "Unknown error detected!" << __E__;
1391  try // just in case any lingering pieces, lets deactivate
1392  {
1393  cfgMgr->destroyTableGroup(groupName, true);
1394  }
1395  catch(...)
1396  {
1397  }
1398 
1399  throw; // unexpected exception!
1400  }
1401  }
1402  else if(requestType == "getActiveTableGroups")
1403  ; // do nothing, since they are always returned
1404  else if(requestType == "copyViewToCurrentColumns")
1405  {
1406  std::string tableName =
1407  CgiDataUtilities::getData(cgiIn, "tableName"); // from GET
1408  std::string sourceVersion = CgiDataUtilities::getData(cgiIn, "sourceVersion");
1409 
1410  __SUP_COUT__ << "tableName: " << tableName << __E__;
1411  __SUP_COUT__ << "sourceVersion: " << sourceVersion << __E__;
1412  __SUP_COUT__ << "userInfo.username_: " << userInfo.username_ << __E__;
1413 
1414  // copy source version to new temporary version
1415  TableVersion newTemporaryVersion;
1416  try
1417  {
1418  newTemporaryVersion =
1419  cfgMgr->copyViewToCurrentColumns(tableName, TableVersion(sourceVersion));
1420 
1421  __SUP_COUT__ << "New temporary version = " << newTemporaryVersion << __E__;
1422  }
1423  catch(std::runtime_error& e)
1424  {
1425  __SUP_COUT__ << "Error detected!\n\n " << e.what() << __E__;
1426  xmlOut.addTextElementToData("Error",
1427  "Error copying view from '" + tableName + "_v" +
1428  sourceVersion + "'! " +
1429  std::string(e.what()));
1430  }
1431  catch(...)
1432  {
1433  __SUP_COUT__ << "Error detected!\n\n " << __E__;
1434  xmlOut.addTextElementToData(
1435  "Error",
1436  "Error copying view from '" + tableName + "_v" + sourceVersion + "'! ");
1437  }
1438 
1439  handleGetTableXML(xmlOut, cfgMgr, tableName, newTemporaryVersion);
1440  }
1441  else if(requestType == "getLastTableGroups")
1442  {
1443  // std::string timeString;
1444  std::map<std::string /* group type */,
1445  std::tuple<std::string /*group name*/,
1446  TableGroupKey,
1447  std::string /* time string*/>>
1448  theGroups;
1449 
1450  theRemoteWebUsers_.getLastTableGroups(theGroups);
1451 
1452  for(const auto& theGroup : theGroups)
1453  {
1454  xmlOut.addTextElementToData("Last" + theGroup.first + "GroupName",
1455  std::get<0>(theGroup.second));
1456  xmlOut.addTextElementToData("Last" + theGroup.first + "GroupKey",
1457  std::get<1>(theGroup.second).toString());
1458  xmlOut.addTextElementToData("Last" + theGroup.first + "GroupTime",
1459  std::get<2>(theGroup.second));
1460  }
1461 
1462  // theGroup = theRemoteWebUsers_.getLastTableGroup("Configured", timeString);
1463  // xmlOut.addTextElementToData("LastConfiguredGroupName", theGroup.first);
1464  // xmlOut.addTextElementToData("LastConfiguredGroupKey", theGroup.second.toString());
1465  // xmlOut.addTextElementToData("LastConfiguredGroupTime", timeString);
1466  // theGroup = theRemoteWebUsers_.getLastTableGroup("Started", timeString);
1467  // xmlOut.addTextElementToData("LastStartedGroupName", theGroup.first);
1468  // xmlOut.addTextElementToData("LastStartedGroupKey", theGroup.second.toString());
1469  // xmlOut.addTextElementToData("LastStartedGroupTime", timeString);
1470  // theGroup = theRemoteWebUsers_.getLastTableGroup("ActivatedConfig", timeString);
1471  // xmlOut.addTextElementToData("LastActivatedConfigGroupName", theGroup.first);
1472  // xmlOut.addTextElementToData("LastActivatedConfigGroupKey",
1473  // theGroup.second.toString());
1474  // xmlOut.addTextElementToData("LastActivatedConfigGroupTime", timeString);
1475  // theGroup = theRemoteWebUsers_.getLastTableGroup("ActivatedContext", timeString);
1476  // xmlOut.addTextElementToData("LastActivatedContextGroupName", theGroup.first);
1477  // xmlOut.addTextElementToData("LastActivatedContextGroupKey",
1478  // theGroup.second.toString());
1479  // xmlOut.addTextElementToData("LastActivatedContextGroupTime", timeString);
1480  // theGroup = theRemoteWebUsers_.getLastTableGroup("ActivatedBackbone", timeString);
1481  // xmlOut.addTextElementToData("LastActivatedBackboneGroupName", theGroup.first);
1482  // xmlOut.addTextElementToData("LastActivatedBackboneGroupKey",
1483  // theGroup.second.toString());
1484  // xmlOut.addTextElementToData("LastActivatedBackboneGroupTime", timeString);
1485  // theGroup = theRemoteWebUsers_.getLastTableGroup("ActivatedIterator", timeString);
1486  // xmlOut.addTextElementToData("LastActivatedIteratorGroupName", theGroup.first);
1487  // xmlOut.addTextElementToData("LastActivatedIteratorGroupKey",
1488  // theGroup.second.toString());
1489  // xmlOut.addTextElementToData("LastActivatedIteratorGroupTime", timeString);
1490 
1491  //check other subsystems active groups
1492  handleOtherSubsystemActiveGroups(xmlOut, cfgMgr, false /* getFullList */);
1493  }
1494  else if(requestType == "getSubsytemTableGroups")
1495  {
1496  std::string subsystem =
1497  CgiDataUtilities::getData(cgiIn, "subsystem"); // from GET
1498  __SUP_COUTV__(subsystem);
1499  handleOtherSubsystemActiveGroups(
1500  xmlOut, cfgMgr, true /* getFullList */, subsystem);
1501  } //end getSubsytemTableGroups
1502  else if(requestType == "diffWithActiveGroup")
1503  {
1504  std::string groupName =
1505  CgiDataUtilities::getData(cgiIn, "groupName"); // from GET
1506  std::string groupKey = CgiDataUtilities::getData(cgiIn, "groupKey"); // from GET
1507  __SUP_COUTV__(groupName);
1508  __SUP_COUTV__(groupKey);
1509 
1510  handleGroupDiff(
1511  xmlOut, cfgMgr, groupName, TableGroupKey(groupKey)); //diff with active group
1512  } //end diffWithActiveGroup
1513  else if(requestType == "diffWithGroupKey")
1514  {
1515  std::string groupName =
1516  CgiDataUtilities::getData(cgiIn, "groupName"); // from GET
1517  std::string groupKey = CgiDataUtilities::getData(cgiIn, "groupKey"); // from GET
1518  std::string diffKey = CgiDataUtilities::getData(cgiIn, "diffKey"); // from GET
1519  std::string diffGroupName =
1520  CgiDataUtilities::getData(cgiIn, "diffGroupName"); // from GET
1521  __SUP_COUTV__(groupName);
1522  __SUP_COUTV__(groupKey);
1523  __SUP_COUTV__(diffKey);
1524  __SUP_COUTV__(diffGroupName);
1525 
1526  handleGroupDiff(xmlOut,
1527  cfgMgr,
1528  groupName,
1529  TableGroupKey(groupKey),
1530  TableGroupKey(diffKey),
1531  diffGroupName);
1532  } //end diffWithGroupKey
1533  else if(requestType == "diffTableVersions")
1534  {
1535  std::string tableName =
1536  CgiDataUtilities::getData(cgiIn, "tableName"); // from GET
1537  std::string vA = CgiDataUtilities::getData(cgiIn, "vA"); // from GET
1538  std::string vB = CgiDataUtilities::getData(cgiIn, "vB"); // from GET
1539  __SUP_COUTV__(tableName);
1540  __SUP_COUTV__(vA);
1541  __SUP_COUTV__(vB);
1542 
1543  TableVersion versionA, versionB;
1544  const std::map<std::string, TableInfo>& allTableInfo = cfgMgr->getAllTableInfo();
1545 
1546  //convert aliases if specified
1547  if(allTableInfo.find(tableName) != allTableInfo.end())
1548  {
1549  if(vA.find(ConfigurationManager::ALIAS_VERSION_PREAMBLE) == 0)
1550  {
1551  // convert alias to version
1552  std::map<std::string /*table*/,
1553  std::map<std::string /*alias*/, TableVersion>>
1554  versionAliases = cfgMgr->getVersionAliases();
1555 
1556  std::string versionAlias;
1557  versionAlias =
1558  vA.substr(ConfigurationManager::ALIAS_VERSION_PREAMBLE.size());
1559 
1560  if(versionAliases.find(tableName) != versionAliases.end() &&
1561  versionAliases[tableName].find(versionAlias) !=
1562  versionAliases[tableName].end())
1563  {
1564  versionA = versionAliases[tableName][versionAlias];
1565  __SUP_COUT__ << "version alias translated to: " << versionA << __E__;
1566  }
1567  else
1568  __SUP_COUT_WARN__ << "version alias '" << versionAlias
1569  << "'was not found in active version aliases!"
1570  << __E__;
1571  }
1572  else // else take specified version
1573  versionA = atoi(vA.c_str());
1574 
1575  if(vB.find(ConfigurationManager::ALIAS_VERSION_PREAMBLE) == 0)
1576  {
1577  // convert alias to version
1578  std::map<std::string /*table*/,
1579  std::map<std::string /*alias*/, TableVersion>>
1580  versionAliases = cfgMgr->getVersionAliases();
1581 
1582  std::string versionAlias;
1583  versionAlias =
1584  vB.substr(ConfigurationManager::ALIAS_VERSION_PREAMBLE.size());
1585 
1586  if(versionAliases.find(tableName) != versionAliases.end() &&
1587  versionAliases[tableName].find(versionAlias) !=
1588  versionAliases[tableName].end())
1589  {
1590  versionB = versionAliases[tableName][versionAlias];
1591  __SUP_COUT__ << "version alias translated to: " << versionB << __E__;
1592  }
1593  else
1594  __SUP_COUT_WARN__ << "version alias '" << versionAlias
1595  << "'was not found in active version aliases!"
1596  << __E__;
1597  }
1598  else // else take specified version
1599  versionB = atoi(vB.c_str());
1600  }
1601  else
1602  {
1603  versionA = atoi(vA.c_str());
1604  versionB = atoi(vB.c_str());
1605  }
1606 
1607  __SUP_COUTV__(versionA);
1608  __SUP_COUTV__(versionB);
1609 
1610  handleTableDiff(xmlOut, cfgMgr, tableName, versionA, versionB);
1611  } //end diffTableVersions
1612  else if(requestType == "findGroupsWithTable")
1613  {
1614  std::string tableName =
1615  CgiDataUtilities::getData(cgiIn, "tableName"); // from GET
1616  std::string tableVersion =
1617  CgiDataUtilities::getData(cgiIn, "tableVersion"); // from GET
1618 
1619  __SUP_COUTV__(tableName);
1620  __SUP_COUTV__(tableVersion);
1621 
1622  std::set<std::string> groupsContainingTable =
1623  cfgMgr->getConfigurationInterface()->findGroupsWithTable(
1624  tableName, TableVersion(tableVersion));
1625  __SUP_COUT__ << "Groups containing " << tableName << "-v" << tableVersion
1626  << " count: " << groupsContainingTable.size() << __E__;
1627  std::string groupsContainingTableString =
1628  StringMacros::setToString(groupsContainingTable);
1629  __SUP_COUTTV__(groupsContainingTableString);
1630 
1631  xmlOut.addNumberElementToData("GroupsContainingCount",
1632  groupsContainingTable.size());
1633  xmlOut.addTextElementToData("GroupsContainingCSV", groupsContainingTableString);
1634 
1635  } //end findGroupsWithTable
1636  else if(requestType == "savePlanCommandSequence")
1637  {
1638  std::string planName = CgiDataUtilities::getData(cgiIn, "planName"); // from GET
1639  std::string commands =
1640  CgiDataUtilities::postData(cgiIn, "commands"); // from POST
1641  std::string modifiedTables = CgiDataUtilities::postData(cgiIn, "modifiedTables");
1642  std::string groupName = CgiDataUtilities::getData(cgiIn, "groupName");
1643  std::string groupKey = CgiDataUtilities::getData(cgiIn, "groupKey");
1644 
1645  __SUP_COUTV__(modifiedTables);
1646  __SUP_COUTV__(planName);
1647  __SUP_COUTV__(commands);
1648  __SUP_COUTV__(groupName);
1649  __SUP_COUTV__(groupKey);
1650 
1651  handleSavePlanCommandSequenceXML(xmlOut,
1652  cfgMgr,
1653  groupName,
1654  TableGroupKey(groupKey),
1655  modifiedTables,
1656  userInfo.username_,
1657  planName,
1658  commands);
1659  }
1660  else if(requestType == "mergeGroups")
1661  {
1662  std::string groupANameContext =
1663  CgiDataUtilities::getData(cgiIn, "groupANameContext");
1664  std::string groupAKeyContext =
1665  CgiDataUtilities::getData(cgiIn, "groupAKeyContext");
1666  std::string groupBNameContext =
1667  CgiDataUtilities::getData(cgiIn, "groupBNameContext");
1668  std::string groupBKeyContext =
1669  CgiDataUtilities::getData(cgiIn, "groupBKeyContext");
1670  std::string groupANameConfig =
1671  CgiDataUtilities::getData(cgiIn, "groupANameConfig");
1672  std::string groupAKeyConfig = CgiDataUtilities::getData(cgiIn, "groupAKeyConfig");
1673  std::string groupBNameConfig =
1674  CgiDataUtilities::getData(cgiIn, "groupBNameConfig");
1675  std::string groupBKeyConfig = CgiDataUtilities::getData(cgiIn, "groupBKeyConfig");
1676  std::string mergeApproach = CgiDataUtilities::getData(cgiIn, "mergeApproach");
1677 
1678  __SUP_COUTV__(groupANameContext);
1679  __SUP_COUTV__(groupAKeyContext);
1680  __SUP_COUTV__(groupBNameContext);
1681  __SUP_COUTV__(groupBKeyContext);
1682  __SUP_COUTV__(groupANameConfig);
1683  __SUP_COUTV__(groupAKeyConfig);
1684  __SUP_COUTV__(groupBNameConfig);
1685  __SUP_COUTV__(groupBKeyConfig);
1686  __SUP_COUTV__(mergeApproach);
1687 
1688  handleMergeGroupsXML(xmlOut,
1689  cfgMgr,
1690  groupANameContext,
1691  TableGroupKey(groupAKeyContext),
1692  groupBNameContext,
1693  TableGroupKey(groupBKeyContext),
1694  groupANameConfig,
1695  TableGroupKey(groupAKeyConfig),
1696  groupBNameConfig,
1697  TableGroupKey(groupBKeyConfig),
1698  userInfo.username_,
1699  mergeApproach);
1700  }
1701  else if(requestType == "getArtdaqSystemVariables")
1702  {
1703  std::string filePath =
1704  std::string(__ENV__("USER_DATA")) + "/ServiceData/ArtdaqSystemVariables.dat";
1705  std::ifstream inFile(filePath);
1706  if(inFile.is_open())
1707  {
1708  std::string line;
1709  while(std::getline(inFile, line))
1710  {
1711  size_t eqPos = line.find('=');
1712  if(eqPos == std::string::npos)
1713  continue;
1714  std::string key = line.substr(0, eqPos);
1715  std::string value = line.substr(eqPos + 1);
1716  xmlOut.addTextElementToData("artdaq_" + key, value);
1717  }
1718  }
1719  }
1720  else if(requestType == "setArtdaqSystemVariable")
1721  {
1722  std::string key = CgiDataUtilities::postData(cgiIn, "key");
1723  std::string value =
1725 
1726  if(key.empty())
1727  {
1728  xmlOut.addTextElementToData("Error", "Variable key must not be empty.");
1729  }
1730  else
1731  {
1732  bool valid = true;
1733  for(char c : key)
1734  if(!std::isalnum(c) && c != '_')
1735  {
1736  valid = false;
1737  break;
1738  }
1739  if(!valid)
1740  {
1741  xmlOut.addTextElementToData("Error",
1742  "Variable key must contain only alphanumeric "
1743  "characters and underscores.");
1744  }
1745  else
1746  {
1747  std::string filePath = std::string(__ENV__("USER_DATA")) +
1748  "/ServiceData/ArtdaqSystemVariables.dat";
1749  std::map<std::string, std::string> vars;
1750  {
1751  std::ifstream inFile(filePath);
1752  if(inFile.is_open())
1753  {
1754  std::string line;
1755  while(std::getline(inFile, line))
1756  {
1757  size_t eqPos = line.find('=');
1758  if(eqPos == std::string::npos)
1759  continue;
1760  vars[line.substr(0, eqPos)] = line.substr(eqPos + 1);
1761  }
1762  }
1763  }
1764 
1765  vars[key] = value;
1766 
1767  std::ofstream outFile(filePath);
1768  if(!outFile.is_open())
1769  {
1770  xmlOut.addTextElementToData(
1771  "Error", "Failed to open persistence file for writing.");
1772  }
1773  else
1774  {
1775  for(auto& [k, v] : vars)
1776  outFile << k << "=" << v << "\n";
1777  __SUP_COUT__ << "Set artdaq system variable " << key << " = " << value
1778  << __E__;
1779  xmlOut.addTextElementToData("Success", "Variable '" + key + "' set.");
1780  }
1781  }
1782  }
1783  }
1784  else if(requestType == "getJsonDocuments")
1785  {
1786  auto* ifc = ConfigurationInterface::getInstance();
1787 
1788  std::set<std::string> allTableNames = ifc->getAllTableNames();
1789 
1790  for(const auto& tableName : allTableNames)
1791  {
1792  if(tableName.find(TableBase::JSON_DOC_PREPEND) != 0)
1793  continue;
1794 
1795  std::string docName = tableName.substr(TableBase::JSON_DOC_PREPEND.size());
1796 
1797  TableBase tmpTable(true, tableName);
1798  std::set<TableVersion> versions = ifc->getVersions(&tmpTable);
1799 
1800  std::string versionList;
1801  for(const auto& v : versions)
1802  {
1803  if(!versionList.empty())
1804  versionList += ",";
1805  versionList += v.toString();
1806  }
1807 
1808  xmlOut.addTextElementToData("jsonDoc_name", docName);
1809  xmlOut.addTextElementToData("jsonDoc_versions", versionList);
1810  }
1811  }
1812  else if(requestType == "getJsonDocumentContent")
1813  {
1814  std::string docName = CgiDataUtilities::getData(cgiIn, "docName");
1815  std::string docVersion = CgiDataUtilities::getData(cgiIn, "docVersion");
1816 
1817  __SUP_COUTV__(docName);
1818  __SUP_COUTV__(docVersion);
1819 
1820  bool valid = !docName.empty();
1821  for(char c : docName)
1822  if(!std::isalnum(c) && c != '_' && c != '-')
1823  {
1824  valid = false;
1825  break;
1826  }
1827  if(!valid)
1828  {
1829  xmlOut.addTextElementToData(
1830  "Error",
1831  "Document name must be non-empty and contain only "
1832  "alphanumeric characters, dashes, and underscores.");
1833  }
1834  else if(docVersion.empty() ||
1835  docVersion.find_first_not_of("0123456789") != std::string::npos)
1836  {
1837  xmlOut.addTextElementToData(
1838  "Error", "Document version must contain only numeric characters.");
1839  }
1840  else
1841  {
1842  try
1843  {
1844  auto* ifc = ConfigurationInterface::getInstance();
1845  std::string json = ifc->loadCustomJSON(docName, TableVersion(docVersion));
1846  xmlOut.addTextElementToData("content", json);
1847  }
1848  catch(const std::exception& e)
1849  {
1850  xmlOut.addTextElementToData("Error",
1851  "Failed to load document '" + docName + "-v" +
1852  docVersion + "': " + e.what());
1853  }
1854  }
1855  }
1856  else if(requestType == "saveJsonDocumentContent")
1857  {
1858  std::string docName = StringMacros::decodeURIComponent(
1859  CgiDataUtilities::postData(cgiIn, "docName"));
1860  std::string content = StringMacros::decodeURIComponent(
1861  CgiDataUtilities::postData(cgiIn, "content"));
1862 
1863  __SUP_COUTV__(docName);
1864  __SUP_COUTVS__(10, content);
1865 
1866  bool valid = !docName.empty();
1867  for(char c : docName)
1868  if(!std::isalnum(c) && c != '_' && c != '-')
1869  {
1870  valid = false;
1871  break;
1872  }
1873  if(!valid)
1874  {
1875  xmlOut.addTextElementToData(
1876  "Error",
1877  "Document name must be non-empty and contain only "
1878  "alphanumeric characters, dashes, and underscores.");
1879  }
1880  else if(content.empty())
1881  {
1882  xmlOut.addTextElementToData("Error", "Document content must not be empty.");
1883  }
1884  else
1885  {
1886  try
1887  {
1888  auto* ifc = ConfigurationInterface::getInstance();
1889  std::pair<std::string, TableVersion> saved =
1890  ifc->saveCustomJSON(content, docName);
1891  __SUP_COUT__ << "Saved JSON document '" << docName << "' as version "
1892  << saved.second.toString() << __E__;
1893  xmlOut.addTextElementToData("newVersion", saved.second.toString());
1894  xmlOut.addTextElementToData(
1895  "Success", "Saved as version " + saved.second.toString());
1896  }
1897  catch(const std::exception& e)
1898  {
1899  xmlOut.addTextElementToData(
1900  "Error", "Failed to save document '" + docName + "': " + e.what());
1901  }
1902  }
1903  }
1904  else if(requestType == "getAppUrnByClass")
1905  {
1906  std::string className = CgiDataUtilities::getData(cgiIn, "className");
1907 
1908  __SUP_COUTV__(className);
1909 
1910  if(className.empty())
1911  {
1912  xmlOut.addTextElementToData("Error", "className must not be empty.");
1913  }
1914  else
1915  {
1916  try
1917  {
1918  std::vector<std::pair<std::string, ConfigurationTree>> appRecords =
1919  cfgMgr->getNode(ConfigurationManager::XDAQ_APPLICATION_TABLE_NAME)
1920  .getChildren();
1921 
1922  bool found = false;
1923  for(const auto& appRecord : appRecords)
1924  {
1925  std::string appClass =
1926  appRecord.second.getNode("Class").getValueAsString();
1927 
1928  // tolerate either a bare class name or a namespace-qualified
1929  // class name (e.g. "CodeEditorSupervisor" or
1930  // "ots::CodeEditorSupervisor") on either side of the comparison
1931  bool classMatches =
1932  appClass == className ||
1933  (appClass.size() > className.size() &&
1934  appClass.compare(appClass.size() - className.size(),
1935  className.size(),
1936  className) == 0 &&
1937  appClass[appClass.size() - className.size() - 1] == ':');
1938  if(!classMatches)
1939  continue;
1940 
1941  std::string appId = appRecord.second.getNode("Id").getValueAsString();
1942  xmlOut.addTextElementToData("urn", appId);
1943  found = true;
1944  break;
1945  }
1946 
1947  if(!found)
1948  xmlOut.addTextElementToData(
1949  "Error",
1950  "No enabled application found for class '" + className + "'.");
1951  }
1952  catch(const std::exception& e)
1953  {
1954  xmlOut.addTextElementToData(
1955  "Error",
1956  "Failed to look up application URN for class '" + className +
1957  "': " + e.what());
1958  }
1959  }
1960  }
1961  else
1962  {
1963  __SUP_SS__ << "requestType Request, " << requestType
1964  << ", not recognized by the Configuration GUI Supervisor (was it "
1965  "intended for another Supervisor?)."
1966  << __E__;
1967  __SUP_COUT__ << "\n" << ss.str();
1968  xmlOut.addTextElementToData("Error", ss.str());
1969  }
1970 
1971  __SUP_COUTT__ << "cfgMgr runtime=" << cfgMgr->runTimeSeconds() << __E__;
1972  // always add active table groups to xml response
1974  xmlOut, cfgMgr, userInfo.username_);
1975  __SUP_COUTT__ << "cfgMgr runtime=" << cfgMgr->runTimeSeconds() << __E__;
1976 
1977 } // end ::request()
1978 catch(const std::runtime_error& e)
1979 {
1980  __SS__ << "A fatal error occurred while handling the request '" << requestType
1981  << ".' Error: " << e.what() << __E__;
1982  __COUT_ERR__ << "\n" << ss.str();
1983  xmlOut.addTextElementToData("Error", ss.str());
1984 
1985  try
1986  {
1987  // always add version tracking bool
1988  xmlOut.addTextElementToData(
1989  "versionTracking",
1990  ConfigurationInterface::isVersionTrackingEnabled() ? "ON" : "OFF");
1991  }
1992  catch(...)
1993  {
1994  __COUT_ERR__ << "Error getting version tracking status!" << __E__;
1995  }
1996 } // end ::request() catch
1997 catch(...)
1998 {
1999  __SS__ << "An unknown fatal error occurred while handling the request '"
2000  << requestType << ".'" << __E__;
2001  try
2002  {
2003  throw;
2004  } //one more try to printout extra info
2005  catch(const std::exception& e)
2006  {
2007  ss << "Exception message: " << e.what();
2008  }
2009  catch(...)
2010  {
2011  }
2012  __COUT_ERR__ << "\n" << ss.str();
2013  xmlOut.addTextElementToData("Error", ss.str());
2014 
2015  try
2016  {
2017  // always add version tracking bool
2018  xmlOut.addTextElementToData(
2019  "versionTracking",
2020  ConfigurationInterface::isVersionTrackingEnabled() ? "ON" : "OFF");
2021  }
2022  catch(...)
2023  {
2024  __COUT_ERR__ << "Error getting version tracking status!" << __E__;
2025  }
2026 
2027 } // end ::request() catch
2028 
2029 //==============================================================================
2044 void ConfigurationGUISupervisor::handleGetAffectedGroupsXML(
2045  HttpXmlDocument& xmlOut,
2046  ConfigurationManagerRW* cfgMgr,
2047  const std::string& rootGroupName,
2048  const TableGroupKey& rootGroupKey,
2049  const std::string& modifiedTables)
2050 try
2051 {
2052  __SUP_COUT__ << "rootGroupName " << rootGroupName << "(" << rootGroupKey
2053  << "). modifiedTables = " << modifiedTables << __E__;
2054 
2055  std::map<std::string, std::pair<std::string, TableGroupKey>> consideredGroups =
2056  cfgMgr->getActiveTableGroups();
2057 
2058  // check that there is a context and table group to consider
2059  // if there is not, then pull from failed list
2060  if(consideredGroups[ConfigurationManager::GROUP_TYPE_NAME_CONTEXT].second.isInvalid())
2061  {
2062  __SUP_COUT__ << "Finding a context group to consider..." << __E__;
2063  if(cfgMgr->getFailedTableGroups().find(
2064  ConfigurationManager::GROUP_TYPE_NAME_CONTEXT) !=
2065  cfgMgr->getFailedTableGroups().end())
2066  {
2067  consideredGroups[ConfigurationManager::GROUP_TYPE_NAME_CONTEXT] =
2068  cfgMgr->getFailedTableGroups().at(
2069  ConfigurationManager::GROUP_TYPE_NAME_CONTEXT);
2070  }
2071  else if(cfgMgr->getFailedTableGroups().find(
2072  ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN) !=
2073  cfgMgr->getFailedTableGroups().end())
2074  {
2075  consideredGroups[ConfigurationManager::GROUP_TYPE_NAME_CONTEXT] =
2076  cfgMgr->getFailedTableGroups().at(
2077  ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN);
2078  }
2079  }
2080  if(consideredGroups[ConfigurationManager::GROUP_TYPE_NAME_CONFIGURATION]
2081  .second.isInvalid())
2082  {
2083  __SUP_COUT__ << "Finding a table group to consider..." << __E__;
2084  if(cfgMgr->getFailedTableGroups().find(
2085  ConfigurationManager::GROUP_TYPE_NAME_CONFIGURATION) !=
2086  cfgMgr->getFailedTableGroups().end())
2087  {
2088  consideredGroups[ConfigurationManager::GROUP_TYPE_NAME_CONFIGURATION] =
2089  cfgMgr->getFailedTableGroups().at(
2090  ConfigurationManager::GROUP_TYPE_NAME_CONFIGURATION);
2091  }
2092  else if(cfgMgr->getFailedTableGroups().find(
2093  ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN) !=
2094  cfgMgr->getFailedTableGroups().end())
2095  {
2096  consideredGroups[ConfigurationManager::GROUP_TYPE_NAME_CONFIGURATION] =
2097  cfgMgr->getFailedTableGroups().at(
2098  ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN);
2099  }
2100  }
2101 
2102  __SUP_COUTV__(StringMacros::mapToString(consideredGroups));
2103 
2104  // determine the type of table group
2105  try
2106  {
2107  std::map<std::string /*name*/, TableVersion /*version*/> rootGroupMemberMap;
2108 
2109  cfgMgr->loadTableGroup(rootGroupName,
2110  rootGroupKey,
2111  0,
2112  &rootGroupMemberMap,
2113  0,
2114  0,
2115  0,
2116  0,
2117  0, // defaults
2118  true); // doNotLoadMember
2119 
2120  const std::string& groupType = cfgMgr->getTypeNameOfGroup(rootGroupMemberMap);
2121 
2122  consideredGroups[groupType] =
2123  std::pair<std::string, TableGroupKey>(rootGroupName, rootGroupKey);
2124  }
2125  catch(const std::runtime_error& e)
2126  {
2127  // if actual group name was attempted re-throw
2128  if(rootGroupName.size())
2129  {
2130  __SUP_SS__ << "Failed to determine type of table group for " << rootGroupName
2131  << "(" << rootGroupKey << ")! " << e.what() << __E__;
2132  __SUP_COUT_ERR__ << "\n" << ss.str();
2133  //__SS_THROW__;
2134  }
2135 
2136  // else assume it was the intention to just consider the active groups
2137  __SUP_COUT__ << "Did not modify considered active groups due to empty root group "
2138  "name - assuming this was intentional."
2139  << __E__;
2140  }
2141  catch(...)
2142  {
2143  // if actual group name was attempted re-throw
2144  if(rootGroupName.size())
2145  {
2146  __SUP_COUT_ERR__ << "Failed to determine type of table group for "
2147  << rootGroupName << "(" << rootGroupKey << ")!" << __E__;
2148  // throw;
2149  }
2150 
2151  // else assume it was the intention to just consider the active groups
2152  __SUP_COUT__ << "Did not modify considered active groups due to empty root group "
2153  "name - assuming this was intentional."
2154  << __E__;
2155  }
2156 
2157  std::map<std::string /*name*/,
2158  std::pair<bool /*foundAffectedGroup*/, TableVersion /*version*/>>
2159  modifiedTablesMap;
2160  std::map<std::string /*name*/,
2161  std::pair<bool /*foundAffectedGroup*/, TableVersion /*version*/>>::iterator
2162  modifiedTablesMapIt;
2163  {
2164  std::istringstream f(modifiedTables);
2165  std::string table, version;
2166  while(getline(f, table, ','))
2167  {
2168  getline(f, version, ',');
2169  modifiedTablesMap.insert(
2170  std::pair<
2171  std::string /*name*/,
2172  std::pair<bool /*foundAffectedGroup*/, TableVersion /*version*/>>(
2173  table,
2174  std::make_pair(false /*foundAffectedGroup*/, TableVersion(version))));
2175  }
2176  __SUP_COUT__ << modifiedTables << __E__;
2177  for(auto& pair : modifiedTablesMap)
2178  __SUP_COUT__ << "modified table " << pair.first << ":" << pair.second.second
2179  << __E__;
2180  }
2181 
2182  bool affected;
2183  xercesc::DOMElement* parentEl = nullptr;
2184  std::string groupComment;
2185  std::vector<std::string> orderedGroupTypes(
2186  {ConfigurationManager::GROUP_TYPE_NAME_CONTEXT,
2187  ConfigurationManager::GROUP_TYPE_NAME_BACKBONE,
2188  ConfigurationManager::GROUP_TYPE_NAME_ITERATE,
2189  ConfigurationManager::GROUP_TYPE_NAME_CONFIGURATION});
2190  for(auto groupType : orderedGroupTypes)
2191  {
2192  if(consideredGroups.find(groupType) == consideredGroups.end())
2193  continue; // skip missing
2194 
2195  const std::pair<std::string, TableGroupKey>& group = consideredGroups[groupType];
2196 
2197  if(group.second.isInvalid())
2198  continue; // skip invalid
2199 
2200  __SUP_COUT__ << "Considering " << groupType << " group " << group.first << " ("
2201  << group.second << ")" << __E__;
2202 
2203  affected = false;
2204  parentEl = nullptr;
2205 
2206  std::map<std::string /*name*/, TableVersion /*version*/> memberMap;
2207  cfgMgr->loadTableGroup(group.first,
2208  group.second,
2209  0,
2210  &memberMap,
2211  0,
2212  0,
2213  &groupComment,
2214  0,
2215  0, // mostly defaults
2216  true /*doNotLoadMember*/);
2217 
2218  __SUP_COUTV__(StringMacros::mapToString(memberMap));
2219  __SUP_COUT__ << "groupComment = " << groupComment << __E__;
2220 
2221  for(auto& table : memberMap)
2222  {
2223  if((modifiedTablesMapIt = modifiedTablesMap.find(table.first)) !=
2224  modifiedTablesMap
2225  .end() && // check if version is different for member table
2226  table.second != (*modifiedTablesMapIt).second.second)
2227  {
2228  __SUP_COUT__ << "Affected by " << (*modifiedTablesMapIt).first << ":"
2229  << (*modifiedTablesMapIt).second.second << __E__;
2230 
2231  memberMap[table.first] = (*modifiedTablesMapIt).second.second;
2232  (*modifiedTablesMapIt).second.first = true; // found affected group
2233 
2234  affected = true;
2235  if(!parentEl)
2236  parentEl = xmlOut.addTextElementToData("AffectedActiveGroup", "");
2237  }
2238  }
2239 
2240  if(groupType == ConfigurationManager::GROUP_TYPE_NAME_CONFIGURATION)
2241  {
2242  __SUP_COUT__ << "Considering mockup tables for Configuration Group..."
2243  << __E__;
2244  for(auto& table : modifiedTablesMap)
2245  {
2246  if(table.second.first) // already found affected group
2247  continue;
2248 
2249  if(table.second.second.isMockupVersion() &&
2250  memberMap.find(table.first) == memberMap.end())
2251  {
2252  __SUP_COUT__ << "Found mockup table '" << table.first
2253  << "' for Configuration Group." << __E__;
2254  memberMap[table.first] = table.second.second;
2255 
2256  if(!parentEl)
2257  parentEl = xmlOut.addTextElementToData("AffectedActiveGroup", "");
2258  //indicate to client this table needs to be added to group!
2259  xmlOut.addTextElementToParent("AddMemberName", table.first, parentEl);
2260  xmlOut.addTextElementToParent(
2261  "AddMemberVersion", table.second.second.toString(), parentEl);
2262 
2263  affected = true;
2264  }
2265  }
2266  }
2267 
2268  __SUP_COUTV__(affected);
2269  if(affected)
2270  {
2271  if(!parentEl)
2272  parentEl = xmlOut.addTextElementToData("AffectedActiveGroup", "");
2273  xmlOut.addTextElementToParent("GroupName", group.first, parentEl);
2274  xmlOut.addTextElementToParent("GroupKey", group.second.toString(), parentEl);
2275  xmlOut.addTextElementToParent("GroupComment", groupComment, parentEl);
2276 
2277  for(auto& table : memberMap)
2278  {
2279  xmlOut.addTextElementToParent("MemberName", table.first, parentEl);
2280  xmlOut.addTextElementToParent(
2281  "MemberVersion", table.second.toString(), parentEl);
2282  }
2283  }
2284  } // end affected group loop
2285 }
2286 catch(std::runtime_error& e)
2287 {
2288  __SUP_COUT__ << "Error detected!\n\n " << e.what() << __E__;
2289  xmlOut.addTextElementToData(
2290  "Error", "Error getting affected groups! " + std::string(e.what()));
2291 }
2292 catch(...)
2293 {
2294  __SUP_COUT__ << "Error detected!\n\n " << __E__;
2295  xmlOut.addTextElementToData("Error", "Error getting affected groups! ");
2296 }
2297 
2298 //==============================================================================
2306 void ConfigurationGUISupervisor::setupActiveTablesXML(
2307  HttpXmlDocument& xmlOut,
2308  ConfigurationManagerRW* cfgMgr,
2309  const std::string& groupName,
2310  const TableGroupKey& groupKey,
2311  const std::string& modifiedTables,
2312  bool refreshAll,
2313  bool doGetGroupInfo,
2314  std::map<std::string /*name*/, TableVersion /*version*/>* returnMemberMap,
2315  bool outputActiveTables,
2316  std::string* accumulatedErrors)
2317 try
2318 {
2319  xmlOut.addTextElementToData("tableGroup", groupName);
2320  xmlOut.addTextElementToData("tableGroupKey", groupKey.toString());
2321 
2322  bool usingActiveGroups = (groupName == "" || groupKey.isInvalid());
2323 
2324  __SUP_COUTTV__(StringMacros::mapToString(cfgMgr->getActiveVersions()));
2325 
2326  // reload all tables so that partially loaded tables are not allowed
2327  if( //usingActiveGroups ||
2328  refreshAll)
2329  {
2330  __SUP_COUT__ << "Refreshing all table info, ignoring warnings..." << __E__;
2331  std::string accumulatedWarnings = "";
2332  cfgMgr->getAllTableInfo(true /* refresh */,
2333  &accumulatedWarnings,
2334  "" /* errorFilterName */,
2335  false /* getGroupKeys */,
2336  false /* getGroupInfo */,
2337  true /* initializeActiveGroups */);
2338  }
2339  else //make sure expected active tables are setup, for standard starting point
2340  {
2341  //This is needed, for example, when on Context group which could point into the
2342  // Configure group; need common starting point (which is the Active group tables)
2343  // Then bring the modified tables over top.
2344  // Context --> Configure is simplest example
2345  // ...but Configure can also point at Context, or Iterator, or Backbone.
2346 
2347  __SUP_COUT__ << "Restoring active table group tables..." << __E__;
2348  auto activeGroups = cfgMgr->getActiveTableGroups();
2349  for(const auto& activeGroup : activeGroups)
2350  {
2351  if(activeGroup.second.first == groupName &&
2352  activeGroup.second.second == groupKey)
2353  {
2354  __SUP_COUTT__ << "Skipping target active group." << __E__;
2355  continue;
2356  }
2357  __SUP_COUTT__ << "Loading " << activeGroup.first << " "
2358  << activeGroup.second.first << "(" << activeGroup.second.second
2359  << ")..." << __E__;
2360  try
2361  {
2362  cfgMgr->loadTableGroup(activeGroup.second.first,
2363  activeGroup.second.second,
2364  false /*doActivate*/
2365  );
2366  }
2367  catch(...) //ignore errors
2368  {
2369  __SUP_COUT__ << "Ignoring errors while setting up active tables for "
2370  << activeGroup.second.first << "("
2371  << activeGroup.second.second << ")..." << __E__;
2372  }
2373  } //end load tables as active, but do not activate groups
2374  }
2375  __SUP_COUTTV__(StringMacros::mapToString(cfgMgr->getActiveVersions()));
2376 
2377  const std::map<std::string, TableInfo>& allTableInfo = cfgMgr->getAllTableInfo();
2378 
2379  std::map<std::string /*name*/, TableVersion /*version*/> modifiedTablesMap;
2380  std::map<std::string /*name*/, TableVersion /*version*/>::iterator
2381  modifiedTablesMapIt;
2382 
2383  if(usingActiveGroups)
2384  {
2385  // no need to load a target group
2386  __SUP_COUT__ << "Using active groups." << __E__;
2387  }
2388  else
2389  {
2390  __SUP_COUT__ << "Loading group '" << groupName << "(" << groupKey << ")'"
2391  << __E__;
2392 
2393  std::string groupComment, groupAuthor, tableGroupCreationTime, groupType;
2394 
2395  // only same member map if object pointer was passed
2396  cfgMgr->loadTableGroup(groupName,
2397  groupKey,
2398  false /*doActivate*/,
2399  returnMemberMap,
2400  0 /*progressBar*/,
2401  accumulatedErrors,
2402  doGetGroupInfo ? &groupComment : 0,
2403  doGetGroupInfo ? &groupAuthor : 0,
2404  doGetGroupInfo ? &tableGroupCreationTime : 0,
2405  false /*doNotLoadMembers*/,
2406  doGetGroupInfo ? &groupType : 0);
2407 
2408  if(doGetGroupInfo)
2409  {
2410  xmlOut.addTextElementToData("tableGroupComment", groupComment);
2411  xmlOut.addTextElementToData("tableGroupAuthor", groupAuthor);
2412  xmlOut.addTextElementToData("tableGroupCreationTime", tableGroupCreationTime);
2413  xmlOut.addTextElementToData("tableGroupType", groupType);
2414  }
2415 
2416  if(accumulatedErrors && *accumulatedErrors != "")
2417  __SUP_COUTV__(*accumulatedErrors);
2418  }
2419  __SUP_COUTTV__(StringMacros::mapToString(cfgMgr->getActiveVersions()));
2420 
2421  // extract modified tables
2422  {
2423  std::istringstream f(modifiedTables);
2424  std::string table, version;
2425  while(getline(f, table, ','))
2426  {
2427  getline(f, version, ',');
2428  modifiedTablesMap.insert(
2429  std::pair<std::string /*name*/, TableVersion /*version*/>(
2430  table, TableVersion(version)));
2431  }
2432  //__SUP_COUT__ << modifiedTables << __E__;
2433  for(auto& pair : modifiedTablesMap)
2434  __SUP_COUT__ << "modified table " << pair.first << ":" << pair.second
2435  << __E__;
2436  }
2437 
2438  // add all active table pairs to xmlOut
2439  std::map<std::string, TableVersion> allActivePairs = cfgMgr->getActiveVersions();
2440  xmlOut.addTextElementToData("DefaultNoLink",
2441  TableViewColumnInfo::DATATYPE_LINK_DEFAULT);
2442 
2443  // construct specially ordered table name set
2444  std::set<std::string, StringMacros::IgnoreCaseCompareStruct> orderedTableSet;
2445  for(const auto& tablePair : allActivePairs)
2446  orderedTableSet.emplace(tablePair.first);
2447 
2448  std::map<std::string, TableInfo>::const_iterator tableInfoIt;
2449  for(auto& orderedTableName : orderedTableSet)
2450  {
2451  tableInfoIt = allTableInfo.find(orderedTableName);
2452  if(tableInfoIt == allTableInfo.end())
2453  {
2454  __SS__ << "Impossible missing table in map '" << orderedTableName << "'"
2455  << __E__;
2456  __SS_THROW__;
2457  }
2458 
2459  if(outputActiveTables)
2460  xmlOut.addTextElementToData("ActiveTableName", orderedTableName);
2461 
2462  // check if name is in modifiedTables
2463  // if so, activate the temporary version
2464  if((modifiedTablesMapIt = modifiedTablesMap.find(orderedTableName)) !=
2465  modifiedTablesMap.end())
2466  {
2467  __SUP_COUT__ << "Found modified table " << (*modifiedTablesMapIt).first
2468  << ": trying... " << (*modifiedTablesMapIt).second << __E__;
2469 
2470  try
2471  {
2472  tableInfoIt->second.tablePtr_->setActiveView(
2473  (*modifiedTablesMapIt).second);
2474  }
2475  catch(...)
2476  {
2477  __SUP_SS__ << "Modified table version v" << (*modifiedTablesMapIt).second
2478  << " failed. Reverting to v"
2479  << tableInfoIt->second.tablePtr_->getView().getVersion() << "."
2480  << __E__;
2481  __SUP_COUT_WARN__ << "Warning detected!\n\n " << ss.str() << __E__;
2482  xmlOut.addTextElementToData(
2483  "Warning",
2484  "Error setting up active tables!\n\n" + std::string(ss.str()));
2485  }
2486  }
2487 
2488  if(outputActiveTables)
2489  {
2490  xmlOut.addTextElementToData(
2491  "ActiveTableVersion",
2492  tableInfoIt->second.tablePtr_->getView().getVersion().toString());
2493  xmlOut.addTextElementToData(
2494  "ActiveTableComment",
2495  tableInfoIt->second.tablePtr_->getView().getAuthor() + ": " +
2496  tableInfoIt->second.tablePtr_->getView().getComment());
2497  }
2498 
2499  } // end ordered table loop
2500 
2501  __SUP_COUTTV__(StringMacros::mapToString(cfgMgr->getActiveVersions()));
2502 
2503 } // end setupActiveTablesXML()
2504 catch(std::runtime_error& e)
2505 {
2506  __SUP_SS__ << ("Error setting up active tables!\n\n" + std::string(e.what()))
2507  << __E__;
2508  __SUP_COUT_ERR__ << "\n" << ss.str();
2509  xmlOut.addTextElementToData("Error", ss.str());
2510  throw; // throw to get info from special errors at a parent level
2511 }
2512 catch(...)
2513 {
2514  __SUP_SS__ << ("Error setting up active tables!\n\n") << __E__;
2515  try
2516  {
2517  throw;
2518  } //one more try to printout extra info
2519  catch(const std::exception& e)
2520  {
2521  ss << "Exception message: " << e.what();
2522  }
2523  catch(...)
2524  {
2525  }
2526  __SUP_COUT_ERR__ << "\n" << ss.str();
2527  xmlOut.addTextElementToData("Error", ss.str());
2528  throw; // throw to get info from special errors at a parent level
2529 } // end setupActiveTablesXML() throw
2530 
2531 //==============================================================================
2546 void ConfigurationGUISupervisor::handleFillCreateTreeNodeRecordsXML(
2547  HttpXmlDocument& xmlOut,
2548  ConfigurationManagerRW* cfgMgr,
2549  const std::string& groupName,
2550  const TableGroupKey& groupKey,
2551  const std::string& startPath,
2552  const std::string& modifiedTables,
2553  const std::string& recordList,
2554  const std::string& author)
2555 {
2556  // setup active tables based on input group and modified tables
2557  setupActiveTablesXML(xmlOut,
2558  cfgMgr,
2559  groupName,
2560  groupKey,
2561  modifiedTables,
2562  true /* refresh all */,
2563  false /* getGroupInfo */,
2564  0 /* returnMemberMap */,
2565  false /* outputActiveTables */);
2566 
2567  try
2568  {
2569  ConfigurationTree targetNode = cfgMgr->getNode(startPath);
2570  TableBase* table = cfgMgr->getTableByName(targetNode.getTableName());
2571 
2572  __SUP_COUT__ << table->getTableName() << __E__;
2573  TableVersion temporaryVersion;
2574 
2575  // if current version is not temporary
2576  // create temporary
2577  // else re-modify temporary version
2578  // edit temporary version directly
2579  // then after all edits return active versions
2580  //
2581 
2582  bool firstSave = true;
2583 
2584  // save current version
2585  TableView backupView(targetNode.getTableName());
2586 
2587  // extract record list
2588  {
2589  std::istringstream f(recordList);
2590  std::string recordUID;
2591 
2592  while(getline(f, recordUID, ',')) // for each record
2593  {
2594  recordUID = StringMacros::decodeURIComponent(recordUID);
2595 
2596  __SUP_COUT__ << "recordUID " << recordUID << __E__;
2597 
2598  if(firstSave) // handle version bookkeeping
2599  {
2600  if(!(temporaryVersion = targetNode.getTableVersion())
2601  .isTemporaryVersion())
2602  {
2603  __SUP_COUT__ << "Start version " << temporaryVersion << __E__;
2604  // create temporary version for editing
2605  temporaryVersion = table->createTemporaryView(temporaryVersion);
2606  cfgMgr->saveNewTable(
2607  targetNode.getTableName(),
2608  temporaryVersion,
2609  true); // proper bookkeeping for temporary version with the new version
2610 
2611  __SUP_COUT__ << "Created temporary version " << temporaryVersion
2612  << __E__;
2613  }
2614  else // else table is already temporary version
2615  __SUP_COUT__ << "Using temporary version " << temporaryVersion
2616  << __E__;
2617 
2618  firstSave = false;
2619 
2620  // copy original to backup before modifying
2621  backupView.copy(table->getView(), temporaryVersion, author);
2622  }
2623 
2624  // at this point have valid temporary version to edit
2625 
2626  // copy "table-newRow" type edit from handleSaveTreeNodeEditXML()
2627  // functionality
2628 
2629  // add row
2630  unsigned int row = table->getViewP()->addRow(
2631  author,
2632  true /*incrementUniqueData*/); // increment all unique data fields to void conflict
2633 
2634  // if TableViewColumnInfo::COL_NAME_STATUS exists, set it to true
2635  try
2636  {
2637  unsigned int col = table->getViewP()->getColStatus();
2638  table->getViewP()->setURIEncodedValue("1", row, col);
2639  }
2640  catch(...)
2641  {
2642  } // if not, ignore
2643 
2644  // set UID value
2645  table->getViewP()->setURIEncodedValue(
2646  recordUID, row, table->getViewP()->getColUID());
2647  }
2648  }
2649 
2650  if(!firstSave) // only test table if there was a change
2651  {
2652  try
2653  {
2654  table->getViewP()->init(); // verify new table (throws runtime_errors)
2655  }
2656  catch(...)
2657  {
2658  __SUP_COUT_INFO__ << "Reverting to original view." << __E__;
2659  __SUP_COUT__ << "Before:" << __E__;
2660  table->getViewP()->print();
2661  table->getViewP()->copy(backupView, temporaryVersion, author);
2662  __SUP_COUT__ << "After:" << __E__;
2663  table->getViewP()->print();
2664 
2665  throw; // rethrow
2666  }
2667  }
2668 
2669  handleFillModifiedTablesXML(xmlOut, cfgMgr);
2670  }
2671  catch(std::runtime_error& e)
2672  {
2673  __SUP_SS__ << ("Error creating new record(s)!\n\n" + std::string(e.what()))
2674  << __E__;
2675  __SUP_COUT_ERR__ << "\n" << ss.str();
2676  xmlOut.addTextElementToData("Error", ss.str());
2677  }
2678  catch(...)
2679  {
2680  __SUP_SS__ << ("Error creating new record(s)!\n\n") << __E__;
2681  try
2682  {
2683  throw;
2684  } //one more try to printout extra info
2685  catch(const std::exception& e)
2686  {
2687  ss << "Exception message: " << e.what();
2688  }
2689  catch(...)
2690  {
2691  }
2692  __SUP_COUT_ERR__ << "\n" << ss.str();
2693  xmlOut.addTextElementToData("Error", ss.str());
2694  }
2695 } //end handleFillCreateTreeNodeRecordsXML()
2696 
2697 //==============================================================================
2700 void ConfigurationGUISupervisor::handleFillModifiedTablesXML(
2701  HttpXmlDocument& xmlOut, ConfigurationManagerRW* cfgMgr)
2702 try
2703 {
2704  // return modified <modified tables>
2705  const std::map<std::string, TableInfo>& allTableInfo = cfgMgr->getAllTableInfo();
2706  std::map<std::string, TableVersion> allActivePairs = cfgMgr->getActiveVersions();
2707  for(auto& activePair : allActivePairs)
2708  {
2709  xmlOut.addTextElementToData("NewActiveTableName", activePair.first);
2710  xmlOut.addTextElementToData("NewActiveTableVersion",
2711  allTableInfo.at(activePair.first)
2712  .tablePtr_->getView()
2713  .getVersion()
2714  .toString());
2715  xmlOut.addTextElementToData(
2716  "NewActiveTableComment",
2717  allTableInfo.at(activePair.first).tablePtr_->getView().getAuthor() + ": " +
2718  allTableInfo.at(activePair.first).tablePtr_->getView().getComment());
2719  }
2720 } //end handleFillModifiedTablesXML()
2721 catch(std::runtime_error& e)
2722 {
2723  __SUP_SS__ << ("Error!\n\n" + std::string(e.what())) << __E__;
2724  __SUP_COUT_ERR__ << "\n" << ss.str();
2725  xmlOut.addTextElementToData("Error", ss.str());
2726 }
2727 catch(...)
2728 {
2729  __SUP_SS__ << ("Error!\n\n") << __E__;
2730  try
2731  {
2732  throw;
2733  } //one more try to printout extra info
2734  catch(const std::exception& e)
2735  {
2736  ss << "Exception message: " << e.what();
2737  }
2738  catch(...)
2739  {
2740  }
2741  __SUP_COUT_ERR__ << "\n" << ss.str();
2742  xmlOut.addTextElementToData("Error", ss.str());
2743 } //end handleFillModifiedTablesXML() catch
2744 
2745 //==============================================================================
2760 void ConfigurationGUISupervisor::handleFillDeleteTreeNodeRecordsXML(
2761  HttpXmlDocument& xmlOut,
2762  ConfigurationManagerRW* cfgMgr,
2763  const std::string& groupName,
2764  const TableGroupKey& groupKey,
2765  const std::string& startPath,
2766  const std::string& modifiedTables,
2767  const std::string& recordList)
2768 {
2769  // setup active tables based on input group and modified tables
2770  setupActiveTablesXML(xmlOut,
2771  cfgMgr,
2772  groupName,
2773  groupKey,
2774  modifiedTables,
2775  true /* refresh all */,
2776  false /* getGroupInfo */,
2777  0 /* returnMemberMap */,
2778  false /* outputActiveTables */);
2779 
2780  try
2781  {
2782  ConfigurationTree targetNode = cfgMgr->getNode(startPath);
2783  TableBase* table = cfgMgr->getTableByName(targetNode.getTableName());
2784 
2785  __SUP_COUT__ << table->getTableName() << __E__;
2786  TableVersion temporaryVersion;
2787 
2788  // if current version is not temporary
2789  // create temporary
2790  // else re-modify temporary version
2791  // edit temporary version directly
2792  // then after all edits return active versions
2793  //
2794 
2795  bool firstSave = true;
2796 
2797  // extract record list
2798  {
2799  std::istringstream f(recordList);
2800  std::string recordUID;
2801  // unsigned int i;
2802 
2803  while(getline(f, recordUID, ',')) // for each record
2804  {
2805  recordUID = StringMacros::decodeURIComponent(recordUID);
2806 
2807  __SUP_COUT__ << "recordUID " << recordUID << __E__;
2808 
2809  if(firstSave) // handle version bookkeeping
2810  {
2811  if(!(temporaryVersion = targetNode.getTableVersion())
2812  .isTemporaryVersion())
2813  {
2814  __SUP_COUT__ << "Start version " << temporaryVersion << __E__;
2815  // create temporary version for editing
2816  temporaryVersion = table->createTemporaryView(temporaryVersion);
2817  cfgMgr->saveNewTable(
2818  targetNode.getTableName(),
2819  temporaryVersion,
2820  true); // proper bookkeeping for temporary version with the new version
2821 
2822  __SUP_COUT__ << "Created temporary version " << temporaryVersion
2823  << __E__;
2824  }
2825  else // else table is already temporary version
2826  __SUP_COUT__ << "Using temporary version " << temporaryVersion
2827  << __E__;
2828 
2829  firstSave = false;
2830  }
2831 
2832  // at this point have valid temporary version to edit
2833 
2834  // copy "delete-uid" type edit from handleSaveTreeNodeEditXML()
2835  // functionality
2836  unsigned int row =
2837  table->getViewP()->findRow(table->getViewP()->getColUID(), recordUID);
2838  table->getViewP()->deleteRow(row);
2839  }
2840  }
2841 
2842  if(!firstSave) // only test table if there was a change
2843  table->getViewP()->init(); // verify new table (throws runtime_errors)
2844 
2845  handleFillModifiedTablesXML(xmlOut, cfgMgr);
2846  }
2847  catch(std::runtime_error& e)
2848  {
2849  __SUP_SS__ << ("Error removing record(s)!\n\n" + std::string(e.what())) << __E__;
2850  __SUP_COUT_ERR__ << "\n" << ss.str();
2851  xmlOut.addTextElementToData("Error", ss.str());
2852  }
2853  catch(...)
2854  {
2855  __SUP_SS__ << ("Error removing record(s)!\n\n") << __E__;
2856  try
2857  {
2858  throw;
2859  } //one more try to printout extra info
2860  catch(const std::exception& e)
2861  {
2862  ss << "Exception message: " << e.what();
2863  }
2864  catch(...)
2865  {
2866  }
2867  __SUP_COUT_ERR__ << "\n" << ss.str();
2868  xmlOut.addTextElementToData("Error", ss.str());
2869  }
2870 } // end handleFillDeleteTreeNodeRecordsXML()
2871 
2872 //==============================================================================
2888 void ConfigurationGUISupervisor::handleFillRenameTreeNodeRecordsXML(
2889  HttpXmlDocument& xmlOut,
2890  ConfigurationManagerRW* cfgMgr,
2891  const std::string& groupName,
2892  const TableGroupKey& groupKey,
2893  const std::string& startPath,
2894  const std::string& modifiedTables,
2895  const std::string& recordList,
2896  const std::string& newRecordList)
2897 {
2898  // setup active tables based on input group and modified tables
2899  setupActiveTablesXML(xmlOut,
2900  cfgMgr,
2901  groupName,
2902  groupKey,
2903  modifiedTables,
2904  true /* refresh all */,
2905  false /* getGroupInfo */,
2906  0 /* returnMemberMap */,
2907  false /* outputActiveTables */);
2908 
2909  try
2910  {
2911  ConfigurationTree targetNode = cfgMgr->getNode(startPath);
2912  TableBase* table = cfgMgr->getTableByName(targetNode.getTableName());
2913 
2914  __SUP_COUT__ << table->getTableName() << __E__;
2915  TableVersion temporaryVersion;
2916 
2917  // if current version is not temporary
2918  // create temporary
2919  // else re-modify temporary version
2920  // edit temporary version directly
2921  // then after all edits return active versions
2922  //
2923 
2924  // extract record list
2925  std::vector<std::string> recordArray =
2927  std::vector<std::string> newRecordArray =
2928  StringMacros::getVectorFromString(newRecordList);
2929 
2930  __SUP_COUTV__(StringMacros::vectorToString(recordArray));
2931  __SUP_COUTV__(StringMacros::vectorToString(newRecordArray));
2932 
2933  if(recordArray.size() == 0 || recordArray.size() != newRecordArray.size())
2934  {
2935  __SUP_SS__
2936  << "Invalid record size vs new record name size, they must be the same: "
2937  << recordArray.size() << " vs " << newRecordArray.size() << __E__;
2938  __SUP_SS_THROW__;
2939  }
2940 
2941  // handle version bookkeeping
2942  {
2943  if(!(temporaryVersion = targetNode.getTableVersion()).isTemporaryVersion())
2944  {
2945  __SUP_COUT__ << "Start version " << temporaryVersion << __E__;
2946  // create temporary version for editing
2947  temporaryVersion = table->createTemporaryView(temporaryVersion);
2948  cfgMgr->saveNewTable(
2949  targetNode.getTableName(),
2950  temporaryVersion,
2951  true); // proper bookkeeping for temporary version with the new version
2952 
2953  __SUP_COUT__ << "Created temporary version " << temporaryVersion << __E__;
2954  }
2955  else // else table is already temporary version
2956  __SUP_COUT__ << "Using temporary version " << temporaryVersion << __E__;
2957  }
2958 
2959  // at this point have valid temporary version to edit
2960 
2961  // for every record, change name
2962  unsigned int row;
2963  for(unsigned int i = 0; i < recordArray.size(); ++i)
2964  {
2965  row = table->getViewP()->findRow(
2966  table->getViewP()->getColUID(),
2967  StringMacros::decodeURIComponent(recordArray[i]));
2968 
2969  table->getViewP()->setValueAsString(
2970  newRecordArray[i], row, table->getViewP()->getColUID());
2971  }
2972 
2973  table->getViewP()->init(); // verify new table (throws runtime_errors)
2974 
2975  handleFillModifiedTablesXML(xmlOut, cfgMgr);
2976  }
2977  catch(std::runtime_error& e)
2978  {
2979  __SUP_SS__ << ("Error renaming record(s)!\n\n" + std::string(e.what())) << __E__;
2980  __SUP_COUT_ERR__ << "\n" << ss.str();
2981  xmlOut.addTextElementToData("Error", ss.str());
2982  }
2983  catch(...)
2984  {
2985  __SUP_SS__ << ("Error renaming record(s)!\n\n") << __E__;
2986  try
2987  {
2988  throw;
2989  } //one more try to printout extra info
2990  catch(const std::exception& e)
2991  {
2992  ss << "Exception message: " << e.what();
2993  }
2994  catch(...)
2995  {
2996  }
2997  __SUP_COUT_ERR__ << "\n" << ss.str();
2998  xmlOut.addTextElementToData("Error", ss.str());
2999  }
3000 } // end handleFillRenameTreeNodeRecordsXML()
3001 
3002 //==============================================================================
3019 void ConfigurationGUISupervisor::handleFillCopyTreeNodeRecordsXML(
3020  HttpXmlDocument& xmlOut,
3021  ConfigurationManagerRW* cfgMgr,
3022  const std::string& groupName,
3023  const TableGroupKey& groupKey,
3024  const std::string& startPath,
3025  const std::string& modifiedTables,
3026  const std::string& recordList,
3027  unsigned int numberOfCopies /* = 1 */)
3028 {
3029  if(!numberOfCopies)
3030  numberOfCopies = 1; // force 0 to 1, assuming user meant to get one copy
3031 
3032  // setup active tables based on input group and modified tables
3033  setupActiveTablesXML(xmlOut,
3034  cfgMgr,
3035  groupName,
3036  groupKey,
3037  modifiedTables,
3038  true /* refresh all */,
3039  false /* getGroupInfo */,
3040  0 /* returnMemberMap */,
3041  false /* outputActiveTables */);
3042 
3043  try
3044  {
3045  ConfigurationTree targetNode = cfgMgr->getNode(startPath);
3046  TableBase* table = cfgMgr->getTableByName(targetNode.getTableName());
3047 
3048  __SUP_COUT__ << table->getTableName() << __E__;
3049  TableVersion temporaryVersion;
3050 
3051  // if current version is not temporary
3052  // create temporary
3053  // else re-modify temporary version
3054  // edit temporary version directly
3055  // then after all edits return active versions
3056  //
3057 
3058  // extract record list
3059  std::vector<std::string> recordArray =
3061  __SUP_COUTV__(StringMacros::vectorToString(recordArray));
3062 
3063  // handle version bookkeeping
3064  {
3065  if(!(temporaryVersion = targetNode.getTableVersion()).isTemporaryVersion())
3066  {
3067  __SUP_COUT__ << "Start version " << temporaryVersion << __E__;
3068  // create temporary version for editing
3069  temporaryVersion = table->createTemporaryView(temporaryVersion);
3070  cfgMgr->saveNewTable(
3071  targetNode.getTableName(),
3072  temporaryVersion,
3073  true); // proper bookkeeping for temporary version with the new version
3074 
3075  __SUP_COUT__ << "Created temporary version " << temporaryVersion << __E__;
3076  }
3077  else // else table is already temporary version
3078  __SUP_COUT__ << "Using temporary version " << temporaryVersion << __E__;
3079  }
3080 
3081  // at this point have valid temporary version to edit
3082 
3083  // for every record, copy spec'd number of times
3084  unsigned int row;
3085  for(const auto& recordUID : recordArray)
3086  {
3087  row = table->getViewP()->findRow(table->getViewP()->getColUID(),
3089  for(unsigned int i = 0; i < numberOfCopies; ++i)
3090  table->getViewP()->copyRows(
3091  cfgMgr->getUsername(),
3092  table->getView(),
3093  row,
3094  1 /*srcRowsToCopy*/,
3095  -1 /*destOffsetRow*/,
3096  true /*generateUniqueDataColumns*/,
3097  recordUID /*baseNameAutoUID*/); // make the name similar end record loop
3098  }
3099 
3100  table->getViewP()->init(); // verify new table (throws runtime_errors)
3101 
3102  handleFillModifiedTablesXML(xmlOut, cfgMgr);
3103  }
3104  catch(std::runtime_error& e)
3105  {
3106  __SUP_SS__ << ("Error copying record(s)!\n\n" + std::string(e.what())) << __E__;
3107  __SUP_COUT_ERR__ << "\n" << ss.str();
3108  xmlOut.addTextElementToData("Error", ss.str());
3109  }
3110  catch(...)
3111  {
3112  __SUP_SS__ << ("Error copying record(s)!\n\n") << __E__;
3113  try
3114  {
3115  throw;
3116  } //one more try to printout extra info
3117  catch(const std::exception& e)
3118  {
3119  ss << "Exception message: " << e.what();
3120  }
3121  catch(...)
3122  {
3123  }
3124  __SUP_COUT_ERR__ << "\n" << ss.str();
3125  xmlOut.addTextElementToData("Error", ss.str());
3126  }
3127 } // end handleFillCopyTreeNodeRecordsXML()
3128 
3129 //==============================================================================
3146 void ConfigurationGUISupervisor::handleFillSetTreeNodeFieldValuesXML(
3147  HttpXmlDocument& xmlOut,
3148  ConfigurationManagerRW* cfgMgr,
3149  const std::string& groupName,
3150  const TableGroupKey& groupKey,
3151  const std::string& startPath,
3152  const std::string& modifiedTables,
3153  const std::string& recordList,
3154  const std::string& fieldList,
3155  const std::string& valueList,
3156  const std::string& author)
3157 {
3158  // setup active tables based on input group and modified tables
3159  setupActiveTablesXML(xmlOut,
3160  cfgMgr,
3161  groupName,
3162  groupKey,
3163  modifiedTables,
3164  true /* refresh all */,
3165  false /* getGroupInfo */,
3166  0 /* returnMemberMap */,
3167  false /* outputActiveTables */);
3168 
3169  // for each field
3170  // return field/value pair in xml
3171 
3172  try
3173  {
3174  std::vector<std::string /*relative-path*/> fieldPaths;
3175  // extract field list
3176  {
3177  std::istringstream f(fieldList);
3178  std::string fieldPath;
3179  while(getline(f, fieldPath, ','))
3180  {
3181  fieldPaths.push_back(StringMacros::decodeURIComponent(fieldPath));
3182  }
3183  __SUP_COUT__ << fieldList << __E__;
3184  for(const auto& field : fieldPaths)
3185  __SUP_COUT__ << "fieldPath " << field << __E__;
3186  }
3187 
3188  std::vector<std::string /*relative-path*/> fieldValues;
3189  // extract value list
3190  {
3191  std::istringstream f(valueList);
3192  std::string fieldValue;
3193  while(getline(f, fieldValue, ','))
3194  {
3195  fieldValues.push_back(fieldValue); // setURIEncodedValue is expected
3196  // StringMacros::decodeURIComponent(fieldValue));
3197  }
3198 
3199  // if last value is "" then push empty value
3200  if(valueList.size() && valueList[valueList.size() - 1] == ',')
3201  fieldValues.push_back("");
3202 
3203  __SUP_COUT__ << valueList << __E__;
3204  for(const auto& value : fieldValues)
3205  __SUP_COUT__ << "fieldValue " << value << __E__;
3206  }
3207 
3208  if(fieldPaths.size() != fieldValues.size())
3209  {
3210  __SUP_SS__;
3211  __THROW__(ss.str() + "Mismatch in fields and values array size!");
3212  }
3213 
3214  // extract record list
3215  {
3216  TableBase* table;
3217  TableVersion temporaryVersion;
3218  std::istringstream f(recordList);
3219  std::string recordUID;
3220  unsigned int i;
3221 
3222  while(getline(f, recordUID, ',')) // for each record
3223  {
3224  recordUID = StringMacros::decodeURIComponent(recordUID);
3225 
3226  /*xercesc::DOMElement* parentEl =*/
3227  xmlOut.addTextElementToData("fieldValues", recordUID);
3228 
3229  // for each field, set value
3230  for(i = 0; i < fieldPaths.size(); ++i)
3231  {
3232  __SUP_COUT__ << "fieldPath " << fieldPaths[i] << __E__;
3233  __SUP_COUT__ << "fieldValue " << fieldValues[i] << __E__;
3234 
3235  // doNotThrowOnBrokenUIDLinks so that link UIDs can be edited like
3236  // other fields
3237  ConfigurationTree targetNode =
3238  cfgMgr->getNode(startPath + "/" + recordUID + "/" + fieldPaths[i],
3239  true /*doNotThrowOnBrokenUIDLinks*/);
3240 
3241  // need table, uid, columnName to set a value
3242 
3243  // assume correct table version is loaded by setupActiveTablesXML()
3244  // table = cfgMgr->getTableByName(
3245  // targetNode.getTableName());
3246  //
3247  //__SUP_COUT__ << "Active version is " << table->getViewVersion() <<
3248  //__E__;
3249 
3250  // mimic handleSaveTreeNodeEditXML L 1750
3251  // Actually call it! ..
3252  // with a modifier?
3253  // or
3254  // handleSaveTreeNodeEditXML(xmlOut,
3255  // cfgMgr,
3256  // targetNode.getTableName(),
3257  // targetNode.getTableVersion(),
3258  // "value",
3259  // targetNode.getUIDAsString(),
3260  // targetNode.getValueName(), //col name
3261  // fieldValues[i]
3262  // );
3263 
3264  // or
3265  // (because problem is this would create a new temporary version each
3266  // time) if current version is not temporary
3267  // create temporary
3268  // else re-modify temporary version
3269  // edit temporary version directly
3270  // then after all edits return active versions
3271  //
3272 
3273  __SUP_COUT__ << "Getting table " << targetNode.getFieldTableName()
3274  << __E__;
3275 
3276  // if link must get parent table name
3277  table = cfgMgr->getTableByName(
3278  targetNode.getFieldTableName()); // NOT getTableName!
3279  if(!(temporaryVersion = table->getViewP()->getVersion())
3280  .isTemporaryVersion())
3281  {
3282  // create temporary version for editing
3283  temporaryVersion =
3284  table->createTemporaryView(table->getViewP()->getVersion());
3285  cfgMgr->saveNewTable(table->getTableName(),
3286  temporaryVersion,
3287  true); // proper bookkeeping for temporary
3288  // version with the new version
3289 
3290  __SUP_COUT__ << "Created temporary version "
3291  << table->getTableName() << "-v" << temporaryVersion
3292  << __E__;
3293  }
3294  // else //else table is already temporary version
3295  __SUP_COUT__ << "Using temporary version " << table->getTableName()
3296  << "-v" << temporaryVersion << __E__;
3297 
3298  // copy "value" type edit from handleSaveTreeNodeEditXML()
3299  // functionality
3300  table->getViewP()->setURIEncodedValue(fieldValues[i],
3301  targetNode.getFieldRow(),
3302  targetNode.getFieldColumn(),
3303  author);
3304 
3305  table->getViewP()
3306  ->init(); // verify new table (throws runtime_errors)
3307  }
3308  }
3309  }
3310 
3311  handleFillModifiedTablesXML(xmlOut, cfgMgr);
3312  }
3313  catch(std::runtime_error& e)
3314  {
3315  __SUP_SS__ << ("Error setting field values!\n\n" + std::string(e.what()))
3316  << __E__;
3317  __SUP_COUT_ERR__ << "\n" << ss.str();
3318  xmlOut.addTextElementToData("Error", ss.str());
3319  }
3320  catch(...)
3321  {
3322  __SUP_SS__ << ("Error setting field values!\n\n") << __E__;
3323  try
3324  {
3325  throw;
3326  } //one more try to printout extra info
3327  catch(const std::exception& e)
3328  {
3329  ss << "Exception message: " << e.what();
3330  }
3331  catch(...)
3332  {
3333  }
3334  __SUP_COUT_ERR__ << "\n" << ss.str();
3335  xmlOut.addTextElementToData("Error", ss.str());
3336  }
3337 } //end handleFillSetTreeNodeFieldValuesXML()
3338 
3339 //==============================================================================
3354 void ConfigurationGUISupervisor::handleFillGetTreeNodeFieldValuesXML(
3355  HttpXmlDocument& xmlOut,
3356  ConfigurationManagerRW* cfgMgr,
3357  const std::string& groupName,
3358  const TableGroupKey& groupKey,
3359  const std::string& startPath,
3360  const std::string& modifiedTables,
3361  const std::string& recordList,
3362  const std::string& fieldList)
3363 {
3364  // setup active tables based on input group and modified tables
3365  setupActiveTablesXML(
3366  xmlOut, cfgMgr, groupName, groupKey, modifiedTables, false /* refreshAll */);
3367 
3368  // for each field
3369  // return field/value pair in xml
3370 
3371  try
3372  {
3373  std::vector<std::string /*relative-path*/> fieldPaths;
3374  // extract field list
3375  {
3376  std::istringstream f(fieldList);
3377  std::string fieldPath;
3378  while(getline(f, fieldPath, ','))
3379  {
3380  fieldPaths.push_back(StringMacros::decodeURIComponent(fieldPath));
3381  }
3382  __SUP_COUT__ << fieldList << __E__;
3383  }
3384 
3385  // extract record list
3386  {
3387  std::istringstream f(recordList);
3388  std::string recordUID;
3389  while(getline(f, recordUID, ',')) // for each record
3390  {
3391  recordUID = StringMacros::decodeURIComponent(recordUID);
3392 
3393  __SUP_COUT__ << "recordUID " << recordUID << __E__;
3394 
3395  xercesc::DOMElement* parentEl =
3396  xmlOut.addTextElementToData("fieldValues", recordUID);
3397 
3398  // for each field, get value
3399  for(const auto& fieldPath : fieldPaths)
3400  {
3401  // __SUP_COUT__ << "fieldPath " << fieldPath << __E__;
3402  // __SUP_COUT__ << "fullPath " << (startPath + "/" + recordUID + "/" + fieldPath) << __E__;
3403 
3404  ConfigurationTree node =
3405  cfgMgr->getNode(startPath + "/" + recordUID + "/" + fieldPath);
3406 
3407  xmlOut.addTextElementToParent("FieldPath", fieldPath, parentEl);
3408 
3409  xmlOut.addTextElementToParent(
3410  "FieldValue",
3411  node.getValueAsString(true /*returnLinkTableValue*/),
3412  parentEl);
3413  }
3414  }
3415  }
3416  }
3417  catch(std::runtime_error& e)
3418  {
3419  __SUP_SS__ << ("Error getting field values!\n\n" + std::string(e.what()))
3420  << __E__;
3421  __SUP_COUT_ERR__ << "\n" << ss.str();
3422  xmlOut.addTextElementToData("Error", ss.str());
3423  }
3424  catch(...)
3425  {
3426  __SUP_SS__ << ("Error getting field values!\n\n") << __E__;
3427  try
3428  {
3429  throw;
3430  } //one more try to printout extra info
3431  catch(const std::exception& e)
3432  {
3433  ss << "Exception message: " << e.what();
3434  }
3435  catch(...)
3436  {
3437  }
3438  __SUP_COUT_ERR__ << "\n" << ss.str();
3439  xmlOut.addTextElementToData("Error", ss.str());
3440  }
3441 } //end handleFillGetTreeNodeFieldValuesXML()
3442 
3443 //==============================================================================
3462 void ConfigurationGUISupervisor::handleFillTreeNodeCommonFieldsXML(
3463  HttpXmlDocument& xmlOut,
3464  ConfigurationManagerRW* cfgMgr,
3465  const std::string& groupName,
3466  const TableGroupKey& groupKey,
3467  const std::string& startPath,
3468  unsigned int depth,
3469  const std::string& modifiedTables,
3470  const std::string& recordList,
3471  const std::string& fieldList)
3472 {
3473  // setup active tables based on input group and modified tables
3474  setupActiveTablesXML(
3475  xmlOut, cfgMgr, groupName, groupKey, modifiedTables, false /* refreshAll */);
3476 
3477  try
3478  {
3479  xercesc::DOMElement* parentEl = xmlOut.addTextElementToData("fields", startPath);
3480 
3481  if(depth == 0)
3482  {
3483  __SUP_SS__ << "Depth of search must be greater than 0." << __E__;
3484  __SUP_COUT__ << ss.str();
3485  __SS_THROW__; // done if 0 depth, no fields
3486  }
3487 
3488  // do not allow traversing for common fields from root level
3489  // the tree view should be used for such a purpose
3490  // if(startPath == "/")
3491  // return;
3492 
3493  std::vector<ConfigurationTree::RecordField> retFieldList;
3494 
3495  {
3496  ConfigurationTree startNode = cfgMgr->getNode(startPath);
3497  if(startNode.isLinkNode() && startNode.isDisconnected())
3498  {
3499  __SUP_SS__ << "Start path was a disconnected link node!" << __E__;
3500  __SUP_SS_THROW__;
3501  return; // quietly ignore disconnected links at depth
3502  // note: at the root level they will be flagged for the user
3503  }
3504 
3505  std::vector<std::string /*relative-path*/> fieldAcceptList, fieldRejectList;
3506  if(fieldList != "")
3507  {
3508  // extract field filter list
3509  {
3510  std::istringstream f(fieldList);
3511  std::string fieldPath, decodedFieldPath;
3512  while(getline(f, fieldPath, ','))
3513  {
3514  decodedFieldPath = StringMacros::decodeURIComponent(fieldPath);
3515 
3516  if(decodedFieldPath[0] == '!') // reject field
3517  fieldRejectList.push_back(decodedFieldPath.substr(1));
3518  else
3519  fieldAcceptList.push_back(decodedFieldPath);
3520  }
3521  __SUP_COUT__ << fieldList << __E__;
3522  for(auto& field : fieldAcceptList)
3523  __SUP_COUT__ << "fieldAcceptList " << field << __E__;
3524  for(auto& field : fieldRejectList)
3525  __SUP_COUT__ << "fieldRejectList " << field << __E__;
3526  }
3527  }
3528 
3529  std::vector<std::string /*relative-path*/> records;
3530  if(recordList == "*") // handle all records case
3531  {
3532  records.clear();
3533  records = startNode.getChildrenNames();
3534  __SUP_COUT__ << "Translating wildcard..." << __E__;
3535  for(auto& record : records)
3536  __SUP_COUT__ << "recordList " << record << __E__;
3537  }
3538  else if(recordList != "")
3539  {
3540  // extract record list
3541  {
3542  std::istringstream f(recordList);
3543  std::string recordStr;
3544  while(getline(f, recordStr, ','))
3545  {
3546  records.push_back(StringMacros::decodeURIComponent(recordStr));
3547  }
3548  __SUP_COUT__ << recordList << __E__;
3549  for(auto& record : records)
3550  __SUP_COUT__ << "recordList " << record << __E__;
3551  }
3552  }
3553 
3554  //=== get common fields call!
3555  retFieldList = startNode.getCommonFields(
3556  records, fieldAcceptList, fieldRejectList, depth);
3557  //=== end get common fields call!
3558  }
3559 
3560  xercesc::DOMElement* parentTypeEl;
3561  for(const auto& fieldInfo : retFieldList)
3562  {
3563  xmlOut.addTextElementToParent(
3564  "FieldTableName", fieldInfo.tableName_, parentEl);
3565  xmlOut.addTextElementToParent(
3566  "FieldColumnName", fieldInfo.columnName_, parentEl);
3567  xmlOut.addTextElementToParent(
3568  "FieldRelativePath", fieldInfo.relativePath_, parentEl);
3569  xmlOut.addTextElementToParent(
3570  "FieldColumnType", fieldInfo.columnInfo_->getType(), parentEl);
3571  xmlOut.addTextElementToParent(
3572  "FieldColumnDataType", fieldInfo.columnInfo_->getDataType(), parentEl);
3573  xmlOut.addTextElementToParent("FieldColumnDefaultValue",
3574  fieldInfo.columnInfo_->getDefaultValue(),
3575  parentEl);
3576  // again, should min and max be included here?
3577  parentTypeEl =
3578  xmlOut.addTextElementToParent("FieldColumnDataChoices", "", parentEl);
3579 
3580  // if there are associated data choices, send info
3581  auto dataChoices = fieldInfo.columnInfo_->getDataChoices();
3582  xmlOut.addTextElementToParent(
3583  "FieldColumnDataChoice", // add default to list to mimic tree handling
3584  fieldInfo.columnInfo_->getDefaultValue(),
3585  parentTypeEl);
3586  for(const auto& dataChoice : dataChoices)
3587  xmlOut.addTextElementToParent(
3588  "FieldColumnDataChoice", dataChoice, parentTypeEl);
3589  }
3590  }
3591  catch(std::runtime_error& e)
3592  {
3593  __SUP_SS__ << ("Error getting common fields!\n\n" + std::string(e.what()))
3594  << __E__;
3595  __SUP_COUT_ERR__ << "\n" << ss.str();
3596  xmlOut.addTextElementToData("Error", ss.str());
3597  }
3598  catch(...)
3599  {
3600  __SUP_SS__ << ("Error getting common fields!\n\n") << __E__;
3601  try
3602  {
3603  throw;
3604  } //one more try to printout extra info
3605  catch(const std::exception& e)
3606  {
3607  ss << "Exception message: " << e.what();
3608  }
3609  catch(...)
3610  {
3611  }
3612  __SUP_COUT_ERR__ << "\n" << ss.str();
3613  xmlOut.addTextElementToData("Error", ss.str());
3614  }
3615 } //end handleFillTreeNodeCommonFieldsXML()
3616 
3617 //==============================================================================
3645 void ConfigurationGUISupervisor::handleFillUniqueFieldValuesForRecordsXML(
3646  HttpXmlDocument& xmlOut,
3647  ConfigurationManagerRW* cfgMgr,
3648  const std::string& groupName,
3649  const TableGroupKey& groupKey,
3650  const std::string& startPath,
3651  const std::string& modifiedTables,
3652  const std::string& recordList,
3653  const std::string& fieldList)
3654 {
3655  // setup active tables based on input group and modified tables
3656  setupActiveTablesXML(
3657  xmlOut, cfgMgr, groupName, groupKey, modifiedTables, false /* refreshAll */);
3658 
3659  try
3660  {
3661  // do not allow traversing for common fields from root level
3662  // the tree view should be used for such a purpose
3663  if(startPath == "/")
3664  return;
3665 
3666  ConfigurationTree startNode = cfgMgr->getNode(startPath);
3667  if(startNode.isLinkNode() && startNode.isDisconnected())
3668  {
3669  __SUP_SS__ << "Start path was a disconnected link node!" << __E__;
3670  __SUP_COUT_ERR__ << "\n" << ss.str();
3671  __SS_THROW__;
3672  }
3673 
3674  // extract records list
3675  std::vector<std::string /*relative-path*/> records;
3676  if(recordList == "*") // handle all records case
3677  {
3678  records.clear();
3679  records = startNode.getChildrenNames();
3680  __SUP_COUT__ << "Translating wildcard..." << __E__;
3681  for(auto& record : records)
3682  __SUP_COUT__ << "recordList " << record << __E__;
3683  }
3684  else if(recordList != "")
3685  {
3686  // extract record list
3687  {
3688  std::istringstream f(recordList);
3689  std::string recordStr;
3690  while(getline(f, recordStr, ','))
3691  {
3692  records.push_back(StringMacros::decodeURIComponent(recordStr));
3693  }
3694  __SUP_COUT__ << recordList << __E__;
3695  for(auto& record : records)
3696  __SUP_COUT__ << "recordList " << record << __E__;
3697  }
3698  } // end records extraction
3699 
3700  // extract fields to get
3701  std::vector<std::string /*relative-path*/> fieldsToGet;
3702  if(fieldList != "")
3703  {
3704  // extract field filter list
3705 
3706  if(fieldList == "AUTO")
3707  {
3708  // automatically choose 3 fields, with preference
3709  // for GroupID, On/Off, and FixedChoice fields.
3710 
3711  __SUP_COUT__ << "Getting AUTO filter fields!" << __E__;
3712 
3713  std::vector<ConfigurationTree::RecordField> retFieldList;
3714  std::vector<std::string /*relative-path*/> fieldAcceptList,
3715  fieldRejectList;
3716  fieldRejectList.push_back("*" + TableViewColumnInfo::COL_NAME_COMMENT);
3717  retFieldList = startNode.getCommonFields(
3718  records, fieldAcceptList, fieldRejectList, 5, true /*auto*/);
3719 
3720  for(const auto& retField : retFieldList)
3721  fieldsToGet.push_back(retField.relativePath_ + retField.columnName_);
3722  }
3723  else
3724  {
3725  std::istringstream f(fieldList);
3726  std::string fieldPath;
3727  while(getline(f, fieldPath, ','))
3728  {
3729  fieldsToGet.push_back(StringMacros::decodeURIComponent(fieldPath));
3730  }
3731  __SUP_COUTV__(fieldList);
3732  }
3733  } // end fields extraction
3734 
3735  __SUP_COUTV__(StringMacros::vectorToString(fieldsToGet));
3736 
3737  // loop through each field and get unique values among records
3738  {
3739  ConfigurationTree startNode = cfgMgr->getNode(startPath);
3740  std::string fieldGroupIDChildLinkIndex;
3741  for(auto& field : fieldsToGet)
3742  {
3743  __SUP_COUTV__(field);
3744 
3745  xercesc::DOMElement* parentEl =
3746  xmlOut.addTextElementToData("field", field);
3747 
3748  // if groupID field, give child link index
3749  // this can be used to pre-select particular group(s)
3750 
3751  // use set to force sorted unique values
3752  std::set<std::string /*unique-values*/> uniqueValues =
3753  startNode.getUniqueValuesForField(
3754  records, field, &fieldGroupIDChildLinkIndex);
3755 
3756  if(fieldGroupIDChildLinkIndex != "")
3757  xmlOut.addTextElementToParent(
3758  "childLinkIndex", fieldGroupIDChildLinkIndex, parentEl);
3759 
3760  for(auto& uniqueValue : uniqueValues)
3761  {
3762  __SUP_COUT__ << "uniqueValue " << uniqueValue << __E__;
3763 
3764  xmlOut.addTextElementToParent("uniqueValue", uniqueValue, parentEl);
3765  }
3766  }
3767  }
3768  }
3769  catch(std::runtime_error& e)
3770  {
3771  __SUP_SS__ << "Error getting unique field values from path '" << startPath
3772  << "' and field list '" << fieldList << "!'\n\n"
3773  << e.what() << __E__;
3774  __SUP_COUT_ERR__ << "\n" << ss.str();
3775  xmlOut.addTextElementToData("Error", ss.str());
3776  }
3777  catch(...)
3778  {
3779  __SUP_SS__ << "Error getting unique field values from path '" << startPath
3780  << "' and field list '" << fieldList << "!'\n\n"
3781  << __E__;
3782  try
3783  {
3784  throw;
3785  } //one more try to printout extra info
3786  catch(const std::exception& e)
3787  {
3788  ss << "Exception message: " << e.what();
3789  }
3790  catch(...)
3791  {
3792  }
3793  __SUP_COUT_ERR__ << "\n" << ss.str();
3794  xmlOut.addTextElementToData("Error", ss.str());
3795  }
3796 } // end handleFillUniqueFieldValuesForRecordsXML()
3797 
3798 //==============================================================================
3818 void ConfigurationGUISupervisor::handleFillTreeViewXML(
3819  HttpXmlDocument& xmlOut,
3820  ConfigurationManagerRW* cfgMgr,
3821  const std::string& groupName,
3822  const TableGroupKey& groupKey,
3823  const std::string& startPath,
3824  unsigned int depth,
3825  bool hideStatusFalse,
3826  const std::string& modifiedTables,
3827  const std::string& filterList,
3828  const std::string& diffGroupName /* = "" */,
3829  const TableGroupKey& diffGroupKey /* = TableGroupKey() */)
3830 {
3831  __SUP_COUTT__ << "get Tree View: " << groupName << "(" << groupKey << ")" << __E__;
3832 
3833  // return xml
3834  // <groupName="groupName"/>
3835  // <tree="path">
3836  // <node="...">
3837  // <node="...">
3838  // <node="...">
3839  // <value="...">
3840  // </node>
3841  // <node="...">
3842  // <value="...">
3843  // </node>
3844  // </node>
3845  // <node="...">
3846  // <value="..">
3847  // </node>
3848  // ...
3849  // </node>
3850  // </tree>
3851 
3852  // return the startPath as root "tree" element
3853  // and then display all children if depth > 0
3854 
3855  //------------------
3856  //First, if doing diff, load tables into cache and copy.
3857  // Loading will leave tables active in the user cfgMgr..
3858  // which will mess up diff. So order:
3859  // 1. load diff tables in user cfgMgr
3860  // 2. copy from cfgMgr cache to diffCfgMgr
3861  // 3. load tree tables in user cfgMgr
3862 
3863  bool doDiff = (diffGroupName != "" && !diffGroupKey.isInvalid());
3864 
3865  std::map<std::string /*name*/, TableVersion /*version*/> diffMemberMap;
3866  ConfigurationManagerRW tmpCfgMgr("TreeDiff");
3867  ConfigurationManagerRW* diffCfgMgr = &tmpCfgMgr;
3868  std::string diffAccumulateErrors;
3869  if(doDiff)
3870  {
3871  //Load diff tables in cfgMgr so that tables are cached,
3872  // then copy to diffCfgMgr as active tables for tree comparison.
3873  // This is more efficient than loading diff tables from db every tree access.
3874 
3875  for(auto& activeTable : cfgMgr->getActiveVersions())
3876  __SUP_COUT__ << "cfgMgr " << activeTable.first << "-v" << activeTable.second
3877  << __E__;
3878 
3879  cfgMgr->loadTableGroup(diffGroupName,
3880  diffGroupKey,
3881  false /*doActivate*/,
3882  &diffMemberMap,
3883  0 /*progressBar*/,
3884  0 /*accumulateErrors*/,
3885  0 /*groupComment*/,
3886  0 /*groupAuthor*/,
3887  0 /*groupCreationTime*/,
3888  false /*doNotLoadMember*/,
3889  0 /*groupTypeString*/
3890  );
3891 
3892  for(auto& activeTable : cfgMgr->getActiveVersions())
3893  __SUP_COUT__ << "cfgMgr " << activeTable.first << "-v" << activeTable.second
3894  << __E__;
3895 
3896  __SUP_COUTT__ << "Diff Group tables loaded." << __E__;
3897  diffCfgMgr->copyTableGroupFromCache(
3898  *cfgMgr, diffMemberMap, diffGroupName, diffGroupKey);
3899  __SUP_COUTT__ << "Diff Group tables copied to local diff config manager."
3900  << __E__;
3901 
3902  //now activate diff table for tree traversal (without calling init())
3903  for(auto& memberPair : diffMemberMap)
3904  diffCfgMgr->getTableByName(memberPair.first)
3905  ->setActiveView(memberPair.second);
3906 
3907  for(const auto& lastGroupLoaded : cfgMgr->getLastTableGroups())
3908  __SUP_COUT__ << "cfgMgr Last loaded " << lastGroupLoaded.first << ": "
3909  << lastGroupLoaded.second.first.first << "("
3910  << lastGroupLoaded.second.first.second << ")";
3911 
3912  for(const auto& lastGroupLoaded : diffCfgMgr->getLastTableGroups())
3913  __SUP_COUT__ << "diffCfgMgr Last loaded " << lastGroupLoaded.first << ": "
3914  << lastGroupLoaded.second.first.first << "("
3915  << lastGroupLoaded.second.first.second << ")";
3916 
3917  //for complete tree traversal, if config type, then load context tables in diff, if context type, then load config tables in diff
3918  if(diffCfgMgr->getLastTableGroups().size() == 1)
3919  {
3920  __SUP_COUT__ << "Type already loaded to diff = "
3921  << diffCfgMgr->getLastTableGroups().begin()->first << __E__;
3922  try
3923  {
3924  auto groupTypeToLoad = ConfigurationManager::GROUP_TYPE_NAME_CONTEXT;
3925  if(diffCfgMgr->getLastTableGroups().begin()->first ==
3926  ConfigurationManager::GROUP_TYPE_NAME_CONTEXT)
3927  groupTypeToLoad = ConfigurationManager::GROUP_TYPE_NAME_CONFIGURATION;
3928  else if(diffCfgMgr->getLastTableGroups().begin()->first ==
3929  ConfigurationManager::GROUP_TYPE_NAME_CONFIGURATION)
3930  groupTypeToLoad = ConfigurationManager::GROUP_TYPE_NAME_CONTEXT;
3931 
3932  __SUP_COUTT__
3933  << "Loading " << groupTypeToLoad
3934  << cfgMgr->getLastTableGroups().at(groupTypeToLoad).first.first << "("
3935  << cfgMgr->getLastTableGroups().at(groupTypeToLoad).first.second
3936  << ")" << __E__;
3937 
3938  diffCfgMgr->copyTableGroupFromCache(
3939  *cfgMgr,
3940  cfgMgr->getLastTableGroups().at(groupTypeToLoad).second,
3941  cfgMgr->getLastTableGroups().at(groupTypeToLoad).first.first,
3942  cfgMgr->getLastTableGroups().at(groupTypeToLoad).first.second);
3943 
3944  //now activate diff table for tree traversal (without calling init())
3945  for(auto& memberPair :
3946  cfgMgr->getLastTableGroups().at(groupTypeToLoad).second)
3947  diffCfgMgr->getTableByName(memberPair.first)
3948  ->setActiveView(memberPair.second);
3949  }
3950  catch(...)
3951  {
3952  } //ignore extra group loading errors
3953  }
3954 
3955  for(auto& activeTable : cfgMgr->getActiveVersions())
3956  __SUP_COUTT__ << "cfgMgr " << activeTable.first << "-v" << activeTable.second
3957  << __E__;
3958  for(auto& activeTable : diffCfgMgr->getActiveVersions())
3959  __SUP_COUTT__ << "diffCfgMgr " << activeTable.first << "-v"
3960  << activeTable.second << __E__;
3961 
3962  __SUP_COUTT__ << "Diff Group tables are setup: " << diffAccumulateErrors << __E__;
3963  } // end do diff load
3964 
3965  //------------------
3966  //Setup active tables based on input group and modified tables
3967  bool usingActiveGroups = (groupName == "" || groupKey.isInvalid());
3968  std::map<std::string /*name*/, TableVersion /*version*/> memberMap;
3969 
3970  std::string accumulatedErrors = "";
3971  setupActiveTablesXML(
3972  xmlOut,
3973  cfgMgr,
3974  groupName,
3975  groupKey,
3976  modifiedTables,
3977  false, //changed to not refresh all, assume no partially loaded tables (startPath == "/"), // refreshAll, if at root node, reload all tables so that partially loaded tables are not allowed
3978  (startPath == "/"), // get group info
3979  &memberMap, // get group member map
3980  true, // output active tables (default)
3981  &accumulatedErrors // accumulate errors
3982  );
3983 
3984  if(memberMap.size() > ConfigurationManager::getFixedContextMemberNames().size() +
3985  1 /* for optional table */
3986  && startPath == "/")
3987  {
3988  __COUTT__ << "Checking for orphaned tables..." << __E__;
3989 
3990  //check Tree for orphaned tables
3991  std::set<std::string /* table name that is linked to */> linkingTables;
3992  for(const auto& tableInfo : cfgMgr->getAllTableInfo())
3993  {
3994  //for each existing active table, check table for links to tables
3995 
3996  __COUTS__(30) << "Table " << tableInfo.first << __E__;
3997  if(!tableInfo.second.tablePtr_->isActive())
3998  continue; //skip if no active view for table
3999  else
4000  __COUTS__(30) << "Table active " << tableInfo.first << __E__;
4001 
4002  const TableView& view = tableInfo.second.tablePtr_->getView();
4003 
4004  bool addedThisTable = false;
4005  for(unsigned int col = 0; col < view.getNumberOfColumns(); ++col)
4006  {
4007  if(!view.getColumnInfo(col).isChildLink())
4008  continue;
4009 
4010  __COUTS__(30) << "Table " << tableInfo.first
4011  << " col: " << view.getColumnInfo(col).getName() << __E__;
4012 
4013  for(unsigned int r = 0; r < view.getNumberOfRows(); ++r)
4014  {
4015  if(view.getDataView()[r][col] == "" ||
4016  view.getDataView()[r][col] ==
4017  TableViewColumnInfo::DATATYPE_STRING_DEFAULT ||
4018  view.getDataView()[r][col] ==
4019  TableViewColumnInfo::DATATYPE_STRING_ALT_DEFAULT)
4020  continue;
4021 
4022  if(!addedThisTable) //add this table to since it seems to have a link!
4023  {
4024  linkingTables.emplace(tableInfo.first);
4025  addedThisTable = true;
4026  }
4027  linkingTables.emplace(
4028  view.getDataView()[r][col]); //add linked table name to set
4029  }
4030  }
4031  }
4032  __COUTTV__(StringMacros::setToString(linkingTables));
4033 
4034  std::string missingTables = "";
4035  for(const auto& member : memberMap)
4036  {
4037  //if not in linking tables set, then note
4038  if(linkingTables.find(member.first) != linkingTables.end())
4039  continue; //linked-to table, so no warning
4040 
4041  if(missingTables.size())
4042  missingTables += ", ";
4043  missingTables += member.first;
4044  }
4045 
4046  if(missingTables.size())
4047  {
4048  __COUTV__(missingTables);
4049  std::stringstream ss;
4050  ss << "The following member tables of table group '" << groupName << "("
4051  << groupKey
4052  << ")' were identified as possibly orphaned (i.e. no active tables link "
4053  "to these tables, and these tables have no links to other tables):\n\n"
4054  << missingTables << ".\n"
4055  << __E__;
4056  xmlOut.addTextElementToData("NoTreeLinkWarning", ss.str());
4057  }
4058  } //end orphaned table check
4059 
4060  if(accumulatedErrors != "")
4061  {
4062  xmlOut.addTextElementToData("Warning", accumulatedErrors);
4063 
4064  __SUP_COUT__ << "Active tables are setup. Warning string: '" << accumulatedErrors
4065  << "'" << __E__;
4066 
4067  __SUP_COUT__ << "Active table versions: "
4068  << StringMacros::mapToString(cfgMgr->getActiveVersions()) << __E__;
4069  }
4070  else
4071  {
4072  __SUP_COUTT__ << "Active tables are setup. No issues found." << __E__;
4073  __SUP_COUTT__ << "Active table versions: "
4074  << StringMacros::mapToString(cfgMgr->getActiveVersions()) << __E__;
4075  }
4076 
4077  try
4078  {
4079  xercesc::DOMElement* parentEl = xmlOut.addTextElementToData("tree", startPath);
4080 
4081  if(depth == 0)
4082  return; // already returned root node in itself
4083 
4084  std::vector<std::pair<std::string, ConfigurationTree>> rootMap;
4085  std::map<std::string, ConfigurationTree> diffRootMap;
4086 
4087  if(startPath == "/")
4088  {
4089  // then consider the configurationManager the root node
4090 
4091  std::string accumulateTreeErrs;
4092 
4093  if(usingActiveGroups)
4094  rootMap = cfgMgr->getChildren(0, &accumulateTreeErrs);
4095  else
4096  rootMap = cfgMgr->getChildren(&memberMap, &accumulateTreeErrs);
4097 
4098  if(doDiff)
4099  {
4100  diffRootMap =
4101  diffCfgMgr->getChildrenMap(&diffMemberMap, &diffAccumulateErrors);
4102  __SUP_COUTV__(diffRootMap.size());
4103  for(auto& diffChild : diffRootMap)
4104  __SUP_COUTV__(diffChild.first);
4105  }
4106 
4107  __SUP_COUTV__(accumulateTreeErrs);
4108 
4109  if(accumulateTreeErrs != "")
4110  xmlOut.addTextElementToData("TreeErrors", accumulateTreeErrs);
4111  }
4112  else
4113  {
4114  ConfigurationTree startNode =
4115  cfgMgr->getNode(startPath, true /*doNotThrowOnBrokenUIDLinks*/);
4116  if(startNode.isLinkNode() && startNode.isDisconnected())
4117  {
4118  xmlOut.addTextElementToData("DisconnectedStartNode", "1");
4119  return; // quietly ignore disconnected links at depth
4120  // note: at the root level they will be flagged for the user
4121  }
4122 
4123  std::map<std::string /*relative-path*/, std::string /*value*/> filterMap;
4125  filterList,
4126  filterMap,
4127  std::set<char>({';'}) /*pair delimiters*/,
4128  std::set<char>({'='}) /*name/value delimiters*/);
4129 
4130  __COUTV__(StringMacros::mapToString(filterMap));
4131 
4132  rootMap = cfgMgr->getNode(startPath).getChildren(filterMap);
4133 
4134  if(doDiff)
4135  {
4136  try
4137  {
4138  ConfigurationTree diffStartNode = diffCfgMgr->getNode(
4139  startPath, true /*doNotThrowOnBrokenUIDLinks*/);
4140 
4141  if(diffStartNode.isLinkNode() && diffStartNode.isDisconnected())
4142  __SUP_COUTT__ << "Diff Group disconnected node." << __E__;
4143  else
4144  diffRootMap =
4145  diffCfgMgr->getNode(startPath).getChildrenMap(filterMap);
4146  }
4147  catch(const std::runtime_error& e)
4148  {
4149  //if diff node does not exist, user was already notified at parent diff, so ignore error.
4150  __SUP_COUTT__ << "Diff Group node does not exist." << __E__;
4151  }
4152  }
4153  }
4154 
4155  if(!doDiff)
4156  {
4157  for(auto& treePair : rootMap)
4158  recursiveTreeToXML(
4159  treePair.second, depth - 1, xmlOut, parentEl, hideStatusFalse);
4160  }
4161  else //doDiff
4162  {
4163  __SUP_COUTT__ << "Diff Tree recursive handling." << __E__;
4164 
4165  //convert vector rootMap to set for searching
4166  std::set<std::string /* treeNodeName */> rootMapToSearch;
4167  for(const auto& rootMember : rootMap)
4168  rootMapToSearch.emplace(rootMember.first);
4169 
4170  std::stringstream rootSs;
4171  for(const auto& rootMember : rootMap)
4172  rootSs << ", " << rootMember.first;
4173 
4174  //add all tables in diff group that are missing to parentEl
4175  std::stringstream diffRootSs;
4176  for(const auto& diffMember : diffRootMap) //diffMemberMap)
4177  {
4178  diffRootSs << ", " << diffMember.first << ":"
4179  << diffMember.second.getNodeType();
4180  if(rootMapToSearch.find(diffMember.first) ==
4181  rootMapToSearch
4182  .end()) //memberMap.find(diffMember.first) == memberMap.end())
4183  {
4184  std::stringstream missingSs;
4185  missingSs << diffMember.first << //" <<< Not in " <<
4186  // groupName << "(" << groupKey << "), present in " <<
4187  " <<< Only in " << diffGroupName << "(" << diffGroupKey
4188  << ") >>>";
4189  xmlOut.addTextElementToParent(
4190  "diffNodeMissing", missingSs.str(), parentEl);
4191  }
4192 
4193  if(diffMember.second.getNodeType() == "UIDLinkNode")
4194  {
4195  __SUP_COUTT__
4196  << "diff active "
4198  << __E__;
4199  __SUP_COUTT__
4200  << "root active "
4202  << __E__;
4203 
4204  __SUP_COUTT__ << "diff map " << diffRootSs.str() << __E__;
4205  __SUP_COUTT__ << "root map " << rootSs.str() << __E__;
4206 
4207  __SUP_COUTT__ << "\t\t" << diffMember.second.getValueName() << ": "
4208  << diffMember.second.getValueAsString() << __E__;
4209 
4210  __SUP_COUTT__ << diffMember.second.nodeDump();
4211  }
4212  }
4213 
4214  __SUP_COUTT__ << "diff map " << diffRootSs.str() << __E__;
4215  __SUP_COUTT__ << "root map " << rootSs.str() << __E__;
4216 
4217  //recurse
4218  for(auto& treePair : rootMap)
4219  {
4220  if(diffRootMap.find(treePair.first) == diffRootMap.end())
4221  {
4222  __SUP_COUTT__ << "Diff Tree recursive handling... " << treePair.first
4223  << __E__;
4224  ConfigurationTree rootNode(diffCfgMgr, nullptr /* table */);
4225  recursiveTreeToXML(
4226  treePair.second,
4227  depth - 1,
4228  xmlOut,
4229  parentEl,
4230  hideStatusFalse,
4231  rootNode /* root node diffTree to indicate record not found in diff group */);
4232  }
4233  else
4234  {
4235  __SUP_COUTT__ << "Diff Tree recursive handling... " << treePair.first
4236  << __E__;
4237  recursiveTreeToXML(treePair.second,
4238  depth - 1,
4239  xmlOut,
4240  parentEl,
4241  hideStatusFalse,
4242  diffRootMap.at(treePair.first));
4243  }
4244  }
4245  }
4246  }
4247  catch(std::runtime_error& e)
4248  {
4249  __SUP_SS__ << "Error detected generating XML tree!\n\n " << e.what() << __E__;
4250  __SUP_COUT_ERR__ << "\n" << ss.str();
4251  xmlOut.addTextElementToData("Error", ss.str());
4252  }
4253  catch(...)
4254  {
4255  __SUP_SS__ << "Error detected generating XML tree!" << __E__;
4256  try
4257  {
4258  throw;
4259  } //one more try to printout extra info
4260  catch(const std::exception& e)
4261  {
4262  ss << "Exception message: " << e.what();
4263  }
4264  catch(...)
4265  {
4266  }
4267  __SUP_COUT_ERR__ << "\n" << ss.str();
4268  xmlOut.addTextElementToData("Error", ss.str());
4269  }
4270 } // end handleFillTreeViewXML()
4271 
4272 //==============================================================================
4278 void ConfigurationGUISupervisor::recursiveTreeToXML(
4279  const ConfigurationTree& t,
4280  unsigned int depth,
4281  HttpXmlDocument& xmlOut,
4282  xercesc::DOMElement* parentEl,
4283  bool hideStatusFalse,
4284  std::optional<std::reference_wrapper<const ConfigurationTree>> diffTree)
4285 {
4286  __COUTS__(30) << t.getValueAsString() << __E__;
4287 
4288  if(t.isValueNode())
4289  {
4290  __COUTS__(30) << "\t" << t.getValueName() << ": " << t.getValueAsString()
4291  << __E__;
4292 
4293  parentEl = xmlOut.addTextElementToParent("node", t.getValueName(), parentEl);
4294  if(diffTree.has_value() &&
4295  t.getValueName() != TableViewColumnInfo::COL_NAME_COMMENT &&
4296  t.getValueName() != TableViewColumnInfo::COL_NAME_AUTHOR &&
4297  t.getValueName() != TableViewColumnInfo::COL_NAME_CREATION)
4298  {
4299  __COUTS__(30) << "\t\t diff type " << diffTree->get().getNodeType() << __E__;
4300 
4301  if(diffTree->get().isValueNode())
4302  {
4303  __COUTS__(30) << "\t" << diffTree->get().getValueAsString() << " ? "
4304  << t.getValueAsString() << __E__;
4305  __COUTS__(30) << "\t" << diffTree->get().getTableName() << "-v"
4306  << diffTree->get().getTableVersion() << " ? "
4307  << t.getTableName() << "-v" << t.getTableVersion() << __E__;
4308 
4309  if(t.getValueAsString() != diffTree->get().getValueAsString())
4310  {
4311  std::stringstream missingSs; //assume only one group loaded for diff
4312  auto diffGroupPair =
4313  diffTree->get().getConfigurationManager()->getGroupOfLoadedTable(
4314  diffTree->get().getTableName());
4315  missingSs << "<<< '" << diffTree->get().getValueAsString() << "' in "
4316  << diffGroupPair.first << "(" << diffGroupPair.second
4317  << ") >>>";
4318  xmlOut.addTextElementToParent("nodeDiff", missingSs.str(), parentEl);
4319  }
4320  }
4321  else
4322  {
4323  std::stringstream missingSs; //assume only one group loaded for diff
4324  //lookup group name in diffManager based on current node's table (best proxy info for missing diff node at this point)
4325  auto diffGroupPair =
4326  diffTree->get().getConfigurationManager()->getGroupOfLoadedTable(
4327  t.getTableName());
4328  missingSs << "<<< Path not found in " << diffGroupPair.first << "("
4329  << diffGroupPair.second << ") >>>";
4330  xmlOut.addTextElementToParent("nodeDiff", missingSs.str(), parentEl);
4331  }
4332 
4333  __COUTS__(30) << "\t" << t.getValueName() << ": " << t.getValueAsString()
4334  << __E__;
4335 
4336  } //end diff tree handling
4337 
4338  xmlOut.addTextElementToParent("value", t.getValueAsString(), parentEl);
4339  parentEl = xmlOut.addTextElementToParent("valueType", t.getValueType(), parentEl);
4340 
4341  // fixed choice and bitmap both use fixed choices strings
4342  // so output them to xml
4343  if(t.getValueType() == TableViewColumnInfo::TYPE_FIXED_CHOICE_DATA ||
4344  t.getValueType() == TableViewColumnInfo::TYPE_BITMAP_DATA)
4345  {
4346  __COUTS__(30) << t.getValueType() << __E__;
4347 
4348  std::vector<std::string> choices = t.getFixedChoices();
4349  for(const auto& choice : choices)
4350  xmlOut.addTextElementToParent("fixedChoice", choice, parentEl);
4351  }
4352  }
4353  else
4354  {
4355  __COUTS__(30) << "\t" << t.getValueAsString() << __E__;
4356 
4357  if(t.isLinkNode())
4358  {
4359  __COUTS__(30) << "\t\t" << t.getValueName() << ": " << t.getValueAsString()
4360  << __E__;
4361 
4362  // Note: The order of xml fields is required by JavaScript, so do NOT change
4363  // order.
4364  parentEl = xmlOut.addTextElementToParent("node", t.getValueName(), parentEl);
4365 
4366  if(diffTree.has_value())
4367  {
4368  __COUTS__(30) << "\t\t diff type " << diffTree->get().getNodeType()
4369  << __E__;
4370 
4371  if(diffTree->get()
4372  .isRootNode()) //then diff group does not have this uid!
4373  {
4374  __COUTS__(30) << "" << t.getValueAsString() << __E__;
4375  std::stringstream missingSs; //assume only one group loaded for diff
4376  //lookup group name in diffManager based on current node's parent's table (best proxy info for missing diff node at this point)
4377  auto diffGroupPair =
4378  diffTree->get().getConfigurationManager()->getGroupOfLoadedTable(
4379  t.getParentTableName());
4380  missingSs << "<<< Path not found in " << diffGroupPair.first << "("
4381  << diffGroupPair.second << ") >>>";
4382  xmlOut.addTextElementToParent("nodeDiff", missingSs.str(), parentEl);
4383  }
4384  else if(t.isDisconnected() != diffTree->get().isDisconnected())
4385  {
4386  __COUTS__(30) << "\t\t diff isDisconnected "
4387  << diffTree->get().isDisconnected() << __E__;
4388 
4389  std::stringstream missingSs; //assume only one group loaded for diff
4390  //lookup group name in diffManager based on current node's parent's table (best proxy info for diff node at this point)
4391  auto diffGroupPair =
4392  diffTree->get().getConfigurationManager()->getGroupOfLoadedTable(
4393  t.getParentTableName());
4394  missingSs << "<<< Link is "
4395  << (diffTree->get().isDisconnected() ? "DISCONNECTED"
4396  : "connected")
4397  << " in " << diffGroupPair.first << "("
4398  << diffGroupPair.second << ") >>>";
4399  xmlOut.addTextElementToParent("nodeDiff", missingSs.str(), parentEl);
4400  }
4401  else if(!t.isDisconnected() &&
4402  t.isUIDLinkNode() != diffTree->get().isUIDLinkNode())
4403  {
4404  __COUTS__(30) << "" << t.getValueAsString() << __E__;
4405  std::stringstream missingSs; //assume only one group loaded for diff
4406  //lookup group name in diffManager based on current node's parent's table (best proxy info for diff node at this point)
4407  auto diffGroupPair =
4408  diffTree->get().getConfigurationManager()->getGroupOfLoadedTable(
4409  t.getParentTableName());
4410  missingSs << "<<< Link is "
4411  << (diffTree->get().isUIDLinkNode() ? "a UID Link"
4412  : "a Group Link")
4413  << " in " << diffGroupPair.first << "("
4414  << diffGroupPair.second << ") >>>";
4415  xmlOut.addTextElementToParent("nodeDiff", missingSs.str(), parentEl);
4416  }
4417  else if(!t.isDisconnected() && t.isUIDLinkNode() &&
4418  t.getValueAsString() !=
4419  diffTree->get().getValueAsString()) //both are UID link
4420  {
4421  __COUTS__(30) << "" << t.getValueAsString() << __E__;
4422  std::stringstream missingSs; //assume only one group loaded for diff
4423  //lookup group name in diffManager based on current node's parent's table (best proxy info for diff node at this point)
4424  auto diffGroupPair =
4425  diffTree->get().getConfigurationManager()->getGroupOfLoadedTable(
4426  t.getParentTableName());
4427  missingSs << "<<< Link to '" << diffTree->get().getValueAsString()
4428  << "' in " << diffGroupPair.first << "("
4429  << diffGroupPair.second << ") >>>";
4430  xmlOut.addTextElementToParent("nodeDiff", missingSs.str(), parentEl);
4431  }
4432  else if(!t.isDisconnected() && !t.isUIDLinkNode()) //both are Group links
4433  {
4434  __COUTS__(30) << "" << t.getValueAsString() << __E__;
4435  std::stringstream missingSs; //assume only one group loaded for diff
4436 
4437  auto tchildren = t.getChildrenMap();
4438  auto dtchildren = diffTree->get().getChildrenMap();
4439  missingSs << "<<< Group link";
4440  if(tchildren.size() != dtchildren.size())
4441  missingSs << " has " << tchildren.size() << " vs "
4442  << dtchildren.size() << " children..";
4443  for(auto& tchild : tchildren)
4444  if(dtchildren.find(tchild.first) == dtchildren.end())
4445  missingSs << " '" << tchild.first << "' missing..";
4446  for(auto& dtchild : dtchildren)
4447  if(tchildren.find(dtchild.first) == tchildren.end())
4448  missingSs << " '" << dtchild.first << "' present...";
4449 
4450  //only add nodeDiff if ss has been appended
4451  if(missingSs.str().length() > std::string("<<< Group link").length())
4452  {
4453  auto diffGroupPair =
4454  diffTree->get()
4455  .getConfigurationManager()
4456  ->getGroupOfLoadedTable(diffTree->get().getTableName());
4457  missingSs << " in " << diffGroupPair.first << "("
4458  << diffGroupPair.second << ") >>>";
4459  xmlOut.addTextElementToParent(
4460  "nodeDiff", missingSs.str(), parentEl);
4461  }
4462  }
4463  else
4464  __COUTS__(30) << "" << t.getValueAsString() << __E__;
4465  } //end diff tree handling
4466 
4467  if(t.isDisconnected())
4468  {
4469  __COUTS__(30) << t.getValueName() << __E__;
4470 
4471  // xmlOut.addTextElementToParent("value", t.getValueAsString(), parentEl);
4472  // xmlOut.addTextElementToParent("DisconnectedLink", t.getValueAsString(),
4473  // parentEl);
4474 
4475  xmlOut.addTextElementToParent("valueType", t.getValueType(), parentEl);
4476 
4477  // add extra fields for disconnected link
4478  xmlOut.addTextElementToParent(
4479  (t.isGroupLinkNode() ? "Group" : "U") + std::string("ID"),
4481  parentEl);
4482  xmlOut.addTextElementToParent(
4483  "LinkTableName", t.getDisconnectedTableName(), parentEl);
4484  xmlOut.addTextElementToParent(
4485  "LinkIndex", t.getChildLinkIndex(), parentEl);
4486 
4487  // add fixed choices (in case link has them)
4488  xercesc::DOMElement* choicesParentEl =
4489  xmlOut.addTextElementToParent("fixedChoices", "", parentEl);
4490  // try
4491  //{
4492 
4493  std::vector<std::string> choices = t.getFixedChoices();
4494  __COUTS__(30) << "choices.size() " << choices.size() << __E__;
4495 
4496  for(const auto& choice : choices)
4497  xmlOut.addTextElementToParent("fixedChoice", choice, choicesParentEl);
4498  //}
4499  // catch(...)
4500  //{
4501  // __COUT__ << "Ignoring unknown fixed choice error"
4502  //} //ignore no fixed choices for disconnected
4503 
4504  return;
4505  }
4506  // else handle connected links
4507 
4508  xmlOut.addTextElementToParent(
4509  (t.isGroupLinkNode() ? "Group" : "U") + std::string("ID"),
4510  t.getValueAsString(),
4511  parentEl);
4512 
4513  xmlOut.addTextElementToParent("LinkTableName", t.getTableName(), parentEl);
4514  xmlOut.addTextElementToParent("LinkIndex", t.getChildLinkIndex(), parentEl);
4515 
4516  // add fixed choices (in case link has them)
4517  {
4518  xercesc::DOMElement* choicesParentEl =
4519  xmlOut.addTextElementToParent("fixedChoices", "", parentEl);
4520  std::vector<std::string> choices = t.getFixedChoices();
4521 
4522  for(const auto& choice : choices)
4523  xmlOut.addTextElementToParent("fixedChoice", choice, choicesParentEl);
4524  }
4525  }
4526  else // uid node (or root node)
4527  {
4528  __COUTS__(30) << "\t\t" << t.getValueAsString() << __E__;
4529  bool returnNode = true; // default to shown
4530 
4531  if(t.isUIDNode() && hideStatusFalse) // only show if status evaluates to true
4532  returnNode = t.isEnabled();
4533 
4534  if(returnNode)
4535  {
4536  parentEl =
4537  xmlOut.addTextElementToParent("node", t.getValueAsString(), parentEl);
4538  if(t.isUIDNode())
4539  {
4540  xmlOut.addTextElementToParent(
4541  "comment", t.getAuthor() + ": " + t.getComment(), parentEl);
4542  xmlOut.addTextElementToParent(
4543  "nodeStatus", t.isEnabled() ? "1" : "0", parentEl);
4544  }
4545 
4546  if(diffTree.has_value())
4547  {
4548  __COUTS__(30)
4549  << "\t\t diff type " << diffTree->get().getNodeType() << __E__;
4550 
4551  if(diffTree->get()
4552  .isRootNode()) //then diff group does not have this uid!
4553  {
4554  __COUTS__(30) << "" << t.getValueAsString() << __E__;
4555  std::stringstream
4556  missingSs; //assume only one group loaded for diff
4557  //lookup group name in diffManager based on current node's table (best proxy info for diff node at this point)
4558  auto diffGroupPair =
4559  diffTree->get()
4560  .getConfigurationManager()
4561  ->getGroupOfLoadedTable(t.getTableName());
4562  missingSs << "<<< Not in " << diffGroupPair.first << "("
4563  << diffGroupPair.second << ") >>>";
4564  xmlOut.addTextElementToParent(
4565  "nodeDiff", missingSs.str(), parentEl);
4566  }
4567  else
4568  __COUTS__(30) << "" << t.getValueAsString() << __E__;
4569  } //end diff tree handling
4570  }
4571  else //hiding node
4572  return; // done.. no further depth needed for node that is not shown
4573  }
4574 
4575  // if depth>=1 toXml all children
4576  // child.toXml(depth-1)
4577  if(depth >= 1)
4578  {
4579  __COUTS__(30) << "\t\t\t" << t.getValueAsString() << __E__;
4580  auto C = t.getChildren();
4581  for(auto& c : C)
4582  recursiveTreeToXML( //TODO -- implement diffTree for depth > 1 requests
4583  c.second,
4584  depth - 1,
4585  xmlOut,
4586  parentEl,
4587  hideStatusFalse);
4588  }
4589  }
4590 } // end recursiveTreeToXML()
4591 
4592 //==============================================================================
4599 void ConfigurationGUISupervisor::handleGetLinkToChoicesXML(
4600  HttpXmlDocument& xmlOut,
4601  ConfigurationManagerRW* cfgMgr,
4602  const std::string& linkToTableName,
4603  const TableVersion& linkToTableVersion,
4604  const std::string& linkIdType,
4605  const std::string& linkIndex,
4606  const std::string& linkInitId)
4607 try
4608 {
4609  // get table
4610  // if uid link
4611  // return all uids
4612  // if groupid link
4613  // find target column
4614  // create the set of values (unique values only)
4615  // note: insert group unions individually (i.e. groups | separated)
4616 
4617  // get table and activate target version
4618  // rename to re-use code template
4619  const std::string& tableName = linkToTableName;
4620  const TableVersion& version = linkToTableVersion;
4621  TableBase* table = cfgMgr->getTableByName(tableName);
4622  try
4623  {
4624  table->setActiveView(version);
4625  }
4626  catch(...)
4627  {
4628  __SUP_COUT__ << "Failed to find stored version, so attempting to load version: "
4629  << version << __E__;
4630  cfgMgr->getVersionedTableByName(tableName, version);
4631  }
4632 
4633  if(version != table->getViewVersion())
4634  {
4635  __SUP_SS__ << "Target table version (" << version
4636  << ") is not the currently active version (" << table->getViewVersion()
4637  << ". Try refreshing the tree." << __E__;
4638  __SUP_COUT_WARN__ << ss.str();
4639  __SS_THROW__;
4640  }
4641 
4642  __SUP_COUT__ << "Active version is " << table->getViewVersion() << __E__;
4643 
4644  if(linkIdType == "UID")
4645  {
4646  // give all UIDs
4647  unsigned int col = table->getView().getColUID();
4648  for(unsigned int row = 0; row < table->getView().getNumberOfRows(); ++row)
4649  xmlOut.addTextElementToData("linkToChoice",
4650  table->getView().getDataView()[row][col]);
4651  }
4652  else if(linkIdType == "GroupID")
4653  {
4654  // find target column
4655  // create the set of values (unique values only)
4656  // note: insert group unions individually (i.e. groups | separated)
4657 
4658  __SUP_COUTV__(linkIndex);
4659  __SUP_COUTV__(linkInitId);
4660 
4661  std::set<std::string> setOfGroupIDs =
4662  table->getView().getSetOfGroupIDs(linkIndex);
4663 
4664  // build list of groupids
4665  // always include initial link group id in choices
4666  // (even if not in set of group ids)
4667  bool foundInitId = false;
4668  for(const auto& groupID : setOfGroupIDs)
4669  {
4670  if(!foundInitId && linkInitId == groupID)
4671  foundInitId = true; // mark init id found
4672 
4673  xmlOut.addTextElementToData("linkToChoice", groupID);
4674  }
4675  // if init id was not found, add to list
4676  if(!foundInitId)
4677  xmlOut.addTextElementToData("linkToChoice", linkInitId);
4678 
4679  // give all UIDs
4680  unsigned int col = table->getView().getColUID();
4681  for(unsigned int row = 0; row < table->getView().getNumberOfRows(); ++row)
4682  {
4683  xmlOut.addTextElementToData("groupChoice",
4684  table->getView().getDataView()[row][col]);
4685  if(table->getView().isEntryInGroup(row, linkIndex, linkInitId))
4686  xmlOut.addTextElementToData("groupMember",
4687  table->getView().getDataView()[row][col]);
4688  }
4689  }
4690  else
4691  {
4692  __SUP_SS__ << "Unrecognized linkIdType '" << linkIdType << ".'" << __E__;
4693  __SS_THROW__;
4694  }
4695 } //end handleGetLinkToChoicesXML()
4696 catch(std::runtime_error& e)
4697 {
4698  __SUP_SS__ << "Error detected saving tree node!\n\n " << e.what() << __E__;
4699  __SUP_COUT_ERR__ << "\n" << ss.str() << __E__;
4700  xmlOut.addTextElementToData("Error", ss.str());
4701 }
4702 catch(...)
4703 {
4704  __SUP_SS__ << "Error detected saving tree node!\n\n " << __E__;
4705  try
4706  {
4707  throw;
4708  } //one more try to printout extra info
4709  catch(const std::exception& e)
4710  {
4711  ss << "Exception message: " << e.what();
4712  }
4713  catch(...)
4714  {
4715  }
4716  __SUP_COUT_ERR__ << "\n" << ss.str() << __E__;
4717  xmlOut.addTextElementToData("Error", ss.str());
4718 } //end handleGetLinkToChoicesXML() catch
4719 
4720 //==============================================================================
4722 void ConfigurationGUISupervisor::handleMergeGroupsXML(
4723  HttpXmlDocument& xmlOut,
4724  ConfigurationManagerRW* cfgMgr,
4725  const std::string& groupANameContext,
4726  const TableGroupKey& groupAKeyContext,
4727  const std::string& groupBNameContext,
4728  const TableGroupKey& groupBKeyContext,
4729  const std::string& groupANameConfig,
4730  const TableGroupKey& groupAKeyConfig,
4731  const std::string& groupBNameConfig,
4732  const TableGroupKey& groupBKeyConfig,
4733  const std::string& author,
4734  const std::string& mergeApproach)
4735 try
4736 {
4737  __SUP_COUT__ << "Merging context group pair " << groupANameContext << " ("
4738  << groupAKeyContext << ") & " << groupBNameContext << " ("
4739  << groupBKeyContext << ") and table group pair " << groupANameConfig
4740  << " (" << groupAKeyConfig << ") & " << groupBNameConfig << " ("
4741  << groupBKeyConfig << ") with approach '" << mergeApproach << __E__;
4742 
4743  // Merges group A and group B
4744  // with consideration for UID conflicts
4745  // Result is a new key of group A's name
4746  //
4747  // There 3 modes:
4748  // Rename -- All records from both groups are maintained, but conflicts from B
4749  // are renamed.
4750  // Must maintain a map of UIDs that are remapped to new name for
4751  // groupB, because linkUID fields must be preserved. Replace --
4752  // Any UID conflicts for a record are replaced by the record from group B.
4753  // Skip -- Any UID conflicts for a record are skipped so that group A record
4754  // remains
4755 
4756  // check valid mode
4757  if(!(mergeApproach == "Rename" || mergeApproach == "Replace" ||
4758  mergeApproach == "Skip"))
4759  {
4760  __SS__ << "Error! Invalid merge approach '" << mergeApproach << ".'" << __E__;
4761  __SS_THROW__;
4762  }
4763 
4764  std::map<std::string /*name*/, TableVersion /*version*/> memberMapAContext,
4765  memberMapBContext, memberMapAConfig, memberMapBConfig;
4766 
4767  // check if skipping group pairs
4768  bool skippingContextPair = false;
4769  bool skippingConfigPair = false;
4770  if(groupANameContext.size() == 0 || groupANameContext[0] == ' ' ||
4771  groupBNameContext.size() == 0 || groupBNameContext[0] == ' ')
4772  {
4773  skippingContextPair = true;
4774  __SUP_COUTV__(skippingContextPair);
4775  }
4776  if(groupANameConfig.size() == 0 || groupANameConfig[0] == ' ' ||
4777  groupBNameConfig.size() == 0 || groupBNameConfig[0] == ' ')
4778  {
4779  skippingConfigPair = true;
4780  __SUP_COUTV__(skippingConfigPair);
4781  }
4782 
4783  // get context group member maps
4784  if(!skippingContextPair)
4785  {
4786  cfgMgr->loadTableGroup(groupANameContext,
4787  groupAKeyContext,
4788  false /*doActivate*/,
4789  &memberMapAContext,
4790  0 /*progressBar*/,
4791  0 /*accumulateErrors*/,
4792  0 /*groupComment*/,
4793  0 /*groupAuthor*/,
4794  0 /*groupCreationTime*/,
4795  false /*doNotLoadMember*/,
4796  0 /*groupTypeString*/
4797  );
4798  __SUP_COUTV__(StringMacros::mapToString(memberMapAContext));
4799 
4800  cfgMgr->loadTableGroup(groupBNameContext,
4801  groupBKeyContext,
4802  false /*doActivate*/,
4803  &memberMapBContext,
4804  0 /*progressBar*/,
4805  0 /*accumulateErrors*/,
4806  0 /*groupComment*/,
4807  0 /*groupAuthor*/,
4808  0 /*groupCreationTime*/,
4809  false /*doNotLoadMember*/,
4810  0 /*groupTypeString*/
4811  );
4812 
4813  __SUP_COUTV__(StringMacros::mapToString(memberMapBContext));
4814  }
4815 
4816  // get table group member maps
4817  if(!skippingConfigPair)
4818  {
4819  cfgMgr->loadTableGroup(groupANameConfig,
4820  groupAKeyConfig,
4821  false /*doActivate*/,
4822  &memberMapAConfig,
4823  0 /*progressBar*/,
4824  0 /*accumulateErrors*/,
4825  0 /*groupComment*/,
4826  0 /*groupAuthor*/,
4827  0 /*groupCreationTime*/,
4828  false /*doNotLoadMember*/,
4829  0 /*groupTypeString*/
4830  );
4831  __SUP_COUTV__(StringMacros::mapToString(memberMapAConfig));
4832 
4833  cfgMgr->loadTableGroup(groupBNameConfig,
4834  groupBKeyConfig,
4835  false /*doActivate*/,
4836  &memberMapBConfig,
4837  0 /*progressBar*/,
4838  0 /*accumulateErrors*/,
4839  0 /*groupComment*/,
4840  0 /*groupAuthor*/,
4841  0 /*groupCreationTime*/,
4842  false /*doNotLoadMember*/,
4843  0 /*groupTypeString*/
4844  );
4845 
4846  __SUP_COUTV__(StringMacros::mapToString(memberMapBConfig));
4847  }
4848 
4849  // for each member of B
4850  // if not found in A member map, add it
4851  // if found in both member maps, and versions are different, load both tables and
4852  // merge
4853 
4854  std::map<std::pair<std::string /*original table*/, std::string /*original uidB*/>,
4855  std::string /*converted uidB*/>
4856  uidConversionMap;
4857  std::map<
4858  std::pair<std::string /*original table*/,
4859  std::pair<std::string /*group linkid*/, std::string /*original gidB*/>>,
4860  std::string /*converted gidB*/>
4861  groupidConversionMap;
4862 
4863  std::stringstream mergeReport;
4864  mergeReport << "======================================" << __E__;
4865  mergeReport << "Time of merge: " << StringMacros::getTimestampString() << __E__;
4866  mergeReport << "Merging context group pair " << groupANameContext << " ("
4867  << groupAKeyContext << ") & " << groupBNameContext << " ("
4868  << groupBKeyContext << ") and table group pair " << groupANameConfig
4869  << " (" << groupAKeyConfig << ") & " << groupBNameConfig << " ("
4870  << groupBKeyConfig << ") with approach '" << mergeApproach << __E__;
4871  mergeReport << "======================================" << __E__;
4872 
4873  // first loop create record conversion map, second loop implement merge (using
4874  // conversion map if Rename)
4875  for(unsigned int i = 0; i < 2; ++i)
4876  {
4877  if(i == 0 && mergeApproach != "Rename")
4878  continue; // only need to construct uidConversionMap for rename approach
4879 
4880  // loop for context and table pair types
4881  for(unsigned int j = 0; j < 2; ++j)
4882  {
4883  if(j == 0 && skippingContextPair) // context
4884  {
4885  __COUT__ << "Skipping context pair..." << __E__;
4886  continue;
4887  }
4888  else if(j == 1 && skippingConfigPair)
4889  {
4890  __COUT__ << "Skipping table pair..." << __E__;
4891  continue;
4892  }
4893 
4894  std::map<std::string /*name*/, TableVersion /*version*/>& memberMapAref =
4895  j == 0 ? memberMapAContext : memberMapAConfig;
4896 
4897  std::map<std::string /*name*/, TableVersion /*version*/>& memberMapBref =
4898  j == 0 ? memberMapBContext : memberMapBConfig;
4899 
4900  if(j == 0) // context
4901  __COUT__ << "Context pair..." << __E__;
4902  else
4903  __COUT__ << "Table pair..." << __E__;
4904 
4905  __COUT__ << "Starting member map B scan." << __E__;
4906  for(const auto& bkey : memberMapBref)
4907  {
4908  __SUP_COUTV__(bkey.first);
4909 
4910  if(memberMapAref.find(bkey.first) == memberMapAref.end())
4911  {
4912  mergeReport << "\n'" << mergeApproach << "'-Missing table '"
4913  << bkey.first << "' A=v" << -1 << ", adding B=v"
4914  << bkey.second << __E__;
4915 
4916  // not found, so add to A member map
4917  memberMapAref[bkey.first] = bkey.second;
4918  }
4919  else if(memberMapAref[bkey.first] != bkey.second)
4920  {
4921  // found table version confict
4922  __SUP_COUTV__(memberMapAref[bkey.first]);
4923  __SUP_COUTV__(bkey.second);
4924 
4925  // load both tables, and merge
4926  TableBase* table = cfgMgr->getTableByName(bkey.first);
4927 
4928  __SUP_COUT__ << "Got table." << __E__;
4929 
4930  TableVersion newVersion = table->mergeViews(
4931  cfgMgr
4932  ->getVersionedTableByName(bkey.first,
4933  memberMapAref[bkey.first])
4934  ->getView(),
4935  cfgMgr->getVersionedTableByName(bkey.first, bkey.second)
4936  ->getView(),
4937  TableVersion() /* destinationVersion*/,
4938  author,
4939  mergeApproach == "Rename"
4940  ? TableBase::MergeApproach::RENAME
4941  : (mergeApproach == "Replace"
4942  ? TableBase::MergeApproach::REPLACE
4943  : TableBase::MergeApproach::SKIP),
4944  uidConversionMap,
4945  groupidConversionMap,
4946  i == 0 /* fillRecordConversionMaps */,
4947  i == 1 /* applyRecordConversionMaps */,
4948  table->getTableName() ==
4949  ConfigurationManager::XDAQ_APPLICATION_TABLE_NAME
4950  /* generateUniqueDataColumns */,
4951  &mergeReport); // dont make destination version the first time
4952 
4953  if(i == 1)
4954  {
4955  __SUP_COUTV__(newVersion);
4956 
4957  try
4958  {
4959  // save all temporary tables to persistent tables
4960  // finish off the version creation
4961  newVersion =
4963  xmlOut,
4964  cfgMgr,
4965  bkey.first,
4966  TableVersion() /*original source version*/,
4967  false /* makeTemporary */,
4968  table,
4969  newVersion /*temporary modified version*/,
4970  false /*ignore duplicates*/,
4971  true /*look for equivalent*/);
4972  }
4973  catch(std::runtime_error& e)
4974  {
4975  __SUP_SS__
4976  << "There was an error saving the '"
4977  << table->getTableName()
4978  << "' merge result to a persistent table version. "
4979  << "Perhaps you can modify this table in one of the "
4980  "groups to resolve this issue, and then re-merge."
4981  << __E__ << e.what();
4982  __SS_THROW__;
4983  }
4984 
4985  __SUP_COUTV__(newVersion);
4986 
4987  memberMapAref[bkey.first] = newVersion;
4988  }
4989  } // end member version conflict handling
4990  } // end B member map loop
4991  } // end context and table loop
4992  } // end top level conversion map or not loop
4993 
4994  // Now save groups
4995 
4996  if(!skippingContextPair)
4997  {
4998  __SUP_COUT__ << "New context member map complete." << __E__;
4999  __SUP_COUTV__(StringMacros::mapToString(memberMapAContext));
5000 
5001  // save the new table group
5002  TableGroupKey newKeyContext = cfgMgr->saveNewTableGroup(
5003  groupANameContext,
5004  memberMapAContext,
5005  "Merger of group " + groupANameContext + " (" + groupAKeyContext.toString() +
5006  ") and " + groupBNameContext + " (" + groupBKeyContext.toString() + ").");
5007 
5008  // return new resulting group
5009  xmlOut.addTextElementToData("ContextGroupName", groupANameContext);
5010  xmlOut.addTextElementToData("ContextGroupKey", newKeyContext.toString());
5011  }
5012  if(!skippingConfigPair)
5013  {
5014  __SUP_COUT__ << "New table member map complete." << __E__;
5015  __SUP_COUTV__(StringMacros::mapToString(memberMapAConfig));
5016 
5017  // save the new table group
5018  TableGroupKey newKeyConfig = cfgMgr->saveNewTableGroup(
5019  groupANameConfig,
5020  memberMapAConfig,
5021  "Merger of group " + groupANameConfig + " (" + groupAKeyConfig.toString() +
5022  ") and " + groupBNameConfig + " (" + groupBKeyConfig.toString() + ").");
5023 
5024  // return new resulting group
5025  xmlOut.addTextElementToData("ConfigGroupName", groupANameConfig);
5026  xmlOut.addTextElementToData("ConfigGroupKey", newKeyConfig.toString());
5027  }
5028 
5029  // output merge report
5030  {
5031  std::string mergeReportBasePath = std::string(__ENV__("USER_DATA"));
5032  std::string mergeReportPath = "/ServiceData/";
5033  // make merge report directories in case they don't exist
5034  mkdir((mergeReportBasePath + mergeReportPath).c_str(), 0755);
5035  mergeReportPath += "ConfigurationGUI_mergeReports/";
5036  // make merge report directories in case they don't exist
5037  mkdir((mergeReportBasePath + mergeReportPath).c_str(), 0755);
5038 
5039  mergeReportPath +=
5040  "merge_" + std::to_string(time(0)) + "_" + std::to_string(clock()) + ".txt";
5041  __SUP_COUTV__(mergeReportPath);
5042 
5043  FILE* fp = fopen((mergeReportBasePath + mergeReportPath).c_str(), "w");
5044  if(fp)
5045  {
5046  fprintf(fp, "%s", mergeReport.str().c_str());
5047  fclose(fp);
5048  xmlOut.addTextElementToData("MergeReportFile",
5049  "/$USER_DATA/" + mergeReportPath);
5050  }
5051  else
5052  xmlOut.addTextElementToData("MergeReportFile", "FILE FAILURE");
5053  } // end output merge report
5054 
5055 } // end handleMergeGroupsXML()
5056 catch(std::runtime_error& e)
5057 {
5058  __SUP_SS__ << "Error merging context group pair " << groupANameContext << " ("
5059  << groupAKeyContext << ") & " << groupBNameContext << " ("
5060  << groupBKeyContext << ") and table group pair " << groupANameConfig
5061  << " (" << groupAKeyConfig << ") & " << groupBNameConfig << " ("
5062  << groupBKeyConfig << ") with approach '" << mergeApproach << "': \n\n"
5063  << e.what() << __E__;
5064  __SUP_COUT_ERR__ << "\n" << ss.str() << __E__;
5065  xmlOut.addTextElementToData("Error", ss.str());
5066 }
5067 catch(...)
5068 {
5069  __SUP_SS__ << "Unknown error merging context group pair " << groupANameContext << " ("
5070  << groupAKeyContext << ") & " << groupBNameContext << " ("
5071  << groupBKeyContext << ") and table group pair " << groupANameConfig
5072  << " (" << groupAKeyConfig << ") & " << groupBNameConfig << " ("
5073  << groupBKeyConfig << ") with approach '" << mergeApproach << ".' \n\n";
5074  try
5075  {
5076  throw;
5077  } //one more try to printout extra info
5078  catch(const std::exception& e)
5079  {
5080  ss << "Exception message: " << e.what();
5081  }
5082  catch(...)
5083  {
5084  }
5085  __SUP_COUT_ERR__ << "\n" << ss.str() << __E__;
5086  xmlOut.addTextElementToData("Error", ss.str());
5087 } // end handleMergeGroupsXML() catch
5088 
5089 //==============================================================================
5091 void ConfigurationGUISupervisor::handleSavePlanCommandSequenceXML(
5092  HttpXmlDocument& xmlOut,
5093  ConfigurationManagerRW* cfgMgr,
5094  const std::string& groupName,
5095  const TableGroupKey& groupKey,
5096  const std::string& modifiedTables,
5097  const std::string& author,
5098  const std::string& planName,
5099  const std::string& commandString)
5100 try
5101 {
5102  __COUT__ << "handleSavePlanCommandSequenceXML " << planName << __E__;
5103 
5104  // setup active tables based on input group and modified tables
5105  setupActiveTablesXML(xmlOut,
5106  cfgMgr,
5107  groupName,
5108  groupKey,
5109  modifiedTables,
5110  true /* refresh all */,
5111  false /* getGroupInfo */,
5112  0 /* returnMemberMap */,
5113  false /* outputActiveTables */);
5114 
5115  TableEditStruct planTable(IterateTable::PLAN_TABLE,
5116  cfgMgr); // Table ready for editing!
5117  TableEditStruct targetTable(IterateTable::TARGET_TABLE,
5118  cfgMgr); // Table ready for editing!
5119  // macro input-argument tables (one loop row per StepLabel, its params in a group)
5120  TableEditStruct loopTable(IterateTable::MACRO_DIM_LOOP_TABLE, cfgMgr);
5121  TableEditStruct loopParamTable(IterateTable::MACRO_DIM_LOOP_PARAM_TABLE, cfgMgr);
5122 
5123  // create table-edit struct for each table that an iterate command type can use
5124  //if two command types have same table, TableEditStruct returns the same temporary version of the table, but then modified_
5125  // will be maintained separately and saving the table becomes a mess.
5126  std::map<std::string /* table name */, TableEditStruct> commandTableToEditMap;
5127  for(const auto& commandPair : IterateTable::commandToTableMap_)
5128  if(commandPair.second != "") // skip tables with no parameters
5129  commandTableToEditMap.emplace(std::pair<std::string, TableEditStruct>(
5130  commandPair.second, TableEditStruct(commandPair.second, cfgMgr)));
5131 
5132  // try to catch any errors while editing..
5133  // if errors delete temporary plan view (if created here)
5134  try
5135  {
5136  // Steps:
5137  // Reset plan commands
5138  // Remove all commands in group "<plan>-Plan"
5139  // Delete linked command parameters row (in separate table)
5140  // If no group remaining, then delete row.
5141  //
5142  // Save plan commands (if modified)
5143  // Create rows and add them to group "<plan>-Plan"
5144  // create row for command paramaters and add to proper table
5145 
5146  std::string groupName = planName + "-Plan";
5147  __SUP_COUT__ << "Handling commands for group " << groupName << __E__;
5148 
5149  unsigned int groupIdCol =
5150  planTable.tableView_->findCol(IterateTable::planTableCols_.GroupID_);
5151  unsigned int cmdTypeCol =
5152  planTable.tableView_->findCol(IterateTable::planTableCols_.CommandType_);
5153 
5154  unsigned int targetGroupIdCol =
5155  targetTable.tableView_->findCol(IterateTable::targetCols_.GroupID_);
5156  unsigned int targetTableCol =
5157  targetTable.tableView_->findCol(IterateTable::targetCols_.TargetLink_);
5158  unsigned int targetUIDCol =
5159  targetTable.tableView_->findCol(IterateTable::targetCols_.TargetLinkUID_);
5160 
5161  unsigned int loopGroupIdCol =
5162  loopTable.tableView_->findCol(IterateTable::macroDimLoopCols_.GroupID_);
5163  unsigned int loopPriorityCol =
5164  loopTable.tableView_->findCol(IterateTable::macroDimLoopCols_.Priority_);
5165  unsigned int loopNumIterCol = loopTable.tableView_->findCol(
5166  IterateTable::macroDimLoopCols_.NumberOfIterations_);
5167  unsigned int loopParamLinkCol =
5168  loopTable.tableView_->findCol(IterateTable::macroDimLoopCols_.ParamLink_);
5169  unsigned int loopParamLinkGroupIdCol = loopTable.tableView_->findCol(
5170  IterateTable::macroDimLoopCols_.ParamLinkGroupID_);
5171  unsigned int loopStepLabelCol = (unsigned int)-1;
5172  try // StepLabel column may be missing from older table versions
5173  {
5174  loopStepLabelCol =
5175  loopTable.tableView_->findCol(IterateTable::macroDimLoopCols_.StepLabel_);
5176  }
5177  catch(...)
5178  {
5179  __SUP_COUT_WARN__
5180  << "Table '" << IterateTable::MACRO_DIM_LOOP_TABLE << "' has no '"
5181  << IterateTable::macroDimLoopCols_.StepLabel_
5182  << "' column; macro argument step labels will not be saved. "
5183  "Save a new version of the table to add the column."
5184  << __E__;
5185  }
5186  unsigned int loopParamGroupIdCol =
5187  loopParamTable.tableView_->findCol(IterateTable::macroParamCols_.GroupID_);
5188  unsigned int loopParamNameCol =
5189  loopParamTable.tableView_->findCol(IterateTable::macroParamCols_.Name_);
5190  unsigned int loopParamValueCol =
5191  loopParamTable.tableView_->findCol(IterateTable::macroParamCols_.Value_);
5192  unsigned int loopParamStepCol =
5193  loopParamTable.tableView_->findCol(IterateTable::macroParamCols_.Step_);
5194  std::string loopGroupLinkIndex =
5195  loopTable.tableView_->getColumnInfo(loopGroupIdCol).getChildLinkIndex();
5196  std::string loopParamGroupLinkIndex =
5197  loopParamTable.tableView_->getColumnInfo(loopParamGroupIdCol)
5198  .getChildLinkIndex();
5199 
5200  std::string groupLinkIndex =
5201  planTable.tableView_->getColumnInfo(groupIdCol).getChildLinkIndex();
5202  __SUP_COUT__ << "groupLinkIndex: " << groupLinkIndex << __E__;
5203 
5204  std::pair<unsigned int /*link col*/, unsigned int /*link id col*/> commandUidLink;
5205  {
5206  bool isGroup; // local because we know is uid link
5207  planTable.tableView_->getChildLink(
5208  planTable.tableView_->findCol(IterateTable::planTableCols_.CommandLink_),
5209  isGroup,
5210  commandUidLink);
5211  }
5212 
5213  unsigned int cmdRow, cmdCol;
5214  std::string targetGroupName;
5215 
5216  // Reset existing plan commands
5217  {
5218  std::string targetUID, cmdType;
5219 
5220  for(unsigned int row = 0; row < planTable.tableView_->getNumberOfRows();
5221  ++row)
5222  {
5223  targetUID = planTable.tableView_
5224  ->getDataView()[row][planTable.tableView_->getColUID()];
5225  __SUP_COUT__ << "targetUID: " << targetUID << __E__;
5226 
5227  // remove command from plan group.. if no more groups, delete
5228  if(planTable.tableView_->isEntryInGroup(row, groupLinkIndex, groupName))
5229  {
5230  __SUP_COUT__ << "Removing." << __E__;
5231 
5232  // delete linked command
5233  // find linked UID in table (mapped by type)
5234  cmdType = planTable.tableView_->getDataView()[row][cmdTypeCol];
5235  auto cmdTypeTableIt = IterateTable::commandToTableMap_.find(cmdType);
5236  if(cmdTypeTableIt != IterateTable::commandToTableMap_.end() &&
5237  cmdTypeTableIt->second !=
5238  "") // skip if invalid command type or if no command parameter table
5239  {
5240  TableEditStruct& cmdTypeTableEdit =
5241  commandTableToEditMap.at(cmdTypeTableIt->second);
5242  cmdRow = cmdTypeTableEdit.tableView_->findRow(
5243  cmdTypeTableEdit.tableView_->getColUID(),
5244  planTable.tableView_
5245  ->getDataView()[row][commandUidLink.second]);
5246 
5247  // before deleting row...
5248  // look for target group
5249  // remove all targets in group
5250  try
5251  {
5252  cmdCol = cmdTypeTableEdit.tableView_->findCol(
5253  IterateTable::commandTargetCols_.TargetsLinkGroupID_);
5254  targetGroupName = cmdTypeTableEdit.tableView_
5255  ->getDataView()[cmdRow][cmdCol];
5256 
5257  for(unsigned int trow = 0;
5258  trow < targetTable.tableView_->getNumberOfRows();
5259  ++trow)
5260  {
5261  // remove command from target group..
5262  if(targetTable.tableView_->isEntryInGroup(
5263  trow,
5264  cmdTypeTableEdit.tableView_->getColumnInfo(cmdCol)
5265  .getChildLinkIndex(),
5266  targetGroupName))
5267  {
5268  __SUP_COUT__ << "Removing target." << __E__;
5269  // remove command entry in plan table
5270  if(targetTable.tableView_->removeRowFromGroup(
5271  trow,
5272  targetGroupIdCol,
5273  targetGroupName,
5274  true /*deleteRowIfNoGroup*/))
5275  --trow; // since row was deleted, go back!
5276  }
5277  }
5278  }
5279  catch(...)
5280  {
5281  __SUP_COUT__ << "No targets." << __E__;
5282  }
5283 
5284  // look for macro argument loop group (macro commands only)
5285  // remove all loop rows in group, and each loop row's parameter rows
5286  try
5287  {
5288  cmdCol = cmdTypeTableEdit.tableView_->findCol(
5289  IterateTable::commandExecuteMacroParams_
5290  .MacroParameterLinkGroupID_);
5291  std::string loopGroupName =
5292  cmdTypeTableEdit.tableView_
5293  ->getDataView()[cmdRow][cmdCol];
5294 
5295  for(unsigned int lrow = 0;
5296  loopGroupName != "" &&
5297  lrow < loopTable.tableView_->getNumberOfRows();
5298  ++lrow)
5299  {
5300  if(!loopTable.tableView_->isEntryInGroup(
5301  lrow, loopGroupLinkIndex, loopGroupName))
5302  continue;
5303 
5304  std::string paramGroupName =
5305  loopTable.tableView_
5306  ->getDataView()[lrow][loopParamLinkGroupIdCol];
5307  for(unsigned int prow = 0;
5308  paramGroupName != "" &&
5309  prow < loopParamTable.tableView_->getNumberOfRows();
5310  ++prow)
5311  if(loopParamTable.tableView_->isEntryInGroup(
5312  prow,
5313  loopParamGroupLinkIndex,
5314  paramGroupName) &&
5315  loopParamTable.tableView_->removeRowFromGroup(
5316  prow,
5317  loopParamGroupIdCol,
5318  paramGroupName,
5319  true /*deleteRowIfNoGroup*/))
5320  {
5321  loopParamTable.modified_ = true;
5322  --prow; // since row was deleted, go back!
5323  }
5324 
5325  __SUP_COUT__ << "Removing macro argument loop row."
5326  << __E__;
5327  if(loopTable.tableView_->removeRowFromGroup(
5328  lrow,
5329  loopGroupIdCol,
5330  loopGroupName,
5331  true /*deleteRowIfNoGroup*/))
5332  {
5333  loopTable.modified_ = true;
5334  --lrow; // since row was deleted, go back!
5335  }
5336  }
5337  }
5338  catch(...)
5339  {
5340  __SUP_COUT__ << "No macro argument loops." << __E__;
5341  }
5342 
5343  // now no more targets, delete row
5344 
5345  cmdTypeTableEdit.tableView_->deleteRow(cmdRow);
5346 
5347  cmdTypeTableEdit.modified_ = true;
5348  }
5349 
5350  // remove command entry in plan table
5351  if(planTable.tableView_->removeRowFromGroup(
5352  row, groupIdCol, groupName, true /*deleteRowIfNoGroup*/))
5353  --row; // since row was deleted, go back!
5354  }
5355  }
5356  }
5357 
5358  // Done resetting existing plan
5359  // Now save new commands
5360 
5361  std::vector<IterateTable::Command> commands;
5362 
5363  // extract command sequence and add to table
5364  // into vector with type, and params
5365  {
5366  std::istringstream f(commandString);
5367  std::string commandSubString, paramSubString, paramValue;
5368  int i;
5369  while(getline(f, commandSubString, ';'))
5370  {
5371  __SUP_COUTT__ << "commandSubString " << commandSubString << __E__;
5372  std::istringstream g(commandSubString);
5373 
5374  i = 0;
5375  while(getline(g, paramSubString, ','))
5376  {
5377  __SUP_COUTT__ << "paramSubString " << paramSubString << __E__;
5378  if(i == 0) // type
5379  {
5380  if(paramSubString != "type")
5381  {
5382  __SUP_SS__ << "Invalid command sequence" << __E__;
5383  __SS_THROW__;
5384  }
5385  // create command object
5386  commands.push_back(IterateTable::Command());
5387 
5388  getline(g, paramValue, ',');
5389  ++i;
5390  __SUP_COUTT__ << "paramValue " << paramValue << __E__;
5391  commands.back().type_ = paramValue;
5392  }
5393  else // params
5394  {
5395  getline(g, paramValue, ',');
5396  ++i;
5397  __SUP_COUTT__ << "paramValue " << paramValue << __E__;
5398 
5399  if(paramSubString == "TargetSubsystem")
5400  commands.back().targetSubsystem_ =
5402  else
5403  commands.back().params_.emplace(
5404  std::pair<std::string /*param name*/,
5405  std::string /*param value*/>(
5406  paramSubString,
5407  StringMacros::decodeURIComponent(paramValue)));
5408  }
5409 
5410  ++i;
5411  }
5412  }
5413 
5414  } // end extract command sequence
5415 
5416  __SUP_COUT__ << "commands size " << commands.size() << __E__;
5417 
5418  // at this point, have extracted commands
5419 
5420  // now save commands to plan group
5421  // group should be "<plan>-Plan"
5422 
5423  unsigned int row, tgtRow;
5424  unsigned int targetIndex;
5425  std::string targetStr, cmdUID;
5426 
5427  for(auto& command : commands)
5428  {
5429  __SUP_COUT__ << "command " << command.type_ << __E__;
5430  __SUP_COUT__ << "table " << IterateTable::commandToTableMap_.at(command.type_)
5431  << __E__;
5432 
5433  // create command entry at plan level
5434  row = planTable.tableView_->addRow(
5435  author, true /*incrementUniqueData*/, "planCommand");
5436  planTable.tableView_->addRowToGroup(row, groupIdCol, groupName);
5437 
5438  // set command type
5439  planTable.tableView_->setURIEncodedValue(command.type_, row, cmdTypeCol);
5440 
5441  // set target subsystem (try/catch for table compat)
5442  try
5443  {
5444  unsigned int tsCol = planTable.tableView_->findCol(
5445  IterateTable::planTableCols_.TargetSubsystem_);
5446  planTable.tableView_->setValueAsString(
5447  command.targetSubsystem_, row, tsCol);
5448  }
5449  catch(...)
5450  {
5451  }
5452 
5453  // set command status true
5454  planTable.tableView_->setValueAsString(
5455  "1", row, planTable.tableView_->getColStatus());
5456 
5457  // create command specifics
5458  auto cmdTypeTableIt = IterateTable::commandToTableMap_.find(command.type_);
5459  if(cmdTypeTableIt != IterateTable::commandToTableMap_.end() &&
5460  cmdTypeTableIt->second !=
5461  "") // skip if invalid command type or if no command parameter table
5462  {
5463  TableEditStruct& cmdTypeTableEdit =
5464  commandTableToEditMap.at(cmdTypeTableIt->second);
5465  __SUP_COUT__ << "table " << cmdTypeTableEdit.tableName_ << __E__;
5466 
5467  // at this point have table, tempVersion, and createdFlag
5468 
5469  // create command parameter entry at command level
5470  cmdRow = cmdTypeTableEdit.tableView_->addRow(
5471  author, true /*incrementUniqueData*/, command.type_ + "_COMMAND_");
5472 
5473  // parameters are linked
5474  // now set value of all parameters
5475  // find parameter column, and set value
5476  // if special target parameter, extract targets
5477  for(auto& param : command.params_)
5478  {
5479  __SUP_COUT__ << "\t param " << param.first << " : " << param.second
5480  << __E__;
5481 
5482  if(param.first == IterateTable::targetParams_.Tables_)
5483  {
5484  __SUP_COUT__ << "\t\t found target tables" << __E__;
5485  std::istringstream f(param.second);
5486 
5487  targetIndex = 0;
5488  while(getline(f, targetStr, '='))
5489  {
5490  __SUP_COUT__ << "\t\t targetStr = " << targetStr << __E__;
5491  if(!command.targets_.size() ||
5492  command.targets_.back().table_ != "")
5493  {
5494  __SUP_COUT__ << "\t\t make targetStr = " << targetStr
5495  << __E__;
5496  // make new target
5497  command.addTarget();
5498  command.targets_.back().table_ = targetStr;
5499  }
5500  else // file existing target
5501  command.targets_[targetIndex++].table_ = targetStr;
5502  }
5503 
5504  continue; // go to next parameter
5505  }
5506 
5507  if(param.first == IterateTable::targetParams_.UIDs_)
5508  {
5509  __SUP_COUT__ << "\t\t found target UIDs" << __E__;
5510  std::istringstream f(param.second);
5511 
5512  targetIndex = 0;
5513  while(getline(f, targetStr, '='))
5514  {
5515  __SUP_COUT__ << "\t\t targetStr = " << targetStr << __E__;
5516  if(!command.targets_.size() ||
5517  command.targets_.back().UID_ != "")
5518  {
5519  __SUP_COUT__ << "\t\t make targetStr = " << targetStr
5520  << __E__;
5521  // make new target
5522  command.addTarget();
5523  command.targets_.back().UID_ = targetStr;
5524  }
5525  else // file existing target
5526  command.targets_[targetIndex++].UID_ = targetStr;
5527  }
5528  continue;
5529  }
5530 
5531  if(param.first == IterateTable::commandExecuteMacroParams_.MacroArgs_)
5532  continue; // handled below, after cmdUID is known
5533 
5534  cmdCol = cmdTypeTableEdit.tableView_->findCol(param.first);
5535 
5536  __SUP_COUT__ << "param col " << cmdCol << __E__;
5537 
5538  cmdTypeTableEdit.tableView_->setURIEncodedValue(
5539  param.second, cmdRow, cmdCol);
5540  } // end parameter loop
5541 
5542  cmdUID =
5543  cmdTypeTableEdit.tableView_
5544  ->getDataView()[cmdRow][cmdTypeTableEdit.tableView_->getColUID()];
5545 
5546  // macro input arguments: "name:start:step:label,..." (pieces URI-encoded)
5547  // -> one loop row per distinct label (first-appearance order), params grouped
5548  {
5549  auto macroArgsIt = command.params_.find(
5550  IterateTable::commandExecuteMacroParams_.MacroArgs_);
5551  bool isMacroCommand = false;
5552  try
5553  {
5554  cmdTypeTableEdit.tableView_->findCol(
5555  IterateTable::commandExecuteMacroParams_.MacroParameterLink_);
5556  isMacroCommand = true;
5557  }
5558  catch(...)
5559  {
5560  }
5561 
5562  if(isMacroCommand)
5563  {
5564  // always link the (possibly empty) loop group
5565  cmdCol = cmdTypeTableEdit.tableView_->findCol(
5566  IterateTable::commandExecuteMacroParams_.MacroParameterLink_);
5567  cmdTypeTableEdit.tableView_->setValueAsString(
5568  IterateTable::MACRO_DIM_LOOP_TABLE, cmdRow, cmdCol);
5569  cmdCol = cmdTypeTableEdit.tableView_->findCol(
5570  IterateTable::commandExecuteMacroParams_
5571  .MacroParameterLinkGroupID_);
5572  cmdTypeTableEdit.tableView_->setValueAsString(
5573  cmdUID + "_Loops", cmdRow, cmdCol);
5574 
5575  std::vector<std::string> labelOrder;
5576  std::map<
5577  std::string /*label*/,
5578  std::vector<std::vector<std::string> /*name,start,step*/>>
5579  argsByLabel;
5580 
5581  if(macroArgsIt != command.params_.end() &&
5582  macroArgsIt->second != "")
5583  {
5584  std::vector<std::string> entries =
5585  StringMacros::getVectorFromString(macroArgsIt->second,
5586  {','});
5587  for(const auto& entry : entries)
5588  {
5589  if(entry == "")
5590  continue;
5591  std::vector<std::string> pieces =
5593  entry, {':'}, {} /*keep whitespace*/);
5594  if(pieces.size() != 4 || pieces[0] == "")
5595  {
5596  __SUP_SS__
5597  << "Invalid macro argument entry '" << entry
5598  << "' for command '" << command.type_
5599  << ".' Expected name:start:step:label." << __E__;
5600  __SUP_SS_THROW__;
5601  }
5602  for(auto& piece : pieces)
5603  piece = StringMacros::decodeURIComponent(piece);
5604 
5605  if(argsByLabel.find(pieces[3]) == argsByLabel.end())
5606  labelOrder.push_back(pieces[3]);
5607  argsByLabel[pieces[3]].push_back(
5608  {pieces[0], pieces[1], pieces[2]});
5609  }
5610  }
5611 
5612  for(size_t li = 0; li < labelOrder.size(); ++li)
5613  {
5614  const std::string& label = labelOrder[li];
5615  std::string paramGroupName =
5616  cmdUID + "_L" + std::to_string(li) + "_Params";
5617 
5618  unsigned int lrow = loopTable.tableView_->addRow(
5619  author, true /*incrementUniqueData*/, cmdUID + "_Dim");
5620  loopTable.tableView_->addRowToGroup(
5621  lrow, loopGroupIdCol, cmdUID + "_Loops");
5622  loopTable.tableView_->setValueAsString(
5623  std::to_string(li), lrow, loopPriorityCol);
5624  loopTable.tableView_->setValueAsString(
5625  "1", lrow, loopNumIterCol);
5626  if(loopStepLabelCol != (unsigned int)-1)
5627  loopTable.tableView_->setURIEncodedValue(
5628  label, lrow, loopStepLabelCol);
5629  loopTable.tableView_->setValueAsString(
5630  IterateTable::MACRO_DIM_LOOP_PARAM_TABLE,
5631  lrow,
5632  loopParamLinkCol);
5633  loopTable.tableView_->setValueAsString(
5634  paramGroupName, lrow, loopParamLinkGroupIdCol);
5635  loopTable.modified_ = true;
5636 
5637  for(const auto& arg : argsByLabel[label])
5638  {
5639  unsigned int prow = loopParamTable.tableView_->addRow(
5640  author,
5641  true /*incrementUniqueData*/,
5642  cmdUID + "_Param");
5643  loopParamTable.tableView_->addRowToGroup(
5644  prow, loopParamGroupIdCol, paramGroupName);
5645  loopParamTable.tableView_->setURIEncodedValue(
5646  arg[0], prow, loopParamNameCol);
5647  loopParamTable.tableView_->setURIEncodedValue(
5648  arg[1], prow, loopParamValueCol);
5649  loopParamTable.tableView_->setURIEncodedValue(
5650  arg[2], prow, loopParamStepCol);
5651  loopParamTable.modified_ = true;
5652  }
5653  __SUP_COUT__ << "Macro argument loop row for label '" << label
5654  << "' with " << argsByLabel[label].size()
5655  << " argument(s)." << __E__;
5656  }
5657  }
5658  } // end macro argument handling
5659 
5660  if(command.targets_.size())
5661  {
5662  // if targets, create group in target table
5663 
5664  __SUP_COUT__ << "targets found for command UID=" << cmdUID << __E__;
5665 
5666  // create link from command table to target
5667  cmdCol = cmdTypeTableEdit.tableView_->findCol(
5668  IterateTable::commandTargetCols_.TargetsLink_);
5669  cmdTypeTableEdit.tableView_->setValueAsString(
5670  IterateTable::TARGET_TABLE, cmdRow, cmdCol);
5671 
5672  cmdCol = cmdTypeTableEdit.tableView_->findCol(
5673  IterateTable::commandTargetCols_.TargetsLinkGroupID_);
5674  cmdTypeTableEdit.tableView_->setValueAsString(
5675  cmdUID + "_Targets", cmdRow, cmdCol);
5676 
5677  // create row(s) for each target in target table with correct groupID
5678 
5679  for(const auto& target : command.targets_)
5680  {
5681  __SUP_COUT__ << target.table_ << " " << target.UID_ << __E__;
5682 
5683  // create target entry in target table in group
5684  tgtRow = targetTable.tableView_->addRow(
5685  author, true /*incrementUniqueData*/, "commandTarget");
5686  targetTable.tableView_->addRowToGroup(
5687  tgtRow, targetGroupIdCol, cmdUID + "_Targets");
5688 
5689  // set target table
5690  targetTable.tableView_->setValueAsString(
5691  target.table_, tgtRow, targetTableCol);
5692 
5693  // set target UID
5694  targetTable.tableView_->setValueAsString(
5695  target.UID_, tgtRow, targetUIDCol);
5696  }
5697  } // end target handling
5698 
5699  // add link at plan level to created UID
5700  planTable.tableView_->setValueAsString(
5701  cmdTypeTableEdit.tableName_, row, commandUidLink.first);
5702  planTable.tableView_->setValueAsString(
5703  cmdUID, row, commandUidLink.second);
5704 
5705  __SUP_COUT__ << "linked to uid = " << cmdUID << __E__;
5706 
5707  cmdTypeTableEdit.modified_ = true;
5708  } // done with command specifics
5709 
5710  } // end command loop
5711 
5712  // commands are created in the temporary tables
5713  // validate with init
5714 
5715  planTable.tableView_->print();
5716  planTable.tableView_->init(); // verify new table (throws runtime_errors)
5717 
5718  __SUP_COUT__ << "requestType tables:" << __E__;
5719 
5720  for(auto& modifiedConfig : commandTableToEditMap)
5721  {
5722  __SUP_COUTV__(modifiedConfig.second.modified_);
5723  modifiedConfig.second.tableView_->print();
5724  modifiedConfig.second.tableView_->init();
5725  }
5726 
5727  targetTable.tableView_->print();
5728  targetTable.tableView_->init(); // verify new table (throws runtime_errors)
5729 
5730  loopTable.tableView_->init();
5731  loopParamTable.tableView_->init();
5732 
5733  } // end try for plan
5734  catch(...)
5735  {
5736  __SUP_COUT__ << "Handling command table errors while saving. Erasing all newly "
5737  "created versions."
5738  << __E__;
5739 
5740  // erase all temporary tables if created here
5741 
5742  for(TableEditStruct* editTable :
5743  {&planTable, &targetTable, &loopTable, &loopParamTable})
5744  if(editTable->createdTemporaryVersion_) // if temporary version created here
5745  {
5746  __SUP_COUT__ << "Erasing temporary version " << editTable->tableName_
5747  << "-v" << editTable->temporaryVersion_ << __E__;
5748  // erase with proper version management
5749  cfgMgr->eraseTemporaryVersion(editTable->tableName_,
5750  editTable->temporaryVersion_);
5751  }
5752 
5753  for(auto& modifiedConfig : commandTableToEditMap)
5754  {
5755  if(modifiedConfig.second
5756  .createdTemporaryVersion_) // if temporary version created here
5757  {
5758  __SUP_COUT__ << "Erasing temporary version "
5759  << modifiedConfig.second.tableName_ << "-v"
5760  << modifiedConfig.second.temporaryVersion_ << __E__;
5761  // erase with proper version management
5762  cfgMgr->eraseTemporaryVersion(modifiedConfig.second.tableName_,
5763  modifiedConfig.second.temporaryVersion_);
5764  }
5765  }
5766 
5767  throw; // re-throw
5768  }
5769 
5770  // all edits are complete and tables verified
5771  // need to save all edits properly
5772  // if not modified, discard
5773 
5775  xmlOut,
5776  cfgMgr,
5777  planTable.tableName_,
5778  planTable.originalVersion_,
5779  true /*make temporary*/,
5780  planTable.table_,
5781  planTable.temporaryVersion_,
5782  true /*ignoreDuplicates*/); // save temporary version properly
5783 
5784  __SUP_COUT__ << "Final plan version is " << planTable.tableName_ << "-v"
5785  << finalVersion << __E__;
5786 
5788  xmlOut,
5789  cfgMgr,
5790  targetTable.tableName_,
5791  targetTable.originalVersion_,
5792  true /*make temporary*/,
5793  targetTable.table_,
5794  targetTable.temporaryVersion_,
5795  true /*ignoreDuplicates*/); // save temporary version properly
5796 
5797  __SUP_COUT__ << "Final target version is " << targetTable.tableName_ << "-v"
5798  << finalVersion << __E__;
5799 
5800  for(TableEditStruct* editTable : {&loopTable, &loopParamTable})
5801  {
5802  if(!editTable->modified_)
5803  {
5804  if(editTable->createdTemporaryVersion_) // if temporary version created here
5805  {
5806  __SUP_COUT__ << "Erasing unmodified temporary version "
5807  << editTable->tableName_ << "-v"
5808  << editTable->temporaryVersion_ << __E__;
5809  cfgMgr->eraseTemporaryVersion(editTable->tableName_,
5810  editTable->temporaryVersion_);
5811  }
5812  continue;
5813  }
5815  xmlOut,
5816  cfgMgr,
5817  editTable->tableName_,
5818  editTable->originalVersion_,
5819  true /*make temporary*/,
5820  editTable->table_,
5821  editTable->temporaryVersion_,
5822  true /*ignoreDuplicates*/); // save temporary version properly
5823  __SUP_COUT__ << "Final version is " << editTable->tableName_ << "-v"
5824  << finalVersion << __E__;
5825  }
5826 
5827  for(auto& modifiedConfig : commandTableToEditMap)
5828  {
5829  if(!modifiedConfig.second.modified_)
5830  {
5831  if(modifiedConfig.second
5832  .createdTemporaryVersion_) // if temporary version created here
5833  {
5834  __SUP_COUT__ << "Erasing unmodified temporary version "
5835  << modifiedConfig.second.tableName_ << "-v"
5836  << modifiedConfig.second.temporaryVersion_ << __E__;
5837  // erase with proper version management
5838  cfgMgr->eraseTemporaryVersion(modifiedConfig.second.tableName_,
5839  modifiedConfig.second.temporaryVersion_);
5840  }
5841  continue;
5842  }
5843 
5845  xmlOut,
5846  cfgMgr,
5847  modifiedConfig.second.tableName_,
5848  modifiedConfig.second.originalVersion_,
5849  true /*make temporary*/,
5850  modifiedConfig.second.table_,
5851  modifiedConfig.second.temporaryVersion_,
5852  true /*ignoreDuplicates*/); // save temporary version properly
5853 
5854  __SUP_COUT__ << "Final version is " << modifiedConfig.second.tableName_ << "-v"
5855  << finalVersion << __E__;
5856  }
5857 
5858  handleFillModifiedTablesXML(xmlOut, cfgMgr);
5859 } // end handleSavePlanCommandSequenceXML()
5860 catch(std::runtime_error& e)
5861 {
5862  __SUP_SS__ << "Error detected saving Iteration Plan!\n\n " << e.what() << __E__;
5863  __SUP_COUT_ERR__ << "\n" << ss.str() << __E__;
5864  xmlOut.addTextElementToData("Error", ss.str());
5865 }
5866 catch(...)
5867 {
5868  __SUP_SS__ << "Error detected saving Iteration Plan!\n\n " << __E__;
5869  try
5870  {
5871  throw;
5872  } //one more try to printout extra info
5873  catch(const std::exception& e)
5874  {
5875  ss << "Exception message: " << e.what();
5876  }
5877  catch(...)
5878  {
5879  }
5880  __SUP_COUT_ERR__ << "\n" << ss.str() << __E__;
5881  xmlOut.addTextElementToData("Error", ss.str());
5882 } // end handleSavePlanCommandSequenceXML() catch
5883 
5884 //==============================================================================
5894 void ConfigurationGUISupervisor::handleSaveTreeNodeEditXML(HttpXmlDocument& xmlOut,
5895  ConfigurationManagerRW* cfgMgr,
5896  const std::string& tableName,
5897  TableVersion version,
5898  const std::string& type,
5899  const std::string& uid,
5900  const std::string& colName,
5901  const std::string& newValue,
5902  const std::string& author)
5903 try
5904 {
5905  __SUP_COUT__ << "Editing table " << tableName << "(" << version << ") uid=" << uid
5906  << " type=" << type << __E__;
5907 
5908  // get the current table/version
5909  // check if the value is new
5910  // if new edit value (in a temporary version only)
5911 
5912  // get table and activate target version
5913  TableBase* table = cfgMgr->getTableByName(tableName);
5914  try
5915  {
5916  table->setActiveView(version);
5917  }
5918  catch(...)
5919  {
5920  if(version.isTemporaryVersion())
5921  throw; // if temporary, there is no hope to find lost version
5922 
5923  __SUP_COUT__ << "Failed to find stored version, so attempting to load version: "
5924  << version << __E__;
5925  cfgMgr->getVersionedTableByName(tableName, version);
5926  }
5927 
5928  __SUP_COUT__ << "Active version is " << table->getViewVersion() << __E__;
5929  __SUP_COUTTV__(table->getView().getComment());
5930 
5931  if(version != table->getViewVersion())
5932  {
5933  __SUP_SS__ << "Target table version (" << version
5934  << ") is not the currently active version (" << table->getViewVersion()
5935  << "). Try refreshing the tree." << __E__;
5936  __SS_THROW__;
5937  }
5938 
5939  unsigned int col = -1;
5940  if(type == "uid" || type == "delete-uid" || type == "tree-copy")
5941  col = table->getView().getColUID();
5942  else if(type == "node-comment")
5943  col = table->getView().findCol(TableViewColumnInfo::COL_NAME_COMMENT);
5944  else if(type == "link-UID" || type == "link-GroupID" || type == "value" ||
5945  type == "value-groupid" || type == "value-bool" || type == "value-bitmap")
5946  col = table->getView().findCol(colName);
5947  else if(type == "table" || type == "link-comment" || type == "table-newGroupRow" ||
5948  type == "table-newUIDRow" || type == "table-newRow")
5949  ; // column N/A
5950  else
5951  {
5952  __SUP_SS__ << "Impossible! Unrecognized edit type: " << type << __E__;
5953  __SS_THROW__;
5954  }
5955 
5956  // check if the comment value is new before making temporary version
5957  if(type == "table" || type == "link-comment")
5958  {
5959  // editing comment, so check if comment is different
5960  if(table->getView().isURIEncodedCommentTheSame(newValue))
5961  {
5962  __SUP_SS__ << "Comment '" << StringMacros::decodeURIComponent(newValue)
5963  << "' is the same as the current comment. No need to save change."
5964  << __E__;
5965  __SS_THROW__;
5966  }
5967  }
5968 
5969  // version handling:
5970  // always make a new temporary-version from source-version
5971  // edit temporary-version
5972  // if edit fails
5973  // delete temporary-version
5974  // else
5975  // return new temporary-version
5976  // if source-version was temporary
5977  // then delete source-version
5978 
5979  TableVersion temporaryVersion = table->createTemporaryView(version);
5980 
5981  __SUP_COUT__ << "Created temporary version " << temporaryVersion << __E__;
5982 
5983  TableView* cfgView = table->getTemporaryView(temporaryVersion);
5984  cfgView->init(); // prepare maps
5985 
5986  __SUP_COUTTV__(table->getView().getComment());
5987 
5988  // edit/verify new table (throws runtime_errors)
5989  try
5990  {
5991  // have view so edit it
5992  if(type == "table" || type == "link-comment")
5993  {
5994  // edit comment
5995  cfgView->setURIEncodedComment(newValue);
5996  }
5997  else if(type == "table-newRow" || type == "table-newUIDRow")
5998  {
5999  // add row
6000  unsigned int row = cfgView->addRow(
6001  author, true /*incrementUniqueData*/, newValue /*baseNameAutoUID*/);
6002 
6003  // if TableViewColumnInfo::COL_NAME_STATUS exists, set it to true
6004  try
6005  {
6006  col = cfgView->getColStatus();
6007  cfgView->setValueAsString("1", row, col);
6008  }
6009  catch(...)
6010  {
6011  } // if not, ignore
6012 
6013  // set UID value
6014  cfgView->setURIEncodedValue(newValue, row, cfgView->getColUID());
6015  }
6016  else if(type == "table-newGroupRow")
6017  {
6018  // get index value and group id value
6019  unsigned int csvIndex = newValue.find(',');
6020 
6021  std::string linkIndex = newValue.substr(0, csvIndex);
6022  std::string groupId = newValue.substr(csvIndex + 1);
6023 
6024  // get new row UID value from second part of string
6025  csvIndex = groupId.find(',');
6026  std::string newRowUID = groupId.substr(csvIndex + 1);
6027  groupId = groupId.substr(0, csvIndex);
6028 
6029  __SUP_COUT__ << "newValue " << linkIndex << "," << groupId << "," << newRowUID
6030  << __E__;
6031 
6032  // add row
6033  unsigned int row = cfgView->addRow(author,
6034  true /*incrementUniqueData*/,
6035  newRowUID /*baseNameAutoID*/,
6036  -1 /* rowToAdd */,
6037  linkIndex,
6038  groupId);
6039 
6040  // set UID value
6041  cfgView->setURIEncodedValue(newRowUID, row, cfgView->getColUID());
6042 
6043  // find groupId column from link index
6044  col = cfgView->getLinkGroupIDColumn(linkIndex);
6045 
6046  // set group id
6047  cfgView->setURIEncodedValue(groupId, row, col);
6048 
6049  // if TableViewColumnInfo::COL_NAME_STATUS exists, set it to true
6050  try
6051  {
6052  col = cfgView->getColStatus();
6053  cfgView->setValueAsString("1", row, col);
6054  }
6055  catch(...)
6056  {
6057  } // if not, ignore
6058  }
6059  else if(type == "delete-uid")
6060  {
6061  // delete row
6062  unsigned int row = cfgView->findRow(col, uid);
6063  cfgView->deleteRow(row);
6064  }
6065  else if(type == "tree-copy")
6066  {
6067  // recursively copy to depth
6068  __COUTV__(newValue);
6069  std::vector<std::string> paramArray =
6071  __COUTV__(StringMacros::vectorToString(paramArray));
6072 
6073  // accept either 2 params (count, depth) or 3 params (count, depth, customUID).
6074  // A non-empty customUID is honored only when copying a single instance.
6075  if(paramArray.size() < 2 || paramArray.size() > 3)
6076  {
6077  __SS__ << "Illegal parameters for tree copy request: must be number of "
6078  "copy instances & depth of copy (optionally followed by a "
6079  "custom UID for the new record)."
6080  << __E__;
6081  __SS_THROW__;
6082  }
6083 
6084  unsigned int row = cfgView->findRow(col, uid);
6085  __COUTV__(uid);
6086  __COUTV__(row);
6087  unsigned int numberOfInstances = atoi(paramArray[0].c_str());
6088  unsigned int depth = atoi(paramArray[1].c_str());
6089  std::string customUID = paramArray.size() == 3
6090  ? StringMacros::decodeURIComponent(paramArray[2])
6091  : std::string("");
6092  __COUTV__(depth);
6093  __COUTV__(numberOfInstances);
6094  __COUTV__(customUID);
6095  if(numberOfInstances > 1000)
6096  {
6097  __SS__ << "Illegal parameters - the maximum number of copy instances is "
6098  "1000. Number of instances provided was "
6099  << numberOfInstances << __E__;
6100  __SS_THROW__;
6101  }
6102  if(!customUID.empty() && numberOfInstances != 1)
6103  {
6104  __SS__ << "A custom UID can only be supplied when copying a single "
6105  "instance. Requested instances: "
6106  << numberOfInstances << __E__;
6107  __SS_THROW__;
6108  }
6109 
6110  std::map<std::string /*modified table*/, TableVersion /* modified version */>
6111  modifiedTablesMap = cfgMgr->getActiveVersions(); // handling copied from
6112  // ConfigurationGUISupervisor::handleFillModifiedTablesXML()
6113 
6114  unsigned int rowsBeforeCopy = cfgView->getNumberOfRows();
6115  ConfigurationSupervisorBase::recursiveCopyTreeUIDNode(xmlOut,
6116  cfgMgr,
6117  modifiedTablesMap,
6118  depth,
6119  depth,
6120  numberOfInstances,
6121  cfgView,
6122  uid);
6123 
6124  // if a custom UID was provided, rename the newly-copied row
6125  if(!customUID.empty() && cfgView->getNumberOfRows() > rowsBeforeCopy)
6126  {
6127  unsigned int uidCol = cfgView->getColUID();
6128  unsigned int newRow = cfgView->getNumberOfRows() - 1;
6129  cfgView->setValueAsString(customUID, newRow, uidCol);
6130  xmlOut.addTextElementToData("CopiedRecordUID", customUID);
6131  }
6132  }
6133  else if(type == "uid" || type == "value" || type == "value-groupid" ||
6134  type == "value-bool" || type == "value-bitmap" || type == "node-comment")
6135  {
6136  unsigned int row = cfgView->findRow(cfgView->getColUID(), uid);
6137  if(!cfgView->setURIEncodedValue(newValue, row, col, author))
6138  {
6139  // no change! so discard
6140  __SUP_SS__ << "Value '" << newValue
6141  << "' is the same as the current value. No need to save "
6142  "change to tree node."
6143  << __E__;
6144  __SS_THROW__;
6145  }
6146  }
6147  else if(type == "link-UID" || type == "link-GroupID")
6148  {
6149  bool isGroup;
6150  std::pair<unsigned int /*link col*/, unsigned int /*link id col*/> linkPair;
6151  if(!cfgView->getChildLink(col, isGroup, linkPair))
6152  {
6153  // not a link ?!
6154  __SUP_SS__ << "Col '" << colName << "' is not a link column." << __E__;
6155  __SS_THROW__;
6156  }
6157 
6158  __SUP_COUT__ << "linkPair " << linkPair.first << "," << linkPair.second
6159  << __E__;
6160 
6161  std::string linkIndex = cfgView->getColumnInfo(col).getChildLinkIndex();
6162 
6163  __SUP_COUT__ << "linkIndex " << linkIndex << __E__;
6164 
6165  // find table value and id value
6166  unsigned int csvIndexStart = 0, csvIndex = newValue.find(',');
6167 
6168  std::string newTable = newValue.substr(csvIndexStart, csvIndex);
6169  csvIndexStart = csvIndex + 1;
6170  csvIndex = newValue.find(',', csvIndexStart);
6171  std::string newLinkId = newValue.substr(
6172  csvIndexStart,
6173  csvIndex -
6174  csvIndexStart); // if no more commas will take the rest of string
6175 
6176  __SUP_COUT__ << "newValue " << newTable << "," << newLinkId << __E__;
6177 
6178  // change target table in two parts
6179  unsigned int row = cfgView->findRow(cfgView->getColUID(), uid);
6180  bool changed = false;
6181  bool needSecondaryChange = (type == "link-GroupID");
6182 
6183  if(!cfgView->setURIEncodedValue(newTable, row, linkPair.first, author))
6184  {
6185  // no change
6186  __SUP_COUT__ << "Value '" << newTable
6187  << "' is the same as the current value." << __E__;
6188  }
6189  else
6190  {
6191  changed = true;
6192  // do NOT need secondary change for UID
6193  }
6194 
6195  std::string originalValue = cfgView->getValueAsString(row, linkPair.second);
6196  if(!cfgView->setURIEncodedValue(newLinkId, row, linkPair.second, author))
6197  {
6198  // no change
6199  __SUP_COUT__ << "Value '" << newLinkId
6200  << "' is the same as the current value." << __E__;
6201  }
6202  else
6203  {
6204  if(!changed)
6205  needSecondaryChange =
6206  true; // if table was unchanged, then need secondary change for
6207  // UID (groupID is already assumed needed)
6208  changed = true;
6209  }
6210 
6211  if(needSecondaryChange) // do secondary changes to child table target
6212  {
6213  bool secondaryChanged = false;
6214  bool defaultIsInGroup =
6215  false; // use to indicate if a recent new member was created
6216 
6217  // first close out main target table
6218  if(!changed) // if no changes throw out new version
6219  {
6220  __SUP_COUT__ << "No changes to primary view. Erasing temporary table."
6221  << __E__;
6222  table->eraseView(temporaryVersion);
6223  }
6224  else // if changes, save it
6225  {
6226  try
6227  {
6228  cfgView->init(); // verify new table (throws runtime_errors)
6229 
6231  xmlOut,
6232  cfgMgr,
6233  tableName,
6234  version,
6235  true /*make temporary*/,
6236  table,
6237  temporaryVersion,
6238  true /*ignoreDuplicates*/); // save
6239  // temporary
6240  // version
6241  // properly
6242  }
6243  catch(std::runtime_error&
6244  e) // erase temporary view before re-throwing error
6245  {
6246  __SUP_COUT__ << "Caught error while editing main table. Erasing "
6247  "temporary version."
6248  << __E__;
6249  table->eraseView(temporaryVersion);
6250  changed = false; // undo changed bool
6251 
6252  // send warning so that, secondary table can still be changed
6253  xmlOut.addTextElementToData(
6254  "Warning",
6255  "Error saving primary tree node! " + std::string(e.what()));
6256  }
6257  }
6258 
6259  // now, onto linked table
6260 
6261  // get the current linked table/version
6262  // check if the value is new
6263  // if new edit value (in a temporary version only)
6264 
6265  __SUP_COUTV__(newValue);
6266  csvIndexStart = csvIndex + 1;
6267  csvIndex = newValue.find(',', csvIndexStart);
6268  version = TableVersion(newValue.substr(
6269  csvIndexStart, csvIndex - csvIndexStart)); // if no more commas will
6270  // take the rest of string
6271 
6272  if(newTable == TableViewColumnInfo::DATATYPE_LINK_DEFAULT)
6273  {
6274  // done, since init was already tested
6275  // the result should be purposely DISCONNECTED link
6276  return;
6277  }
6278 
6279  // get table and activate target version
6280  table = cfgMgr->getTableByName(newTable);
6281  try
6282  {
6283  table->setActiveView(version);
6284  }
6285  catch(...)
6286  {
6287  if(version.isTemporaryVersion())
6288  throw; // if temporary, there is no hope to find lost version
6289 
6290  __SUP_COUT__ << "Failed to find stored version, so attempting to "
6291  "load version: "
6292  << newTable << " v" << version << __E__;
6293  cfgMgr->getVersionedTableByName(newTable, version);
6294  }
6295 
6296  __SUP_COUT__ << newTable << " active version is "
6297  << table->getViewVersion() << __E__;
6298 
6299  if(version != table->getViewVersion())
6300  {
6301  __SUP_SS__;
6302  if(version.isMockupVersion())
6303  ss << "Target table '" << newTable
6304  << "' is likely not a member of the current table group "
6305  << "since the mock-up version was not successfully loaded. "
6306  << "\n\n"
6307  <<
6308  // same as ConfigurationGUI.html L:9833
6309  (std::string("") +
6310  "To add a table to a group, click the group name to go to "
6311  "the " +
6312  "group view, then click 'Add/Remove/Modify Member Tables.' "
6313  "You " +
6314  "can then add or remove tables and save the new group." +
6315  "\n\n" +
6316  "OR!!! Click the following button to add the table '" +
6317  newTable +
6318  "' to the currently active Configuration Group: " +
6319  "<input type='button' style='color:black !important;' " +
6320  "title='Click to add table to the active Configuration "
6321  "Group' " +
6322  "onclick='addTableToConfigurationGroup(\"" + newTable +
6323  "\"); Debug.closeErrorPop();event.stopPropagation();' "
6324  "value='Add Table'>" +
6325  "</input>")
6326  << __E__;
6327  else
6328  ss << "Target table version (" << version
6329  << ") is not the currently active version ("
6330  << table->getViewVersion() << "). Try refreshing the tree."
6331  << __E__;
6332  __SS_THROW__;
6333  }
6334 
6335  // create temporary version for editing
6336  temporaryVersion = table->createTemporaryView(version);
6337 
6338  __SUP_COUT__ << "Created temporary version " << temporaryVersion << __E__;
6339 
6340  cfgView = table->getTemporaryView(temporaryVersion);
6341 
6342  cfgView->init(); // prepare column lookup map
6343 
6344  if(type == "link-UID")
6345  {
6346  // handle UID links slightly differently
6347  // when editing link-UID,.. if specified name does not exist in child
6348  // table, then change the UID in the child table (rename target
6349  // record).
6350  // Otherwise, it is impossible to rename unique links targets in the
6351  // tree-view GUI.
6352 
6353  col = cfgView->getColUID();
6354  __SUP_COUT__ << "target col " << col << __E__;
6355 
6356  unsigned int row = -1;
6357  try
6358  {
6359  row = cfgView->findRow(col, newLinkId);
6360  }
6361  catch(...) // ignore not found error
6362  {
6363  }
6364  if(row == (unsigned int)-1) // if row not found then add a row
6365  {
6366  __SUP_COUT__ << "New link UID '" << newLinkId
6367  << "' was not found, so attempting to change UID of "
6368  "target record '"
6369  << originalValue << "'" << __E__;
6370  try
6371  {
6372  row = cfgView->findRow(col, originalValue);
6373  if(cfgView->setURIEncodedValue(newLinkId, row, col, author))
6374  {
6375  secondaryChanged = true;
6376  __SUP_COUT__ << "Original target record '"
6377  << originalValue << "' was changed to '"
6378  << newLinkId << "'" << __E__;
6379  }
6380  }
6381  catch(...) // ignore not found error
6382  {
6383  __SUP_COUT__ << "Original target record '" << originalValue
6384  << "' not found." << __E__;
6385  }
6386  }
6387  }
6388  else if(type == "link-GroupID")
6389  {
6390  // handle groupID links slightly differently
6391  // have to look at changing link table too!
6392  // if group ID, set all in member list to be members of group
6393 
6394  col = cfgView->getLinkGroupIDColumn(linkIndex);
6395 
6396  __SUP_COUT__ << "target col " << col << __E__;
6397 
6398  // extract vector of members to be
6399  std::vector<std::string> memberUIDs;
6400  do
6401  {
6402  csvIndexStart = csvIndex + 1;
6403  csvIndex = newValue.find(',', csvIndexStart);
6404  memberUIDs.push_back(
6405  newValue.substr(csvIndexStart, csvIndex - csvIndexStart));
6406  __SUP_COUT__ << "memberUIDs: " << memberUIDs.back() << __E__;
6407  } while(csvIndex !=
6408  (unsigned int)std::string::npos); // no more commas
6409 
6410  // for each row,
6411  // check if should be in group
6412  // if should be but is not
6413  // add to group, CHANGE
6414  // if should not be but is
6415  // remove from group, CHANGE
6416  //
6417 
6418  std::string targetUID;
6419  bool shouldBeInGroup;
6420  bool isInGroup;
6421 
6422  for(unsigned int row = 0; row < cfgView->getNumberOfRows(); ++row)
6423  {
6424  targetUID = cfgView->getDataView()[row][cfgView->getColUID()];
6425  __SUP_COUT__ << "targetUID: " << targetUID << __E__;
6426 
6427  shouldBeInGroup = false;
6428  for(unsigned int i = 0; i < memberUIDs.size(); ++i)
6429  if(targetUID == memberUIDs[i])
6430  {
6431  // found in member uid list
6432  shouldBeInGroup = true;
6433  break;
6434  }
6435 
6436  isInGroup = cfgView->isEntryInGroup(row, linkIndex, newLinkId);
6437 
6438  // if should be but is not
6439  if(shouldBeInGroup && !isInGroup)
6440  {
6441  __SUP_COUT__ << "Changed to YES: " << row << __E__;
6442  secondaryChanged = true;
6443 
6444  cfgView->addRowToGroup(row, col, newLinkId);
6445 
6446  } // if should not be but is
6447  else if(!shouldBeInGroup && isInGroup)
6448  {
6449  __SUP_COUT__ << "Changed to NO: " << row << __E__;
6450  secondaryChanged = true;
6451 
6452  cfgView->removeRowFromGroup(row, col, newLinkId);
6453  }
6454  else if(targetUID ==
6455  cfgView
6456  ->getDefaultRowValues()[cfgView->getColUID()] &&
6457  isInGroup)
6458  {
6459  // use to indicate if a recent new member was created
6460  defaultIsInGroup = true;
6461  }
6462  }
6463  } // end (type == "link-GroupID")
6464 
6465  // first close out main target table
6466  if(!secondaryChanged) // if no changes throw out new version
6467  {
6468  __SUP_COUT__
6469  << "No changes to secondary view. Erasing temporary table."
6470  << __E__;
6471  table->eraseView(temporaryVersion);
6472  }
6473  else // if changes, save it
6474  {
6475  try
6476  {
6477  cfgView->init(); // verify new table (throws runtime_errors)
6478 
6480  xmlOut,
6481  cfgMgr,
6482  newTable,
6483  version,
6484  true /*make temporary*/,
6485  table,
6486  temporaryVersion,
6487  true /*ignoreDuplicates*/); // save
6488  // temporary
6489  // version
6490  // properly
6491  }
6492  catch(std::runtime_error&
6493  e) // erase temporary view before re-throwing error
6494  {
6495  __SUP_COUT__ << "Caught error while editing secondary table. "
6496  "Erasing temporary version."
6497  << __E__;
6498  table->eraseView(temporaryVersion);
6499  secondaryChanged = false; // undo changed bool
6500 
6501  // send warning so that, secondary table can still be changed
6502  xmlOut.addTextElementToData(
6503  "Warning",
6504  "Error saving secondary tree node! " + std::string(e.what()));
6505  }
6506  }
6507 
6508  // Block error message if default is in group, assume new member was just
6509  // created. Blocked because its hard to detect if changes were recently
6510  // made (one idea: to check if all other values are defaults, to assume it
6511  // was just created)
6512  if(0 && !changed && !secondaryChanged && !defaultIsInGroup)
6513  {
6514  __SUP_SS__ << "Link to table '" << newTable << "', linkID '"
6515  << newLinkId
6516  << "', and selected group members are the same as the "
6517  "current value. "
6518  << "No need to save changes to tree." << __E__;
6519  __SS_THROW__;
6520  }
6521 
6522  return; // exit since table inits were already tested
6523  }
6524  else if(0 && !changed) // '0 &&' to block error message because sometimes
6525  // things get setup twice depending on the path of the
6526  // user (e.g. when editing links in tree-view)
6527  { // '0 &&' to block error message also because versions are temporary at
6528  // this point anyway, might as well abuse temporary versions
6529  __SUP_SS__ << "Link to table '" << newTable << "' and linkID '"
6530  << newLinkId
6531  << "' are the same as the current values. No need to save "
6532  "change to tree node."
6533  << __E__;
6534  __SS_THROW__;
6535  }
6536  }
6537 
6538  cfgView->init(); // verify new table (throws runtime_errors)
6539  }
6540  catch(...) // erase temporary view before re-throwing error
6541  {
6542  __SUP_COUT__ << "Caught error while editing. Erasing temporary version." << __E__;
6543  table->eraseView(temporaryVersion);
6544  throw;
6545  }
6546 
6548  xmlOut,
6549  cfgMgr,
6550  tableName,
6551  version,
6552  true /*make temporary*/,
6553  table,
6554  temporaryVersion,
6555  true /*ignoreDuplicates*/); // save temporary version properly
6556 } //end handleSaveTreeNodeEditXML()
6557 catch(std::runtime_error& e)
6558 {
6559  __SUP_SS__ << "Error saving tree node! " << e.what() << __E__;
6560  __SUP_COUT_ERR__ << "\n" << ss.str() << __E__;
6561  xmlOut.addTextElementToData("Error", ss.str());
6562 }
6563 catch(...)
6564 {
6565  __SUP_SS__ << "Unknown Error saving tree node! " << __E__;
6566  try
6567  {
6568  throw;
6569  } //one more try to printout extra info
6570  catch(const std::exception& e)
6571  {
6572  ss << "Exception message: " << e.what();
6573  }
6574  catch(...)
6575  {
6576  }
6577  __SUP_COUT_ERR__ << "\n" << ss.str() << __E__;
6578  xmlOut.addTextElementToData("Error", ss.str());
6579 } //end handleSaveTreeNodeEditXML() catch
6580 
6581 //==============================================================================
6622 void ConfigurationGUISupervisor::handleGetTableXML(HttpXmlDocument& xmlOut,
6623  ConfigurationManagerRW* cfgMgr,
6624  const std::string& tableName,
6625  TableVersion version,
6626  bool allowIllegalColumns /* = false */,
6627  bool getRawData /* = false */,
6628  bool descriptionOnly /* = false */)
6629 try
6630 {
6631  xercesc::DOMElement *parentEl, *subparentEl;
6632 
6633  std::string accumulatedErrors = "";
6634 
6635  if(allowIllegalColumns)
6636  xmlOut.addTextElementToData("allowIllegalColumns", "1");
6637 
6638  const std::map<std::string, TableInfo>& allTableInfo = cfgMgr->getAllTableInfo(
6639  allowIllegalColumns /* if allowIllegalColumns, then also refresh */,
6640  allowIllegalColumns ? &accumulatedErrors : 0,
6641  tableName); // filter errors by tableName
6642 
6643  TableBase* table = cfgMgr->getTableByName(tableName);
6644 
6645  if(!getRawData)
6646  {
6647  // send all table names along with
6648  // and check for specific version
6649  xmlOut.addTextElementToData("ExistingTableNames",
6650  TableViewColumnInfo::DATATYPE_LINK_DEFAULT);
6651  for(auto& configPair : allTableInfo)
6652  {
6653  xmlOut.addTextElementToData("ExistingTableNames", configPair.first);
6654  if(configPair.first == tableName && // check that version exists
6655  configPair.second.versions_.find(version) ==
6656  configPair.second.versions_.end())
6657  {
6658  __SUP_COUT__ << "Version not found, so using mockup." << __E__;
6659  version = TableVersion(); // use INVALID
6660  }
6661  }
6662  }
6663 
6664  xmlOut.addTextElementToData("TableName", tableName); // table name
6665  xmlOut.addTextElementToData("TableDescription",
6666  table->getTableDescription()); // table name
6667 
6668  if(descriptionOnly)
6669  return;
6670 
6671  // existing table versions
6672  if(!getRawData)
6673  {
6674  // get version aliases for translation
6675  std::map<
6676  std::string /*table name*/,
6677  std::map<std::string /*version alias*/, TableVersion /*aliased version*/>>
6678  versionAliases;
6679  try
6680  {
6681  // use whatever backbone is currently active
6682  versionAliases = cfgMgr->getVersionAliases();
6683  for(const auto& aliases : versionAliases)
6684  for(const auto& alias : aliases.second)
6685  __SUP_COUTT__ << "ALIAS: " << aliases.first << " " << alias.first
6686  << " ==> " << alias.second << __E__;
6687  }
6688  catch(const std::runtime_error& e)
6689  {
6690  __SUP_COUT__ << "Could not get backbone information for version aliases: "
6691  << e.what() << __E__;
6692  }
6693 
6694  auto tableIterator = versionAliases.find(tableName);
6695 
6696  parentEl = xmlOut.addTextElementToData("TableVersions", "");
6697  //add lo and hi spans, instead of each individual value
6698  TableVersion lo, hi;
6699  for(const TableVersion& v : allTableInfo.at(tableName).versions_)
6700  {
6701  //Steps:
6702  // 1. check for version aliases
6703  // 2. if version aliases, leave as standalone version
6704  // else stack spans of versions for faster xml transfer
6705  std::vector<std::string> aliases;
6706  if(tableIterator != versionAliases.end())
6707  {
6708  // check if this version has one or many aliases
6709  for(const auto& aliasPair : tableIterator->second)
6710  {
6711  if(v == aliasPair.second)
6712  {
6713  __SUP_COUT__ << "Found Alias " << aliasPair.second << " --> "
6714  << aliasPair.first << __E__;
6715  aliases.push_back(aliasPair.first);
6716  }
6717  }
6718  }
6719  //now have version aliases or not
6720 
6721  if(aliases.size()) //keep versions with aliases standalone
6722  {
6723  __SUP_COUT__ << "Handling version w/aliases" << __E__;
6724  }
6725  else if(lo.version() ==
6726  TableVersion::INVALID) //establish start of potential span
6727  {
6728  hi = lo = v;
6729  continue;
6730  }
6731  else if(hi.version() + 1 == v.version()) //span is growing
6732  {
6733  hi = v;
6734  continue;
6735  }
6736  //else jump by more than one, so close out span
6737 
6738  if(lo.version() != TableVersion::INVALID)
6739  {
6740  if(lo == hi) //single value
6741  xmlOut.addTextElementToParent("Version", lo.toString(), parentEl);
6742  else //span
6743  xmlOut.addTextElementToParent(
6744  "Version", "_" + lo.toString() + "_" + hi.toString(), parentEl);
6745  }
6746  hi = lo = v.version();
6747 
6748  if(versionAliases.size()) //keep versions with aliases standalone
6749  {
6750  subparentEl =
6751  xmlOut.addTextElementToParent("Version", v.toString(), parentEl);
6752  for(const auto& alias : aliases)
6753  xmlOut.addTextElementToParent("VersionAlias", alias, subparentEl);
6754  hi = lo = TableVersion::INVALID; //invalidate for fresh start
6755  } //end version alias handling
6756 
6757  } //end version loop
6758 
6759  if(lo.version() != TableVersion::INVALID) //check if last one to do!
6760  {
6761  if(lo == hi) //single value
6762  xmlOut.addTextElementToParent("Version", lo.toString(), parentEl);
6763  else //span
6764  xmlOut.addTextElementToParent(
6765  "Version", "_" + lo.toString() + "_" + hi.toString(), parentEl);
6766  }
6767  } //end existing table version handling
6768 
6769  // table columns and then rows (from table view)
6770 
6771  // get view pointer
6772  TableView* tableViewPtr;
6773  if(version.isInvalid()) // use mock-up
6774  {
6775  tableViewPtr = table->getMockupViewP();
6776  }
6777  else // use view version
6778  {
6779  try
6780  {
6781  // locally accumulate 'manageable' errors getting the version to avoid
6782  // reverting to mockup
6783  std::string localAccumulatedErrors = "";
6784  tableViewPtr =
6785  cfgMgr
6786  ->getVersionedTableByName(tableName,
6787  version,
6788  allowIllegalColumns /*looseColumnMatching*/,
6789  &localAccumulatedErrors,
6790  getRawData)
6791  ->getViewP();
6792 
6793  if(getRawData)
6794  {
6795  xmlOut.addTextElementToData("TableRawData",
6796  tableViewPtr->getSourceRawData());
6797 
6798  const std::set<std::string>& srcColNames =
6799  tableViewPtr->getSourceColumnNames();
6800  for(auto& srcColName : srcColNames)
6801  xmlOut.addTextElementToData("ColumnHeader", srcColName);
6802 
6803  if(!version.isTemporaryVersion())
6804  {
6805  // if version is temporary, view is already ok
6806  table->eraseView(
6807  version); // clear so that the next get will fill the table
6808  tableViewPtr = cfgMgr
6810  tableName,
6811  version,
6812  allowIllegalColumns /*looseColumnMatching*/,
6813  &localAccumulatedErrors,
6814  false /* getRawData */)
6815  ->getViewP();
6816  }
6817  } // end rawData handling
6818 
6819  if(localAccumulatedErrors != "")
6820  xmlOut.addTextElementToData("Error", localAccumulatedErrors);
6821  }
6822  catch(std::runtime_error& e) // default to mock-up for fail-safe in GUI editor
6823  {
6824  __SUP_SS__ << "Failed to get table " << tableName << " version " << version
6825  << "... defaulting to mock-up! " << __E__;
6826  ss << "\n\n...Here is why it failed:\n\n" << e.what() << __E__;
6827 
6828  __SUP_COUT_ERR__ << "\n" << ss.str();
6829  version = TableVersion();
6830  tableViewPtr = table->getMockupViewP();
6831 
6832  xmlOut.addTextElementToData("Error", "Error getting view! " + ss.str());
6833  }
6834  catch(...) // default to mock-up for fail-safe in GUI editor
6835  {
6836  __SUP_SS__ << "Failed to get table " << tableName << " version: " << version
6837  << "... defaulting to mock-up! "
6838  << "(You may want to try again to see what was partially loaded "
6839  "into cache before failure. "
6840  << "If you think, the failure is due to a column name change, "
6841  << "you can also try to Copy the failing view to the new column "
6842  "names using "
6843  << "'Copy and Move' functionality.)" << __E__;
6844  try
6845  {
6846  throw;
6847  } //one more try to printout extra info
6848  catch(const std::exception& e)
6849  {
6850  ss << "Exception message: " << e.what();
6851  }
6852  catch(...)
6853  {
6854  }
6855 
6856  __SUP_COUT_ERR__ << "\n" << ss.str();
6857  version = TableVersion();
6858  tableViewPtr = table->getMockupViewP();
6859 
6860  xmlOut.addTextElementToData("Error", "Error getting view! " + ss.str());
6861  }
6862  }
6863  xmlOut.addTextElementToData("TableVersion", version.toString()); // table version
6864 
6865  if(getRawData)
6866  return; // no need to go further for rawData handling
6867 
6868  // get 'columns' of view
6869  xercesc::DOMElement* choicesParentEl;
6870  parentEl = xmlOut.addTextElementToData("CurrentVersionColumnHeaders", "");
6871 
6872  std::vector<TableViewColumnInfo> colInfo = tableViewPtr->getColumnsInfo();
6873 
6874  for(int i = 0; i < (int)colInfo.size(); ++i) // column headers and types
6875  {
6876  xmlOut.addTextElementToParent("ColumnHeader", colInfo[i].getName(), parentEl);
6877  xmlOut.addTextElementToParent("ColumnType", colInfo[i].getType(), parentEl);
6878  xmlOut.addTextElementToParent(
6879  "ColumnDataType", colInfo[i].getDataType(), parentEl);
6880 
6881  // NOTE!! ColumnDefaultValue defaults may be unique to this version of the table,
6882  // whereas DefaultRowValue are the defaults for the mockup
6883  xmlOut.addTextElementToParent(
6884  "ColumnDefaultValue", colInfo[i].getDefaultValue(), parentEl);
6885 
6886  choicesParentEl = xmlOut.addTextElementToParent("ColumnChoices", "", parentEl);
6887  // add data choices if necessary
6888  if(colInfo[i].getType() == TableViewColumnInfo::TYPE_FIXED_CHOICE_DATA ||
6889  colInfo[i].getType() == TableViewColumnInfo::TYPE_BITMAP_DATA ||
6890  colInfo[i].isChildLink())
6891  {
6892  for(auto& choice : colInfo[i].getDataChoices())
6893  xmlOut.addTextElementToParent("ColumnChoice", choice, choicesParentEl);
6894  }
6895 
6896  xmlOut.addTextElementToParent(
6897  "ColumnMinValue", colInfo[i].getMinValue(), parentEl);
6898  xmlOut.addTextElementToParent(
6899  "ColumnMaxValue", colInfo[i].getMaxValue(), parentEl);
6900  }
6901 
6902  // verify mockup columns after columns are posted to xmlOut
6903  try
6904  {
6905  if(version.isInvalid())
6906  tableViewPtr->init();
6907  }
6908  catch(std::runtime_error& e)
6909  {
6910  // append accumulated errors, because they may be most useful
6911  __THROW__(e.what() + std::string("\n\n") + accumulatedErrors);
6912  }
6913  catch(...)
6914  {
6915  throw;
6916  }
6917 
6918  parentEl = xmlOut.addTextElementToData("CurrentVersionRows", "");
6919 
6920  int numRows = (int)tableViewPtr->getNumberOfRows();
6921  int numCols = (int)tableViewPtr->getNumberOfColumns();
6922 
6923  for(int c = 0; c < numCols; ++c)
6924  {
6925  std::string csvStr;
6926  csvStr.reserve(numRows * 20);
6927  for(int r = 0; r < numRows; ++r)
6928  {
6929  if(r > 0)
6930  csvStr += ",";
6931  if(colInfo[c].getDataType() == TableViewColumnInfo::DATATYPE_TIME)
6932  {
6933  std::string timeAsString;
6934  tableViewPtr->getValue(timeAsString, r, c);
6935  csvStr += StringMacros::encodeURIComponent(timeAsString);
6936  }
6937  else
6938  csvStr +=
6939  StringMacros::encodeURIComponent(tableViewPtr->getDataView()[r][c]);
6940  }
6941  xmlOut.addTextElementToParent("ColCSV", csvStr, parentEl);
6942  }
6943 
6944  // add "other" fields associated with configView
6945  xmlOut.addTextElementToData("TableComment", tableViewPtr->getComment());
6946  xmlOut.addTextElementToData("TableAuthor", tableViewPtr->getAuthor());
6947  xmlOut.addTextElementToData("TableCreationTime",
6948  std::to_string(tableViewPtr->getCreationTime()));
6949  xmlOut.addTextElementToData("TableLastAccessTime",
6950  std::to_string(tableViewPtr->getLastAccessTime()));
6951 
6952  // add to xml the default row values
6953  // NOTE!! ColumnDefaultValue defaults may be unique to this version of the table,
6954  // whereas DefaultRowValue are the defaults for the mockup
6955  std::vector<std::string> defaultRowValues =
6956  table->getMockupViewP()->getDefaultRowValues();
6957  // don't give author and time.. force default author, let JS fill time
6958  for(unsigned int c = 0; c < defaultRowValues.size() - 2; ++c)
6959  {
6960  xmlOut.addTextElementToData("DefaultRowValue", defaultRowValues[c]);
6961  }
6962 
6963  const std::set<std::string> srcColNames = tableViewPtr->getSourceColumnNames();
6964 
6965  if(accumulatedErrors != "") // add accumulated errors to xmlOut
6966  {
6967  __SUP_SS__ << (std::string("Column errors were allowed for this request, so "
6968  "perhaps you can ignore this, ") +
6969  "but please note the following warnings:\n" + accumulatedErrors)
6970  << __E__;
6971  __SUP_COUT_ERR__ << ss.str();
6972  xmlOut.addTextElementToData("TableWarnings", ss.str());
6973  }
6974  else if(!version.isTemporaryVersion() && // not temporary (these are not filled from
6975  // interface source)
6976  (srcColNames.size() != tableViewPtr->getNumberOfColumns() ||
6977  tableViewPtr->getSourceColumnMismatch() !=
6978  0)) // check for column size mismatch
6979  {
6980  __SUP_SS__ << "\n\nThere were warnings found when loading the table " << tableName
6981  << ":v" << version << ". Please see the details below:\n\n"
6982  << tableViewPtr->getMismatchColumnInfo();
6983 
6984  __SUP_COUT__ << "\n" << ss.str();
6985  xmlOut.addTextElementToData("TableWarnings", ss.str());
6986  }
6987 
6988 } // end handleGetTableXML()
6989 catch(std::runtime_error& e)
6990 {
6991  __SUP_SS__ << "Error getting table view!\n\n " << e.what() << __E__;
6992  __SUP_COUT_ERR__ << ss.str();
6993  xmlOut.addTextElementToData("Error", ss.str());
6994 }
6995 catch(...)
6996 {
6997  __SUP_SS__ << "Error getting table view!\n\n " << __E__;
6998  try
6999  {
7000  throw;
7001  } //one more try to printout extra info
7002  catch(const std::exception& e)
7003  {
7004  ss << "Exception message: " << e.what();
7005  }
7006  catch(...)
7007  {
7008  }
7009  __SUP_COUT_ERR__ << ss.str();
7010  xmlOut.addTextElementToData("Error", ss.str());
7011 } // end handleGetTableXML() catch
7012 
7013 //==============================================================================
7020 ConfigurationManagerRW* ConfigurationGUISupervisor::refreshUserSession(
7021  std::string username, bool refresh)
7022 {
7023  uint64_t sessionIndex =
7024  0; // make session by username for now! (may never want to change back)
7025 
7026  std::stringstream ssMapKey;
7027  ssMapKey << username << ":" << sessionIndex;
7028  std::string mapKey = ssMapKey.str();
7029  __SUP_COUTT__ << "Using Config Session " << mapKey
7030  << " ... Total Session Count: " << userConfigurationManagers_.size()
7031  << " refresh=" << refresh << __E__;
7032 
7033  time_t now = time(0);
7034 
7035  // create new table mgr if not one for active session index
7036 
7037  if(TTEST(1))
7038  {
7039  for(auto& pair : userConfigurationManagers_)
7040  __SUP_COUTTV__(pair.first);
7041  }
7042 
7043  const std::string preLoadCfgMgrName = ":0";
7044  if(userConfigurationManagers_.size() == 1 &&
7045  userConfigurationManagers_.find(preLoadCfgMgrName) !=
7046  userConfigurationManagers_.end())
7047  {
7048  __SUP_COUT__ << "Using pre-loaded Configuration Manager. time=" << time(0) << " "
7049  << clock() << " Setting author from "
7050  << userConfigurationManagers_.at(preLoadCfgMgrName)->getUsername()
7051  << " to " << username << __E__;
7052  userConfigurationManagers_[mapKey] =
7053  userConfigurationManagers_.at(preLoadCfgMgrName);
7054  userLastUseTime_[mapKey] = userLastUseTime_.at(preLoadCfgMgrName);
7055  //also set author!
7056  userConfigurationManagers_.at(mapKey)->setUsername(username);
7057  }
7058 
7059  if(userConfigurationManagers_.find(mapKey) == userConfigurationManagers_.end())
7060  {
7061  __SUP_COUT__ << "Creating new Configuration Manager. time=" << time(0) << " "
7062  << clock() << __E__;
7063  userConfigurationManagers_[mapKey] = new ConfigurationManagerRW(username);
7064 
7065  // update table info for each new configuration manager
7066  // IMPORTANTLY this also fills all configuration manager pointers with instances,
7067  // so we are not dealing with changing pointers later on
7068  userConfigurationManagers_[mapKey]->getAllTableInfo(
7069  true /* refresh */, // load empty instance of everything important
7070  0 /* accumulatedWarnings */,
7071  "" /* errorFilterName */,
7072  true /* getGroupKeys */,
7073  false /* getGroupInfo */,
7074  true /* initializeActiveGroups */);
7075  }
7076  else if(userLastUseTime_.find(mapKey) == userLastUseTime_.end())
7077  {
7078  __SUP_SS__ << "Fatal error managing user sessions! Check the logs for "
7079  "Configuration Interface failure."
7080  << __E__;
7081  __SUP_COUT_ERR__ << "\n" << ss.str();
7082  __SS_THROW__;
7083  }
7084  else if(
7085  refresh ||
7086  (now - userLastUseTime_[mapKey]) >
7087  CONFIGURATION_MANAGER_REFRESH_THRESHOLD) // check if should refresh all table info
7088  {
7089  __SUP_COUT__ << "Refreshing all table info." << __E__;
7090  userConfigurationManagers_[mapKey]->getAllTableInfo(
7091  true /* refresh */,
7092  0 /* accumulatedWarnings */,
7093  "" /* errorFilterName */,
7094  false /* getGroupKeys */,
7095  false /* getGroupInfo */,
7096  true /* initializeActiveGroups */);
7097  }
7098  __SUP_COUTT__ << "Configuration Manager for author="
7099  << userConfigurationManagers_[mapKey]->getUsername()
7100  << " ready. time=" << time(0) << " " << clock() << " runTimeSeconds()="
7101  << userConfigurationManagers_[mapKey]->runTimeSeconds() << __E__;
7102 
7103  // update active sessionIndex last use time
7104  userLastUseTime_[mapKey] = now;
7105 
7106  // check for stale sessions and remove them (so table user maps do not grow forever)
7107  for(std::map<std::string, time_t>::iterator it = userLastUseTime_.begin();
7108  it != userLastUseTime_.end();
7109  ++it)
7110  if(now - it->second > CONFIGURATION_MANAGER_EXPIRATION_TIME) // expired!
7111  {
7112  __SUP_COUT__ << now << ":" << it->second << " = " << now - it->second
7113  << __E__;
7114  delete userConfigurationManagers_[it->first]; // call destructor
7115  if(!(userConfigurationManagers_.erase(it->first))) // erase by key
7116  {
7117  __SUP_SS__ << "Fatal error erasing configuration manager by key!"
7118  << __E__;
7119  __SUP_COUT_ERR__ << "\n" << ss.str();
7120  __SS_THROW__;
7121  }
7122  userLastUseTime_.erase(it); // erase by iterator
7123 
7124  it =
7125  userLastUseTime_
7126  .begin(); // fail safe.. reset it, to avoid trying to understand what happens with the next iterator
7127  }
7128 
7129  return userConfigurationManagers_[mapKey];
7130 } //end refreshUserSession()
7131 
7132 //==============================================================================
7137 void ConfigurationGUISupervisor::handleDeleteTableInfoXML(HttpXmlDocument& xmlOut,
7138  ConfigurationManagerRW* cfgMgr,
7139  std::string& tableName)
7140 {
7141  if(0 == rename((TABLE_INFO_PATH + tableName + TABLE_INFO_EXT).c_str(),
7142  (TABLE_INFO_PATH + tableName + TABLE_INFO_EXT + ".unused").c_str()))
7143  __SUP_COUT_INFO__ << ("Table Info File successfully renamed: " +
7144  (TABLE_INFO_PATH + tableName + TABLE_INFO_EXT + ".unused"))
7145  << __E__;
7146  else
7147  {
7148  __SUP_COUT_ERR__ << ("Error renaming file to " +
7149  (TABLE_INFO_PATH + tableName + TABLE_INFO_EXT + ".unused"))
7150  << __E__;
7151 
7152  xmlOut.addTextElementToData(
7153  "Error",
7154  ("Error renaming Table Info File to " +
7155  (TABLE_INFO_PATH + tableName + TABLE_INFO_EXT + ".unused")));
7156  return;
7157  }
7158 
7159  // reload all with refresh to remove new table
7160  cfgMgr->getAllTableInfo(true /* refresh */);
7161 } // end handleDeleteTableInfoXML()
7162 
7163 //==============================================================================
7170 void ConfigurationGUISupervisor::handleSaveTableInfoXML(
7171  HttpXmlDocument& xmlOut,
7172  ConfigurationManagerRW* cfgMgr,
7173  std::string& tableName,
7174  const std::string& data,
7175  const std::string& tableDescription,
7176  const std::string& columnChoicesCSV,
7177  bool allowOverwrite)
7178 {
7179  // create all caps name and validate
7180  // only allow alpha-numeric names with "Table" at end
7181  std::string capsName;
7182  try
7183  {
7184  capsName = TableBase::convertToCaps(tableName, true);
7185  }
7186  catch(std::runtime_error& e)
7187  { // error! non-alpha
7188  xmlOut.addTextElementToData("Error", e.what());
7189  return;
7190  }
7191 
7192  if(!allowOverwrite)
7193  {
7194  FILE* fp = fopen((TABLE_INFO_PATH + tableName + TABLE_INFO_EXT).c_str(), "r");
7195  if(fp)
7196  {
7197  fclose(fp);
7198  xmlOut.addTextElementToData("TableName", tableName);
7199  xmlOut.addTextElementToData("OverwriteError", "1");
7200  xmlOut.addTextElementToData(
7201  "Error",
7202  "File already exists! ('" +
7203  (TABLE_INFO_PATH + tableName + TABLE_INFO_EXT) + "')");
7204  return;
7205  }
7206  }
7207 
7208  __SUP_COUT__ << "capsName=" << capsName << __E__;
7209  __SUP_COUT__ << "tableName=" << tableName << __E__;
7210  __SUP_COUT__ << "tableDescription=" << tableDescription << __E__;
7211  __SUP_COUT__ << "columnChoicesCSV=" << columnChoicesCSV << __E__;
7212 
7213  // create preview string to validate column info before write to file
7214  std::stringstream outss;
7215 
7216  outss << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n";
7217  outss << "\t<ROOT xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
7218  "xsi:noNamespaceSchemaLocation=\"TableInfo.xsd\">\n";
7219  outss << "\t\t<TABLE Name=\"" << tableName << "\">\n";
7220  outss << "\t\t\t<VIEW Name=\"" << capsName
7221  << "\" Type=\"File,Database,DatabaseTest\" Description=\"" << tableDescription
7222  << "\">\n";
7223 
7224  // each column is represented by 4 fields or 6
7225  // - type, name, dataType, defaultValue, minValue, maxValue
7226 
7227  std::istringstream columnChoicesISS(columnChoicesCSV);
7228  std::string columnChoicesString;
7229  std::string columnDefaultValue, columnMinValue, columnMaxValue;
7230  std::vector<std::string> columnParameters;
7231  std::vector<std::string> columnData =
7232  StringMacros::getVectorFromString(data, {';'} /*delimiter*/);
7233 
7234  std::set<std::string> childLinkIndices, childLinkUIDIndices, childLinkGroupIDIndices;
7235 
7236  for(unsigned int c = 0; c < columnData.size() - 1; ++c)
7237  {
7238  columnParameters =
7239  StringMacros::getVectorFromString(columnData[c], {','} /*delimiter*/);
7240  __COUT__ << "Column #" << c << ": "
7241  << StringMacros::vectorToString(columnParameters) << __E__;
7242  for(unsigned int p = 0; p < columnParameters.size(); ++p)
7243  {
7244  __COUT__ << "\t Parameter #" << p << ": " << columnParameters[p] << __E__;
7245  }
7246  __COUT__ << "\t creating the new xml" << __E__;
7247 
7248  std::string& columnType = columnParameters[0];
7249  std::string& columnName = columnParameters[1];
7250  std::string& columnDataType = columnParameters[2];
7251  const std::string columnStorageName =
7252  TableBase::convertToCaps(columnName); // now caps;
7253 
7254  outss << "\t\t\t\t<COLUMN Type=\"";
7255  outss << columnType;
7256  outss << "\" \t Name=\"";
7257  outss << columnName;
7258  outss << "\" \t StorageName=\"";
7259  try
7260  {
7261  outss << columnStorageName;
7262  }
7263  catch(std::runtime_error& e)
7264  { // error! non-alpha
7265  xmlOut.addTextElementToData(
7266  "Error",
7267  std::string("For column name '") + columnName + "' - " + e.what());
7268  return;
7269  }
7270  outss << "\" \t DataType=\"";
7271  outss << columnDataType;
7272 
7273  columnDefaultValue = StringMacros::decodeURIComponent(columnParameters[3]);
7274 
7275  std::string* columnDefaultValuePtr = nullptr;
7276  if(columnDefaultValue !=
7277  TableViewColumnInfo::getDefaultDefaultValue(columnType, columnDataType))
7278  {
7279  __SUP_COUT__ << "FOUND user spec'd default value '" << columnDefaultValue
7280  << "'" << __E__;
7281  outss << "\" \t DefaultValue=\"";
7282  outss << columnParameters[3];
7283  columnDefaultValuePtr = &columnParameters[3];
7284  }
7285  getline(columnChoicesISS, columnChoicesString, ';');
7286  outss << "\" \t DataChoices=\"";
7287  outss << columnChoicesString;
7288 
7289  std::string* columnMinValuePtr = nullptr;
7290  std::string* columnMaxValuePtr = nullptr;
7291 
7292  if(columnParameters.size() > 4 &&
7293  columnDataType == TableViewColumnInfo::DATATYPE_NUMBER)
7294  {
7295  columnMinValue = StringMacros::decodeURIComponent(columnParameters[4]);
7296  if(columnMinValue != "")
7297  {
7298  if(columnMinValue !=
7300  {
7301  __SUP_COUT__ << "FOUND user spec'd min value '" << columnParameters[4]
7302  << "'" << __E__;
7305  {
7306  __SS__ << "Inavlid user spec'd min value '" << columnParameters[4]
7307  << "' which evaluates to '" << columnMinValue
7308  << "' and is not a valid number. The minimum value must "
7309  "be a number (environment variables and math "
7310  "operations are allowed)."
7311  << __E__;
7312  __SS_THROW__;
7313  }
7314  outss << "\" \t MinValue=\"" << columnParameters[4];
7315  columnMinValuePtr = &columnParameters[4];
7316  }
7317  }
7318 
7319  columnMaxValue = StringMacros::decodeURIComponent(columnParameters[5]);
7320  if(columnMaxValue != "")
7321  {
7322  if(columnMaxValue !=
7324  {
7325  __SUP_COUT__ << "FOUND user spec'd max value = " << columnMaxValue
7326  << __E__;
7329  {
7330  __SS__ << "Inavlid user spec'd max value '" << columnParameters[5]
7331  << "' which evaluates to '" << columnMaxValue
7332  << "' and is not a valid number. The maximum value must "
7333  "be a number (environment variables and math "
7334  "operations are allowed)."
7335  << __E__;
7336  __SS_THROW__;
7337  }
7338  outss << "\" \t MaxValue=\"" << columnParameters[5];
7339  columnMaxValuePtr = &columnParameters[5];
7340  }
7341  }
7342  }
7343 
7344  //validate each column before saving a bad table file
7345  try
7346  {
7347  TableViewColumnInfo testCol(columnType,
7348  columnName,
7349  columnStorageName,
7350  columnDataType,
7351  columnDefaultValuePtr,
7352  columnChoicesString,
7353  columnMinValuePtr,
7354  columnMaxValuePtr,
7355  nullptr //capturedExceptionString
7356  );
7357  }
7358  catch(const std::runtime_error& e)
7359  {
7360  __SS__ << "Error identified with Column #" << c << ": \n" << e.what();
7361  __SS_THROW__;
7362  }
7363 
7364  if(TableViewColumnInfo::isChildLink(columnType))
7365  childLinkIndices.insert(columnType.substr(sizeof("ChildLink-") - 1));
7366  else if(columnType.find("ChildLinkUID-") == 0)
7367  childLinkUIDIndices.insert(columnType.substr(sizeof("ChildLinkUID-") - 1));
7368  else if(columnType.find("ChildLinkGroupID-") == 0)
7369  childLinkGroupIDIndices.insert(
7370  columnType.substr(sizeof("ChildLinkGroupID-") - 1));
7371 
7372  outss << "\"/>\n";
7373  }
7374 
7375  // Cross-column validation: every ChildLinkUID and ChildLinkGroupID must
7376  // have a matching ChildLink with the same index.
7377  for(const auto& idx : childLinkUIDIndices)
7378  if(childLinkIndices.find(idx) == childLinkIndices.end())
7379  {
7380  __SS__ << "Column type 'ChildLinkUID-" << idx
7381  << "' has no matching 'ChildLink-" << idx
7382  << "' column. A ChildLinkUID column must be paired with a "
7383  "ChildLink column using the same link index."
7384  << __E__;
7385  __SS_THROW__;
7386  }
7387  for(const auto& idx : childLinkGroupIDIndices)
7388  if(childLinkIndices.find(idx) == childLinkIndices.end())
7389  {
7390  __SS__ << "Column type 'ChildLinkGroupID-" << idx
7391  << "' has no matching 'ChildLink-" << idx
7392  << "' column. A ChildLinkGroupID column must be paired with a "
7393  "ChildLink column using the same link index. "
7394  "Did you intend to make a target of a Group Link? "
7395  "If so, use the 'GroupID' column type instead."
7396  << __E__;
7397  __SS_THROW__;
7398  }
7399  for(const auto& idx : childLinkIndices)
7400  if(childLinkUIDIndices.find(idx) == childLinkUIDIndices.end() &&
7401  childLinkGroupIDIndices.find(idx) == childLinkGroupIDIndices.end())
7402  {
7403  __SS__ << "Column type 'ChildLink-" << idx
7404  << "' has no matching 'ChildLinkUID-" << idx
7405  << "' or 'ChildLinkGroupID-" << idx
7406  << "' column. A ChildLink column must be paired with either a "
7407  "ChildLinkUID or ChildLinkGroupID column using the same link index."
7408  << __E__;
7409  __SS_THROW__;
7410  }
7411 
7412  outss << "\t\t\t</VIEW>\n";
7413  outss << "\t\t</TABLE>\n";
7414  outss << "\t</ROOT>\n";
7415 
7416  __SUP_COUT__ << outss.str() << __E__;
7417 
7418  FILE* fp = fopen((TABLE_INFO_PATH + tableName + TABLE_INFO_EXT).c_str(), "w");
7419  if(!fp)
7420  {
7421  xmlOut.addTextElementToData("Error",
7422  "Failed to open destination Table Info file:" +
7423  (TABLE_INFO_PATH + tableName + TABLE_INFO_EXT));
7424  return;
7425  }
7426 
7427  fprintf(fp, "%s", outss.str().c_str());
7428  fclose(fp);
7429 
7430  __SUP_COUT_INFO__ << "Finished saving Table Info for '" << tableName
7431  << ".' Looking for errors in all table column info..." << __E__;
7432 
7433  // reload all table info with refresh AND reset to pick up possibly new table
7434  // check for errors related to this tableName
7435  std::string accumulatedErrors = "";
7436  cfgMgr->getAllTableInfo(true /* refresh */, &accumulatedErrors, tableName);
7437 
7438  // if errors associated with this table name stop and report
7439  if(accumulatedErrors != "")
7440  {
7441  __SUP_SS__ << ("The new version of the '" + tableName +
7442  "' table column info was saved, however errors were detected "
7443  "reading back the table '" +
7444  tableName + "' after the save attempt:\n\n" + accumulatedErrors)
7445  << __E__;
7446 
7447  __SUP_COUT_ERR__ << ss.str() << __E__;
7448  xmlOut.addTextElementToData("Error", ss.str());
7449 
7450  return;
7451  }
7452 
7453  // return the new table info
7454  handleGetTableXML(xmlOut, cfgMgr, tableName, TableVersion());
7455 
7456  // After save, debug all table column info
7457  const std::map<std::string, TableInfo>& allTableInfo = cfgMgr->getAllTableInfo();
7458 
7459  // give a print out of currently illegal table column info
7460  for(const auto& cfgInfo : allTableInfo)
7461  {
7462  try
7463  {
7464  cfgMgr->getTableByName(cfgInfo.first)->getMockupViewP()->init();
7465  }
7466  catch(std::runtime_error& e)
7467  {
7468  __SUP_COUT_WARN__ << "\n\n##############################################\n"
7469  << "Error identified in column info of table '"
7470  << cfgInfo.first << "':\n\n"
7471  << e.what() << "\n\n"
7472  << __E__;
7473  }
7474  }
7475 } // end handleSaveTableInfoXML()
7476 
7477 //==============================================================================
7485 void ConfigurationGUISupervisor::handleSetGroupAliasInBackboneXML(
7486  HttpXmlDocument& xmlOut,
7487  ConfigurationManagerRW* cfgMgr,
7488  const std::string& groupAliasCSV,
7489  const std::string& groupNameCSV,
7490  const std::string& groupKeyCSV,
7491  const std::string& author)
7492 try
7493 {
7494  cfgMgr->loadConfigurationBackbone();
7495  std::map<std::string, TableVersion> activeVersions = cfgMgr->getActiveVersions();
7496 
7497  const std::string groupAliasesTableName =
7498  ConfigurationManager::GROUP_ALIASES_TABLE_NAME;
7499  if(activeVersions.find(groupAliasesTableName) == activeVersions.end())
7500  {
7501  __SUP_SS__ << "Active version of " << groupAliasesTableName << " missing!"
7502  << __E__;
7503  xmlOut.addTextElementToData("Error", ss.str());
7504  return;
7505  }
7506 
7507  // put all old backbone versions in xmlOut
7508  const std::set<std::string> backboneMembers = cfgMgr->getBackboneMemberNames();
7509  for(auto& memberName : backboneMembers)
7510  {
7511  __SUP_COUT__ << "activeVersions[\"" << memberName
7512  << "\"]=" << activeVersions[memberName] << __E__;
7513 
7514  xmlOut.addTextElementToData("oldBackboneName", memberName);
7515  xmlOut.addTextElementToData("oldBackboneVersion",
7516  activeVersions[memberName].toString());
7517  }
7518 
7519  // make a temporary version from active view
7520  // modify the chosen groupAlias row
7521  // save as new version
7522 
7523  TableBase* table = cfgMgr->getTableByName(groupAliasesTableName);
7524  TableVersion originalVersion = activeVersions[groupAliasesTableName];
7525  TableVersion temporaryVersion = table->createTemporaryView(originalVersion);
7526 
7527  __SUP_COUT__ << "\t\t temporaryVersion: " << temporaryVersion << __E__;
7528  bool isDifferent = false;
7529 
7530  try
7531  {
7532  TableView* configView = table->getTemporaryView(temporaryVersion);
7533 
7534  unsigned int col = configView->findCol("GroupKeyAlias");
7535  unsigned int ccol = configView->findCol(TableViewColumnInfo::COL_NAME_COMMENT);
7536  unsigned int ncol = configView->findCol("GroupName");
7537  unsigned int kcol = configView->findCol("GroupKey");
7538 
7539  // only make a new version if we are changing compared to active backbone
7540  std::vector<std::string> groupAliases =
7541  StringMacros::getVectorFromString(groupAliasCSV);
7542  std::vector<std::string> groupNames =
7543  StringMacros::getVectorFromString(groupNameCSV);
7544  std::vector<std::string> groupKeys =
7545  StringMacros::getVectorFromString(groupKeyCSV);
7546  __SUP_COUTV__(StringMacros::vectorToString(groupAliases));
7547  __SUP_COUTV__(StringMacros::vectorToString(groupNames));
7548  __SUP_COUTV__(StringMacros::vectorToString(groupKeys));
7549 
7550  size_t i = 0;
7551  for(const auto& groupAlias : groupAliases)
7552  {
7553  if(groupAlias == "" || groupNames[i] == "" || groupKeys[i] == "")
7554  {
7555  //skip empty aliases
7556  __SUP_COUT_WARN__ << "Empty alias parameter found [" << i << "] = {"
7557  << groupAlias << ", " << groupNames[i] << "("
7558  << groupKeys[i] << ")}" << __E__;
7559  ++i;
7560  continue;
7561  }
7562 
7563  bool localIsDifferent = false;
7564  const std::string& groupName = groupNames[i];
7565  const TableGroupKey groupKey(groupKeys[i]);
7566  ++i;
7567 
7568  unsigned int row = -1;
7569  // find groupAlias row
7570  try
7571  {
7572  row = configView->findRow(col, groupAlias);
7573  }
7574  catch(...) // ignore not found error
7575  {
7576  }
7577 
7578  if(row == (unsigned int)-1) // if row not found then add a row
7579  {
7580  localIsDifferent = true;
7581  row = configView->addRow();
7582 
7583  // set all columns in new row
7584  configView->setValue(
7585  "This Group Alias was automatically setup by the server.", row, ccol);
7586  configView->setValue(groupAlias, row, col);
7587  }
7588 
7589  __SUP_COUT__ << "\t\t row: " << row << __E__;
7590 
7591  __SUP_COUT__ << "\t\t groupName: " << groupName << " vs "
7592  << configView->getDataView()[row][ncol] << __E__;
7593  if(groupName != configView->getDataView()[row][ncol])
7594  {
7595  configView->setValue(groupName, row, ncol);
7596  localIsDifferent = true;
7597  }
7598 
7599  __SUP_COUT__ << "\t\t groupKey: " << groupKey << " vs "
7600  << configView->getDataView()[row][kcol] << __E__;
7601  if(groupKey.toString() != configView->getDataView()[row][kcol])
7602  {
7603  configView->setValue(groupKey.toString(), row, kcol);
7604  localIsDifferent = true;
7605  }
7606 
7607  if(localIsDifferent) // set author/time of new record if different
7608  {
7609  configView->setValue(
7610  author,
7611  row,
7612  configView->findCol(TableViewColumnInfo::COL_NAME_AUTHOR));
7613  configView->setValue(
7614  time(0),
7615  row,
7616  configView->findCol(TableViewColumnInfo::COL_NAME_CREATION));
7617  isDifferent = true;
7618  }
7619  } //end group alias modify loop
7620  }
7621  catch(...)
7622  {
7623  __SUP_COUT_ERR__ << "Error editing Group Alias view!" << __E__;
7624 
7625  // delete temporaryVersion
7626  table->eraseView(temporaryVersion);
7627  throw;
7628  }
7629 
7630  TableVersion newAssignedVersion;
7631  if(isDifferent) // make new version if different
7632  {
7633  __SUP_COUT__ << "\t\t**************************** Save as new table version"
7634  << __E__;
7635 
7636  // save or find equivalent
7638  xmlOut,
7639  cfgMgr,
7640  table->getTableName(),
7641  originalVersion,
7642  false /*makeTemporary*/,
7643  table,
7644  temporaryVersion,
7645  false /*ignoreDuplicates*/,
7646  true /*lookForEquivalent*/);
7647  }
7648  else // use existing version
7649  {
7650  __SUP_COUT__
7651  << "\t\t**************************** Using the existing table version"
7652  << __E__;
7653 
7654  // delete temporaryVersion
7655  table->eraseView(temporaryVersion);
7656  newAssignedVersion = activeVersions[groupAliasesTableName];
7657 
7658  xmlOut.addTextElementToData("savedName", groupAliasesTableName);
7659  xmlOut.addTextElementToData("savedVersion", newAssignedVersion.toString());
7660  }
7661 
7662  __SUP_COUT__ << "\t\t newAssignedVersion: " << newAssignedVersion << __E__;
7663 } //end handleSetGroupAliasInBackboneXML()
7664 catch(std::runtime_error& e)
7665 {
7666  __SUP_SS__ << "Error saving new Group Alias view!\n\n " << e.what() << __E__;
7667  __SUP_COUT_ERR__ << ss.str();
7668  xmlOut.addTextElementToData("Error", ss.str());
7669 }
7670 catch(...)
7671 {
7672  __SUP_SS__ << "Error saving new Group Alias view!\n\n " << __E__;
7673  try
7674  {
7675  throw;
7676  } //one more try to printout extra info
7677  catch(const std::exception& e)
7678  {
7679  ss << "Exception message: " << e.what();
7680  }
7681  catch(...)
7682  {
7683  }
7684  __SUP_COUT_ERR__ << ss.str();
7685  xmlOut.addTextElementToData("Error", ss.str());
7686 } //end handleSetGroupAliasInBackboneXML() catch
7687 
7688 //==============================================================================
7696 void ConfigurationGUISupervisor::handleSetTableAliasInBackboneXML(
7697  HttpXmlDocument& xmlOut,
7698  ConfigurationManagerRW* cfgMgr,
7699  const std::string& tableAlias,
7700  const std::string& tableName,
7701  TableVersion version,
7702  const std::string& author)
7703 try
7704 {
7705  cfgMgr->loadConfigurationBackbone();
7706  std::map<std::string, TableVersion> activeVersions = cfgMgr->getActiveVersions();
7707 
7708  const std::string versionAliasesTableName =
7709  ConfigurationManager::VERSION_ALIASES_TABLE_NAME;
7710  if(activeVersions.find(versionAliasesTableName) == activeVersions.end())
7711  {
7712  __SUP_SS__ << "Active version of " << versionAliasesTableName << " missing!"
7713  << __E__;
7714  xmlOut.addTextElementToData("Error", ss.str());
7715  return;
7716  }
7717 
7718  // put all old backbone versions in xmlOut
7719  const std::set<std::string> backboneMembers = cfgMgr->getBackboneMemberNames();
7720  for(auto& memberName : backboneMembers)
7721  {
7722  __SUP_COUT__ << "activeVersions[\"" << memberName
7723  << "\"]=" << activeVersions[memberName] << __E__;
7724 
7725  xmlOut.addTextElementToData("oldBackboneName", memberName);
7726  xmlOut.addTextElementToData("oldBackboneVersion",
7727  activeVersions[memberName].toString());
7728  }
7729 
7730  // make a temporary version from active view
7731  // modify the chosen versionAlias row
7732  // save as new version
7733 
7734  TableBase* table = cfgMgr->getTableByName(versionAliasesTableName);
7735  TableVersion originalVersion = activeVersions[versionAliasesTableName];
7736  TableVersion temporaryVersion = table->createTemporaryView(originalVersion);
7737 
7738  __SUP_COUT__ << "\t\t temporaryVersion: " << temporaryVersion << __E__;
7739 
7740  bool isDifferent = false;
7741 
7742  try
7743  {
7744  TableView* configView = table->getTemporaryView(temporaryVersion);
7745 
7746  unsigned int col;
7747  unsigned int col2 = configView->findCol("VersionAlias");
7748  unsigned int col3 = configView->findCol("TableName");
7749 
7750  // only make a new version if we are changing compared to active backbone
7751 
7752  unsigned int row = -1;
7753  // find tableName, versionAlias pair
7754  // NOTE: only accept the first pair, repeats are ignored.
7755  try
7756  {
7757  unsigned int tmpRow = -1;
7758  do
7759  { // start looking from beyond last find
7760  tmpRow = configView->findRow(col3, tableName, tmpRow + 1);
7761  } while(configView->getDataView()[tmpRow][col2] != tableAlias);
7762  // at this point the first pair was found! (else exception was thrown)
7763  row = tmpRow;
7764  }
7765  catch(...)
7766  {
7767  }
7768  if(row == (unsigned int)-1) // if row not found then add a row
7769  {
7770  isDifferent = true;
7771  row = configView->addRow();
7772 
7773  // set all columns in new row
7774  col = configView->findCol(TableViewColumnInfo::COL_NAME_COMMENT);
7775  configView->setValue(
7776  std::string("Entry was added by server in ") +
7777  "ConfigurationGUISupervisor::setTableAliasInActiveBackbone().",
7778  row,
7779  col);
7780 
7781  col = configView->findCol("VersionAliasUID");
7782  configView->setValue(
7783  tableName.substr(0, tableName.rfind("Table")) + tableAlias, row, col);
7784 
7785  configView->setValue(tableAlias, row, col2);
7786  configView->setValue(tableName, row, col3);
7787  }
7788 
7789  __SUP_COUT__ << "\t\t row: " << row << __E__;
7790 
7791  col = configView->findCol("Version");
7792  __SUP_COUT__ << "\t\t version: " << version << " vs "
7793  << configView->getDataView()[row][col] << __E__;
7794  if(version.toString() != configView->getDataView()[row][col])
7795  {
7796  configView->setValue(version.toString(), row, col);
7797  isDifferent = true;
7798  }
7799 
7800  if(isDifferent) // set author/time of new version if different
7801  {
7802  configView->setValue(
7803  author, row, configView->findCol(TableViewColumnInfo::COL_NAME_AUTHOR));
7804  configView->setValue(
7805  time(0),
7806  row,
7807  configView->findCol(TableViewColumnInfo::COL_NAME_CREATION));
7808  }
7809  }
7810  catch(...)
7811  {
7812  __SUP_COUT_ERR__ << "Error editing Version Alias view!" << __E__;
7813 
7814  // delete temporaryVersion
7815  table->eraseView(temporaryVersion);
7816  throw;
7817  }
7818 
7819  TableVersion newAssignedVersion;
7820  if(isDifferent) // make new version if different
7821  {
7822  __SUP_COUT__ << "\t\t**************************** Save as new table version"
7823  << __E__;
7824 
7826  xmlOut,
7827  cfgMgr,
7828  table->getTableName(),
7829  originalVersion,
7830  false /*makeTemporary*/,
7831  table,
7832  temporaryVersion,
7833  false /*ignoreDuplicates*/,
7834  true /*lookForEquivalent*/);
7835  }
7836  else // use existing version
7837  {
7838  __SUP_COUT__ << "\t\t**************************** Using existing table version"
7839  << __E__;
7840 
7841  // delete temporaryVersion
7842  table->eraseView(temporaryVersion);
7843  newAssignedVersion = activeVersions[versionAliasesTableName];
7844 
7845  xmlOut.addTextElementToData("savedName", versionAliasesTableName);
7846  xmlOut.addTextElementToData("savedVersion", newAssignedVersion.toString());
7847  }
7848 
7849  __SUP_COUT__ << "\t\t newAssignedVersion: " << newAssignedVersion << __E__;
7850 } // end handleSetVersionAliasInBackboneXML()
7851 catch(std::runtime_error& e)
7852 {
7853  __SUP_SS__ << "Error saving new Version Alias view!\n\n " << e.what() << __E__;
7854  __SUP_COUT_ERR__ << ss.str();
7855  xmlOut.addTextElementToData("Error", ss.str());
7856 }
7857 catch(...)
7858 {
7859  __SUP_SS__ << "Error saving new Version Alias view!\n\n " << __E__;
7860  try
7861  {
7862  throw;
7863  } //one more try to printout extra info
7864  catch(const std::exception& e)
7865  {
7866  ss << "Exception message: " << e.what();
7867  }
7868  catch(...)
7869  {
7870  }
7871  __SUP_COUT_ERR__ << ss.str();
7872  xmlOut.addTextElementToData("Error", ss.str());
7873 } // end handleSetVersionAliasInBackboneXML() catch
7874 
7875 //==============================================================================
7881 void ConfigurationGUISupervisor::handleAliasGroupMembersInBackboneXML(
7882  HttpXmlDocument& xmlOut,
7883  ConfigurationManagerRW* cfgMgr,
7884  const std::string& versionAlias,
7885  const std::string& groupName,
7886  TableGroupKey groupKey,
7887  const std::string& author)
7888 try
7889 {
7890  cfgMgr->loadConfigurationBackbone();
7891  std::map<std::string, TableVersion> activeVersions = cfgMgr->getActiveVersions();
7892 
7893  const std::string versionAliasesTableName =
7894  ConfigurationManager::VERSION_ALIASES_TABLE_NAME;
7895  if(activeVersions.find(versionAliasesTableName) == activeVersions.end())
7896  {
7897  __SUP_SS__ << "Active version of " << versionAliasesTableName << " missing!"
7898  << __E__;
7899  xmlOut.addTextElementToData("Error", ss.str());
7900  return;
7901  }
7902 
7903  // put all old backbone versions in xmlOut
7904  const std::set<std::string> backboneMembers = cfgMgr->getBackboneMemberNames();
7905  for(auto& memberName : backboneMembers)
7906  {
7907  __SUP_COUT__ << "activeVersions[\"" << memberName
7908  << "\"]=" << activeVersions[memberName] << __E__;
7909 
7910  xmlOut.addTextElementToData("oldBackboneName", memberName);
7911  xmlOut.addTextElementToData("oldBackboneVersion",
7912  activeVersions[memberName].toString());
7913  }
7914 
7915  // make a temporary version from active view
7916  // modify the chosen versionAlias row
7917  // save as new version
7918 
7919  TableBase* table = cfgMgr->getTableByName(versionAliasesTableName);
7920  TableVersion temporaryVersion =
7921  table->createTemporaryView(activeVersions[versionAliasesTableName]);
7922 
7923  __SUP_COUT__ << "\t\t temporaryVersion: " << temporaryVersion << __E__;
7924 
7925  TableView* configView = table->getTemporaryView(temporaryVersion);
7926 
7927  // only make a new version if we are changing compared to active backbone
7928  bool isDifferent = false;
7929 
7930  // get member names and versions
7931  std::map<std::string /*name*/, TableVersion /*version*/> memberMap;
7932  try
7933  {
7934  cfgMgr->loadTableGroup(groupName,
7935  groupKey,
7936  false /*doActivate*/,
7937  &memberMap,
7938  0,
7939  0,
7940  0,
7941  0,
7942  0, // defaults
7943  true /*doNotLoadMember*/);
7944  }
7945  catch(...)
7946  {
7947  xmlOut.addTextElementToData(
7948  "Error",
7949  "Table group \"" + TableGroupKey::getFullGroupString(groupName, groupKey) +
7950  "\" can not be retrieved!");
7951  return;
7952  }
7953 
7954  unsigned int col;
7955  unsigned int col2 = configView->findCol("VersionAlias");
7956  unsigned int col3 = configView->findCol("TableName");
7957 
7958  for(auto& memberPair : memberMap)
7959  {
7960  bool thisMemberIsDifferent = false;
7961  unsigned int row = -1;
7962 
7963  __SUP_COUT__ << "Adding alias for " << memberPair.first << "_v"
7964  << memberPair.second << " to " << versionAlias << __E__;
7965 
7966  // find tableName, versionAlias pair
7967  // NOTE: only accept the first pair, repeats are ignored.
7968  try
7969  {
7970  unsigned int tmpRow = -1;
7971  do
7972  { // start looking from beyond last find
7973  tmpRow = configView->findRow(col3, memberPair.first, tmpRow + 1);
7974  } while(configView->getDataView()[tmpRow][col2] != versionAlias);
7975  // at this point the first pair was found! (else exception was thrown)
7976  row = tmpRow;
7977  }
7978  catch(...)
7979  {
7980  }
7981  if(row == (unsigned int)-1) // if row not found then add a row
7982  {
7983  thisMemberIsDifferent = true;
7984  row = configView->addRow();
7985 
7986  // set all columns in new row
7987  col = configView->findCol(TableViewColumnInfo::COL_NAME_COMMENT);
7988  configView->setValue(
7989  std::string("Entry was added by server in ") +
7990  "ConfigurationGUISupervisor::setTableAliasInActiveBackbone().",
7991  row,
7992  col);
7993 
7994  col = configView->getColUID();
7995  configView->setValue(
7996  memberPair.first.substr(0, memberPair.first.rfind("Table")) +
7997  versionAlias,
7998  row,
7999  col);
8000 
8001  configView->setValue(versionAlias, row, col2);
8002  configView->setValue(memberPair.first, row, col3);
8003  }
8004 
8005  col = configView->findCol("Version");
8006 
8007  if(memberPair.second.toString() != configView->getDataView()[row][col])
8008  {
8009  configView->setValue(memberPair.second.toString(), row, col);
8010  thisMemberIsDifferent = true;
8011  }
8012 
8013  if(thisMemberIsDifferent) // change author and time if row is different
8014  {
8015  configView->setValue(
8016  author, row, configView->findCol(TableViewColumnInfo::COL_NAME_AUTHOR));
8017  configView->setValue(
8018  time(0),
8019  row,
8020  configView->findCol(TableViewColumnInfo::COL_NAME_CREATION));
8021  }
8022 
8023  if(thisMemberIsDifferent)
8024  isDifferent = true;
8025  }
8026 
8027  // configView->print();
8028 
8029  TableVersion newAssignedVersion;
8030  if(isDifferent) // make new version if different
8031  {
8032  __SUP_COUT__ << "\t\t**************************** Save v" << temporaryVersion
8033  << " as new table version" << __E__;
8034 
8035  newAssignedVersion =
8036  cfgMgr->saveNewTable(versionAliasesTableName, temporaryVersion);
8037  }
8038  else // use existing version
8039  {
8040  __SUP_COUT__ << "\t\t**************************** Using existing table version"
8041  << __E__;
8042 
8043  // delete temporaryVersion
8044  table->eraseView(temporaryVersion);
8045  newAssignedVersion = activeVersions[versionAliasesTableName];
8046  }
8047 
8048  xmlOut.addTextElementToData("savedName", versionAliasesTableName);
8049  xmlOut.addTextElementToData("savedVersion", newAssignedVersion.toString());
8050  __SUP_COUT__ << "\t\t Resulting Version: " << newAssignedVersion << __E__;
8051 } // end handleAliasGroupMembersInBackboneXML()
8052 catch(std::runtime_error& e)
8053 {
8054  __SUP_SS__ << "Error saving new Version Alias view!\n\n " << e.what() << __E__;
8055  __SUP_COUT_ERR__ << ss.str();
8056  xmlOut.addTextElementToData("Error", ss.str());
8057 }
8058 catch(...)
8059 {
8060  __SUP_SS__ << "Error saving new Version Alias view!\n\n " << __E__;
8061  try
8062  {
8063  throw;
8064  } //one more try to printout extra info
8065  catch(const std::exception& e)
8066  {
8067  ss << "Exception message: " << e.what();
8068  }
8069  catch(...)
8070  {
8071  }
8072  __SUP_COUT_ERR__ << ss.str();
8073  xmlOut.addTextElementToData("Error", ss.str());
8074 } // end handleAliasGroupMembersInBackboneXML() catch
8075 
8076 //==============================================================================
8087 void ConfigurationGUISupervisor::handleGroupAliasesXML(HttpXmlDocument& xmlOut,
8088  ConfigurationManagerRW* cfgMgr)
8089 {
8090  cfgMgr->loadConfigurationBackbone();
8091  std::map<std::string, TableVersion> activeVersions = cfgMgr->getActiveVersions();
8092 
8093  std::string groupAliasesTableName = ConfigurationManager::GROUP_ALIASES_TABLE_NAME;
8094  if(activeVersions.find(groupAliasesTableName) == activeVersions.end())
8095  {
8096  __SUP_SS__ << "\nActive version of " << groupAliasesTableName << " missing! "
8097  << groupAliasesTableName
8098  << " is a required member of the Backbone table group."
8099  << "\n\nLikely you need to activate a valid Backbone table group."
8100  << __E__;
8101  __SUP_COUT__ << ss.str(); // just output findings, and return empty xml to avoid
8102  // infinite error loops in GUI
8103  // xmlOut.addTextElementToData("Error", ss.str());
8104  return;
8105  }
8106  __SUP_COUT__ << "activeVersions[\"" << groupAliasesTableName
8107  << "\"]=" << activeVersions[groupAliasesTableName] << __E__;
8108  xmlOut.addTextElementToData("GroupAliasesTableName", groupAliasesTableName);
8109  xmlOut.addTextElementToData("GroupAliasesTableVersion",
8110  activeVersions[groupAliasesTableName].toString());
8111 
8112  std::vector<std::pair<std::string, ConfigurationTree>> aliasNodePairs =
8113  cfgMgr->getNode(groupAliasesTableName).getChildren();
8114 
8115  const int numOfThreads = StringMacros::getConcurrencyCount() / 2;
8116  __SUP_COUT__ << " getConcurrencyCount " << StringMacros::getConcurrencyCount()
8117  << " ==> " << numOfThreads << " threads for alias group loads." << __E__;
8118 
8119  if(numOfThreads < 2) // no multi-threading
8120  {
8121  std::string groupName, groupKey, groupComment, groupAuthor, groupCreateTime,
8122  groupType;
8123  for(auto& aliasNodePair : aliasNodePairs)
8124  {
8125  groupName = aliasNodePair.second.getNode("GroupName").getValueAsString();
8126  groupKey = aliasNodePair.second.getNode("GroupKey").getValueAsString();
8127 
8128  xmlOut.addTextElementToData("GroupAlias", aliasNodePair.first);
8129  xmlOut.addTextElementToData("GroupName", groupName);
8130  xmlOut.addTextElementToData("GroupKey", groupKey);
8131  xmlOut.addTextElementToData(
8132  "AliasComment",
8133  aliasNodePair.second.getNode(TableViewColumnInfo::COL_NAME_COMMENT)
8134  .getValueAsString());
8135 
8136  // get group comment
8137  groupComment =
8138  ConfigurationManager::UNKNOWN_INFO; // clear just in case failure
8139  groupType = ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN;
8140  try
8141  {
8142  cfgMgr->loadTableGroup(groupName,
8143  TableGroupKey(groupKey),
8144  false /* doActivate */,
8145  0 /* groupMembers */,
8146  0 /* progressBar */,
8147  0 /* accumulatedWarnings */,
8148  &groupComment,
8149  &groupAuthor,
8150  &groupCreateTime,
8151  true /*doNotLoadMembers*/,
8152  &groupType);
8153  }
8154  catch(...)
8155  {
8156  __SUP_COUT_WARN__ << "Failed to load group '" << groupName << "("
8157  << groupKey << ")' to extract group comment and type."
8158  << __E__;
8159  }
8160  xmlOut.addTextElementToData("GroupComment", groupComment);
8161  xmlOut.addTextElementToData("GroupType", groupType);
8162  } // end alias pair loop
8163  }
8164  else //multi-threading
8165  {
8166  int threadsLaunched = 0;
8167  int foundThreadIndex = 0;
8168  std::vector<std::shared_ptr<std::atomic<bool>>> threadDone;
8169  for(int i = 0; i < numOfThreads; ++i)
8170  threadDone.push_back(std::make_shared<std::atomic<bool>>(true));
8171 
8172  std::vector<std::shared_ptr<ots::GroupInfo>> sharedGroupInfoPtrs;
8173  std::string groupName, groupKey;
8174 
8175  for(auto& aliasNodePair : aliasNodePairs)
8176  {
8177  //make temporary group info for thread
8178  sharedGroupInfoPtrs.push_back(std::make_shared<ots::GroupInfo>());
8179 
8180  groupName = aliasNodePair.second.getNode("GroupName").getValueAsString();
8181  groupKey = aliasNodePair.second.getNode("GroupKey").getValueAsString();
8182 
8183  if(threadsLaunched >= numOfThreads)
8184  {
8185  //find availableThreadIndex
8186  foundThreadIndex = -1;
8187  while(foundThreadIndex == -1)
8188  {
8189  for(int i = 0; i < numOfThreads; ++i)
8190  if(*(threadDone[i]))
8191  {
8192  foundThreadIndex = i;
8193  break;
8194  }
8195  if(foundThreadIndex == -1)
8196  {
8197  __SUP_COUTT__ << "Waiting for available thread..." << __E__;
8198  usleep(10000);
8199  }
8200  } //end thread search loop
8201  threadsLaunched = numOfThreads - 1;
8202  }
8203  __SUP_COUTT__ << "Starting load group thread... " << groupName << "("
8204  << groupKey << ")" << __E__;
8205  *(threadDone[foundThreadIndex]) = false;
8206 
8207  std::thread(
8208  [](ConfigurationManagerRW* theCfgMgr,
8209  std::string theGroupName,
8210  ots::TableGroupKey theGroupKey,
8211  std::shared_ptr<ots::GroupInfo> theGroupInfo,
8212  std::shared_ptr<std::atomic<bool>> theThreadDone) {
8214  theGroupName,
8215  theGroupKey,
8216  theGroupInfo,
8217  theThreadDone);
8218  },
8219  cfgMgr,
8220  groupName,
8221  TableGroupKey(groupKey),
8222  sharedGroupInfoPtrs.back(),
8223  threadDone[foundThreadIndex])
8224  .detach();
8225 
8226  ++threadsLaunched;
8227  ++foundThreadIndex;
8228 
8229  } //end alias group thread loop
8230 
8231  //check for all threads done
8232  do
8233  {
8234  foundThreadIndex = -1;
8235  for(int i = 0; i < numOfThreads; ++i)
8236  if(!*(threadDone[i]))
8237  {
8238  foundThreadIndex = i;
8239  break;
8240  }
8241  if(foundThreadIndex != -1)
8242  {
8243  __SUP_COUTT__ << "Waiting for thread to finish... " << foundThreadIndex
8244  << __E__;
8245  usleep(10000);
8246  }
8247  } while(foundThreadIndex != -1); //end thread done search loop
8248 
8249  //threads done now, so copy group info
8250  size_t i = 0;
8251  for(auto& aliasNodePair : aliasNodePairs)
8252  {
8253  groupName = aliasNodePair.second.getNode("GroupName").getValueAsString();
8254  groupKey = aliasNodePair.second.getNode("GroupKey").getValueAsString();
8255 
8256  if(groupKey != sharedGroupInfoPtrs[i]->getLatestKey().toString())
8257  {
8258  __SUP_SS__ << "Error loading group information for the group alias '"
8259  << aliasNodePair.first << "' mapping to group '" << groupName
8260  << "(" << groupKey << ")" << __E__;
8261  __SUP_SS_THROW__;
8262  }
8263 
8264  xmlOut.addTextElementToData("GroupAlias", aliasNodePair.first);
8265  xmlOut.addTextElementToData("GroupName", groupName);
8266  xmlOut.addTextElementToData(
8267  "GroupKey", sharedGroupInfoPtrs[i]->getLatestKey().toString());
8268  xmlOut.addTextElementToData(
8269  "AliasComment",
8270  aliasNodePair.second.getNode(TableViewColumnInfo::COL_NAME_COMMENT)
8271  .getValueAsString());
8272 
8273  xmlOut.addTextElementToData(
8274  "GroupComment", sharedGroupInfoPtrs[i]->getLatestKeyGroupComment());
8275  xmlOut.addTextElementToData(
8276  "GroupAuthor", sharedGroupInfoPtrs[i]->getLatestKeyGroupAuthor());
8277  xmlOut.addTextElementToData(
8278  "GroupCreationTime",
8279  sharedGroupInfoPtrs[i]->getLatestKeyGroupCreationTime());
8280  xmlOut.addTextElementToData(
8281  "GroupType", sharedGroupInfoPtrs[i]->getLatestKeyGroupTypeString());
8282  // xmlOut.addTextElementToData("GroupMemberMap", sharedGroupInfoPtrs[i]->latestKeyMemberMap_);
8283  ++i;
8284  } //end copy group info loop
8285 
8286  } //end multi-thread handling
8287 } // end handleGroupAliasesXML
8288 
8289 //==============================================================================
8300 void ConfigurationGUISupervisor::handleVersionAliasesXML(HttpXmlDocument& xmlOut,
8301  ConfigurationManagerRW* cfgMgr)
8302 {
8303  cfgMgr->loadConfigurationBackbone();
8304  std::map<std::string, TableVersion> activeVersions = cfgMgr->getActiveVersions();
8305 
8306  std::string versionAliasesTableName =
8307  ConfigurationManager::VERSION_ALIASES_TABLE_NAME;
8308  if(activeVersions.find(versionAliasesTableName) == activeVersions.end())
8309  {
8310  __SUP_SS__ << "Active version of VersionAliases missing!"
8311  << "Make sure you have a valid active Backbone Group." << __E__;
8312  xmlOut.addTextElementToData("Error", ss.str());
8313  return;
8314  }
8315  __SUP_COUT__ << "activeVersions[\"" << versionAliasesTableName
8316  << "\"]=" << activeVersions[versionAliasesTableName] << __E__;
8317  xmlOut.addTextElementToData("VersionAliasesVersion",
8318  activeVersions[versionAliasesTableName].toString());
8319 
8320  std::vector<std::pair<std::string, ConfigurationTree>> aliasNodePairs =
8321  cfgMgr->getNode(versionAliasesTableName).getChildren();
8322 
8323  for(auto& aliasNodePair : aliasNodePairs)
8324  {
8325  // note : these are column names in the versionAliasesTableName table
8326  // VersionAlias, TableName, Version, CommentDescription
8327  xmlOut.addTextElementToData(
8328  "VersionAlias",
8329  aliasNodePair.second.getNode("VersionAlias").getValueAsString());
8330  xmlOut.addTextElementToData(
8331  "TableName", aliasNodePair.second.getNode("TableName").getValueAsString());
8332  xmlOut.addTextElementToData(
8333  "Version", aliasNodePair.second.getNode("Version").getValueAsString());
8334  xmlOut.addTextElementToData(
8335  "Comment",
8336  aliasNodePair.second.getNode(TableViewColumnInfo::COL_NAME_COMMENT)
8337  .getValueAsString());
8338  }
8339 } // end handleVersionAliasesXML()
8340 
8341 //==============================================================================
8347 void ConfigurationGUISupervisor::handleGetTableGroupTypeXML(
8348  HttpXmlDocument& xmlOut, ConfigurationManagerRW* cfgMgr, const std::string& tableList)
8349 {
8350  std::map<std::string /*name*/, TableVersion /*version*/> memberMap;
8351  std::string name, versionStr;
8352  auto c = tableList.find(',', 0);
8353  auto i = c;
8354  i = 0; // auto used to get proper index/length type
8355  while(c < tableList.length())
8356  {
8357  // add the table name and version pair to the map
8358  name = tableList.substr(i, c - i);
8359  i = c + 1;
8360  c = tableList.find(',', i);
8361  if(c == std::string::npos) // missing version list entry?!
8362  {
8363  __SUP_SS__ << "Incomplete Table Name-Version pair!" << __E__;
8364  __SUP_COUT_ERR__ << "\n" << ss.str();
8365  xmlOut.addTextElementToData("Error", ss.str());
8366  return;
8367  }
8368 
8369  versionStr = tableList.substr(i, c - i);
8370  i = c + 1;
8371  c = tableList.find(',', i);
8372 
8373  memberMap[name] = TableVersion(versionStr);
8374  }
8375 
8376  std::string groupTypeString = "";
8377  // try to determine type, dont report errors, just mark ots::GroupType::UNKNOWN_TYPE
8378  try
8379  {
8380  // determine the type of the table group
8381  groupTypeString = cfgMgr->getTypeNameOfGroup(memberMap);
8382  xmlOut.addTextElementToData("TableGroupType", groupTypeString);
8383  }
8384  catch(std::runtime_error& e)
8385  {
8386  __SUP_SS__ << "Table group has invalid type! " << e.what() << __E__;
8387  __SUP_COUT__ << "\n" << ss.str();
8388  groupTypeString = ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN;
8389  xmlOut.addTextElementToData("TableGroupType", groupTypeString);
8390  }
8391  catch(...)
8392  {
8393  __SUP_SS__ << "Table group has invalid type! " << __E__;
8394  try
8395  {
8396  throw;
8397  } //one more try to printout extra info
8398  catch(const std::exception& e)
8399  {
8400  ss << "Exception message: " << e.what();
8401  }
8402  catch(...)
8403  {
8404  }
8405  __SUP_COUT__ << "\n" << ss.str();
8406  groupTypeString = ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN;
8407  xmlOut.addTextElementToData("TableGroupType", groupTypeString);
8408  }
8409 } //end handleGetTableGroupTypeXML()
8410 
8411 //==============================================================================
8427 void ConfigurationGUISupervisor::handleTableGroupsXML(HttpXmlDocument& xmlOut,
8428  ConfigurationManagerRW* cfgMgr,
8429  bool returnMembers)
8430 {
8431  __SUP_COUTT__ << "cfgMgr runtime=" << cfgMgr->runTimeSeconds() << __E__;
8432  // use xmlOut.dataSs_ since there is no need for escape the string and it can be a huge data block to escape and recursively print
8433  // xercesc::DOMElement* parentEl;
8434 
8435  // get all group info from cache (if no cache, get from interface)
8436 
8437  if(!cfgMgr->getAllGroupInfo().size() ||
8438  cfgMgr->getAllGroupInfo().begin()->second.getLatestKeyGroupTypeString() == "" ||
8439  cfgMgr->getAllGroupInfo().begin()->second.getLatestKeyGroupTypeString() ==
8440  ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN ||
8441  ( //if active Context group type is not defined, then refresh
8442  cfgMgr->getActiveGroupKey(ConfigurationManager::GroupType::CONTEXT_TYPE) !=
8443  TableGroupKey::INVALID &&
8444  cfgMgr->getAllGroupInfo().find(cfgMgr->getActiveGroupName(
8445  ConfigurationManager::GroupType::CONTEXT_TYPE)) !=
8446  cfgMgr->getAllGroupInfo().end() &&
8447  (cfgMgr->getAllGroupInfo()
8448  .at(cfgMgr->getActiveGroupName(
8449  ConfigurationManager::GroupType::CONTEXT_TYPE))
8450  .getLatestKeyGroupTypeString() == "" ||
8451  cfgMgr->getAllGroupInfo()
8452  .at(cfgMgr->getActiveGroupName(
8453  ConfigurationManager::GroupType::CONTEXT_TYPE))
8454  .getLatestKeyGroupTypeString() ==
8455  ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN)) ||
8456  ( //if active Config group type is not defined, then refresh
8457  cfgMgr->getActiveGroupKey(
8458  ConfigurationManager::GroupType::CONFIGURATION_TYPE) !=
8459  TableGroupKey::INVALID &&
8460  cfgMgr->getAllGroupInfo().find(cfgMgr->getActiveGroupName(
8461  ConfigurationManager::GroupType::CONFIGURATION_TYPE)) !=
8462  cfgMgr->getAllGroupInfo().end() &&
8463  (cfgMgr->getAllGroupInfo()
8464  .at(cfgMgr->getActiveGroupName(
8465  ConfigurationManager::GroupType::CONFIGURATION_TYPE))
8466  .getLatestKeyGroupTypeString() == "" ||
8467  cfgMgr->getAllGroupInfo()
8468  .at(cfgMgr->getActiveGroupName(
8469  ConfigurationManager::GroupType::CONFIGURATION_TYPE))
8470  .getLatestKeyGroupTypeString() ==
8471  ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN)))
8472  {
8473  __SUP_COUT__
8474  << "Group Info cache appears empty or stale. Attempting to regenerate..."
8475  << __E__;
8476  cfgMgr->getAllTableInfo(true /*refresh*/,
8477  0 /* accumulatedWarnings */,
8478  "" /* errorFilterName */,
8479  true /* getGroupKeys */,
8480  true /* getGroupInfo */,
8481  true /* initializeActiveGroups */);
8482  }
8483 
8484  const std::map<std::string, GroupInfo>& allGroupInfo = cfgMgr->getAllGroupInfo();
8485 
8486  __SUP_COUTT__ << "cfgMgr runtime=" << cfgMgr->runTimeSeconds() << __E__;
8487 
8488  std::string groupName;
8489  std::string groupString, groupTypeString, groupComment, groupCreationTime,
8490  groupAuthor;
8491  for(auto& groupInfo : allGroupInfo)
8492  {
8493  groupName = groupInfo.first;
8494 
8495  //get group info and force update of groupKeys from DB Interface cache if possible
8496  cfgMgr->getGroupInfo(groupName, true /* attemptToReloadKeys */);
8497 
8498  if(groupInfo.second.getKeys().size() == 0)
8499  {
8500  __SUP_COUT__ << "Group name '" << groupName
8501  << "' found, but no keys so ignoring." << __E__;
8502  continue;
8503  }
8504 
8505  xmlOut.dataSs_ << "<TableGroupName value='" << groupName << "'/>" << __E__;
8506  xmlOut.dataSs_ << "<TableGroupKey value='" << groupInfo.second.getLatestKey()
8507  << "'/>" << __E__;
8508 
8509  // trusting the cache!
8510  xmlOut.dataSs_ << "<TableGroupType value='"
8511  << groupInfo.second.getLatestKeyGroupTypeString() << "'/>"
8512  << __E__;
8513  xmlOut.dataSs_ << "<TableGroupComment value='"
8515  groupInfo.second.getLatestKeyGroupComment(),
8516  true /* allowWhiteSpace */)
8517  << "'/>" << __E__;
8518  xmlOut.dataSs_ << "<TableGroupAuthor value='"
8519  << groupInfo.second.getLatestKeyGroupAuthor() << "'/>" << __E__;
8520  xmlOut.dataSs_ << "<TableGroupCreationTime value='"
8521  << groupInfo.second.getLatestKeyGroupCreationTime() << "'/>"
8522  << __E__;
8523 
8524  if(returnMembers)
8525  {
8526  // parentEl = xmlOut.addTextElementToData("TableGroupMembers", "");
8527  xmlOut.dataSs_ << "<TableGroupMembers value=''>" << __E__;
8528 
8529  for(auto& memberPair : groupInfo.second.getLatestKeyMemberMap())
8530  {
8531  xmlOut.dataSs_ << "\t<MemberName value='" << memberPair.first << "'/>"
8532  << __E__;
8533  xmlOut.dataSs_ << "\t<MemberVersion value='" << memberPair.second << "'/>"
8534  << __E__;
8535 
8536  // xmlOut.addTextElementToParent("MemberName", memberPair.first, parentEl);
8537  // xmlOut.addTextElementToParent(
8538  // "MemberVersion", memberPair.second.toString(), parentEl);
8539  }
8540  xmlOut.dataSs_ << "</TableGroupMembers>" << __E__;
8541  } // end if returnMembers
8542 
8543  // add other group keys to xml for this group name
8544  // but just empty members (not displayed anyway)
8545  for(auto& keyInSet : groupInfo.second.getKeys())
8546  {
8547  if(keyInSet == groupInfo.second.getLatestKey())
8548  continue; // skip the lastest
8549 
8550  xmlOut.dataSs_ << "<TableGroupName value='" << groupName << "'/>" << __E__;
8551  xmlOut.dataSs_ << "<TableGroupKey value='" << keyInSet << "'/>" << __E__;
8552  // xmlOut.addTextElementToData("TableGroupName", groupName);
8553  // xmlOut.addTextElementToData("TableGroupKey", keyInSet.toString());
8554 
8555  // TODO -- make loadingHistoricalInfo an input parameter
8556  bool loadingHistoricalInfo = false;
8557  if(loadingHistoricalInfo)
8558  {
8559  groupComment = ""; // clear just in case failure
8560  try
8561  {
8562  cfgMgr->loadTableGroup(groupName,
8563  keyInSet,
8564  0,
8565  0,
8566  0,
8567  0,
8568  &groupComment,
8569  0,
8570  0, // mostly defaults
8571  true /*doNotLoadMembers*/,
8572  &groupTypeString);
8573  }
8574  catch(...)
8575  {
8576  groupTypeString = ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN;
8577  __SUP_COUT_WARN__ << "Failed to load group '" << groupName << "("
8578  << keyInSet
8579  << ")' to extract group comment and type." << __E__;
8580  }
8581 
8582  xmlOut.dataSs_ << "<TableGroupType value='" << groupTypeString << "'/>"
8583  << __E__;
8584  xmlOut.dataSs_ << "<TableGroupComment value='"
8585  << StringMacros::escapeString(groupComment,
8586  true /* allowWhiteSpace */)
8587  << "'/>" << __E__;
8588  xmlOut.dataSs_ << "<TableGroupAuthor value='" << groupAuthor << "'/>"
8589  << __E__;
8590  xmlOut.dataSs_ << "<TableGroupCreationTime value='" << groupCreationTime
8591  << "'/>" << __E__;
8592  // xmlOut.addTextElementToData("TableGroupType", groupTypeString);
8593  // xmlOut.addTextElementToData("TableGroupComment", groupComment);
8594  // xmlOut.addTextElementToData("TableGroupAuthor", groupAuthor);
8595  // xmlOut.addTextElementToData("TableGroupCreationTime", groupCreationTime);
8596  }
8597  else
8598  {
8599  // just use guess that historical groups are of same type
8600  xmlOut.dataSs_ << "<TableGroupType value='"
8601  << groupInfo.second.getLatestKeyGroupTypeString() << "'/>"
8602  << __E__;
8603  //leave place holder comment,author,time for javascript parsing
8604  xmlOut.dataSs_ << "<TableGroupComment value='"
8605  << ""
8606  << "'/>" << __E__;
8607  xmlOut.dataSs_ << "<TableGroupAuthor value='"
8608  << ""
8609  << "'/>" << __E__;
8610  xmlOut.dataSs_ << "<TableGroupCreationTime value='"
8611  << ""
8612  << "'/>" << __E__;
8613  }
8614 
8615  if(returnMembers)
8616  {
8617  //need to add empty group members, event for historical groups, for easier Javascript extraction
8618  xmlOut.dataSs_ << "<TableGroupMembers/>" << __E__;
8619  // xmlOut.addTextElementToData("TableGroupMembers", "");
8620  }
8621 
8622  } // end other key loop
8623  __SUP_COUTT__ << groupName << " runtime=" << cfgMgr->runTimeSeconds() << __E__;
8624  } // end primary group loop
8625  __SUP_COUTT__ << "cfgMgr runtime=" << cfgMgr->runTimeSeconds() << __E__;
8626 } // end handleTableGroupsXML()
8627 
8628 //==============================================================================
8647 void ConfigurationGUISupervisor::handleTablesXML(HttpXmlDocument& xmlOut,
8648  ConfigurationManagerRW* cfgMgr,
8649  const std::string& filterStartTimeStr,
8650  const std::string& filterEndTimeStr,
8651  const std::string& filterMode)
8652 {
8653  time_t filterStartTime = 0;
8654  time_t filterEndTime = 0;
8655  if(filterStartTimeStr != "")
8656  {
8657  try
8658  {
8659  filterStartTime = static_cast<time_t>(std::stoll(filterStartTimeStr));
8660  }
8661  catch(const std::exception& e)
8662  {
8663  __SUP_SS__ << "Error parsing startTime parameter: " << e.what() << __E__;
8664  __SUP_COUT_ERR__ << "\n" << ss.str();
8665  xmlOut.addTextElementToData("Error", ss.str());
8666  return;
8667  }
8668  }
8669  if(filterEndTimeStr != "")
8670  {
8671  try
8672  {
8673  filterEndTime = static_cast<time_t>(std::stoll(filterEndTimeStr));
8674  }
8675  catch(const std::exception& e)
8676  {
8677  __SUP_SS__ << "Error parsing endTime parameter: " << e.what() << __E__;
8678  __SUP_COUT_ERR__ << "\n" << ss.str();
8679  xmlOut.addTextElementToData("Error", ss.str());
8680  return;
8681  }
8682  }
8683  if(filterStartTime != 0 && filterEndTime != 0 && filterStartTime > filterEndTime)
8684  {
8685  __SUP_SS__ << "Invalid time range: startTime (" << filterStartTime
8686  << ") must be <= endTime (" << filterEndTime << ")." << __E__;
8687  __SUP_SS_THROW__;
8688  }
8689  if(filterMode != "created" && filterMode != "loaded")
8690  {
8691  __SUP_SS__ << "Invalid filterMode parameter '" << filterMode
8692  << ".' Expected 'created' or 'loaded.'" << __E__;
8693  __SUP_COUT_ERR__ << "\n" << ss.str();
8694  xmlOut.addTextElementToData("Error", ss.str());
8695  return;
8696  }
8697  // diagnostics: track where the time is going
8698  const auto diagStartTime = std::chrono::steady_clock::now();
8699  auto diagElapsedSec = [](const std::chrono::steady_clock::time_point& start) {
8700  return std::chrono::duration<double>(std::chrono::steady_clock::now() - start)
8701  .count();
8702  };
8703 
8704  if(cfgMgr->getAllGroupInfo().size() == 0 || cfgMgr->getActiveVersions().size() == 0)
8705  {
8706  __SUP_COUT__ << "Table Info cache appears empty. Attempting to regenerate."
8707  << __E__;
8708  cfgMgr->getAllTableInfo(true /*refresh*/,
8709  0 /* accumulatedWarnings */,
8710  "" /* errorFilterName */,
8711  false /* getGroupKeys */,
8712  false /* getGroupInfo */,
8713  true /* initializeActiveGroups */);
8714  __SUP_COUT__ << "getAllTableInfo() regenerate took "
8715  << diagElapsedSec(diagStartTime) << " s" << __E__;
8716  }
8717 
8718  xercesc::DOMElement* parentEl;
8719  const std::map<std::string, TableInfo>& allTableInfo = cfgMgr->getAllTableInfo();
8720 
8721  // construct specially ordered table name set
8722  std::set<std::string, StringMacros::IgnoreCaseCompareStruct> orderedTableSet;
8723  for(const auto& tablePair : allTableInfo)
8724  orderedTableSet.emplace(tablePair.first);
8725 
8726  // std::map<std::string, TableInfo>::const_iterator it = allTableInfo.begin();
8727 
8728  __SUP_COUT__ << "# of tables to consider: " << allTableInfo.size() << __E__;
8729 
8730  const auto diagAliasStartTime = std::chrono::steady_clock::now();
8731  std::map<std::string, std::map<std::string, TableVersion>> versionAliases =
8732  cfgMgr->getVersionAliases();
8733 
8734  __SUP_COUT__ << "# of tables w/aliases: " << versionAliases.size() << " (took "
8735  << diagElapsedSec(diagAliasStartTime) << " s)" << __E__;
8736 
8737  if(filterStartTime != 0 && filterEndTime != 0 && filterMode == "created")
8738  {
8739  // parallel pre-warm of the process-wide creation time cache, so the
8740  // filter loop below gets immediate cache hits (only versions not yet in
8741  // the disk-persisted cache are loaded, so this is cheap after first use)
8742  const auto diagPreloadStartTime = std::chrono::steady_clock::now();
8743  cfgMgr->preloadVersionCreationTimes();
8744  __SUP_COUT__ << "Version creation time cache pre-warm took "
8745  << diagElapsedSec(diagPreloadStartTime) << " s" << __E__;
8746  }
8747 
8748  // diagnostics accumulated over the table loop
8749  size_t diagNumTablesFound = 0; //tables with at least one version listed
8750  size_t diagNumVersionsConsidered = 0;
8751  size_t diagNumVersionsMatched = 0;
8752  size_t diagNumTimeLookups = 0;
8753  double diagTimeLookupSec = 0; //cumulative time in creation/load time lookups
8754  double diagSlowestTableSec = 0;
8755  std::string diagSlowestTableName = "";
8756  size_t diagTableCount = 0;
8757 
8758  for(const auto& orderedTableName : orderedTableSet) // while(it !=
8759  // allTableInfo.end())
8760  {
8761  std::map<std::string, TableInfo>::const_iterator it =
8762  allTableInfo.find(orderedTableName);
8763  if(it == allTableInfo.end())
8764  {
8765  __SS__ << "Impossible missing table in map '" << orderedTableName << "'"
8766  << __E__;
8767  __SS_THROW__;
8768  }
8769 
8770  // for each table name
8771  // get existing version keys
8772 
8773  // add system table name
8774  xmlOut.addTextElementToData("TableName", it->first);
8775  parentEl = xmlOut.addTextElementToData("TableVersions", "");
8776 
8777  // include aliases for this table (if the versions exist)
8778  if(versionAliases.find(it->first) != versionAliases.end())
8779  for(auto& aliasVersion : versionAliases[it->first])
8780  if(it->second.versions_.find(aliasVersion.second) !=
8781  it->second.versions_
8782  .end()) //Note : scratch version is always an alias ==> ConfigurationManager::SCRATCH_VERSION_ALIAS)
8783  xmlOut.addTextElementToParent(
8784  "Version",
8785  ConfigurationManager::ALIAS_VERSION_PREAMBLE + aliasVersion.first,
8786  parentEl);
8787 
8788  // get all table versions for the current table
8789  // except skip scratch version
8790  // for speed, group versions into spans:
8791  //======
8793  auto vSpanToXML = [&diagNumVersionsConsidered,
8794  &diagNumVersionsMatched,
8795  &diagNumTimeLookups,
8796  &diagTimeLookupSec](auto const& sortedKeys,
8797  auto& xmlOut,
8798  auto& configEl,
8799  const std::string& tableName,
8800  ConfigurationManagerRW* cfgMgr,
8801  const time_t filterStartTime,
8802  const time_t filterEndTime,
8803  const std::string& filterMode) {
8804  //add lo and hi spans, instead of each individual value
8805  size_t lo = -1, hi = -1;
8806  bool allVersionsFiltered = true;
8807  for(auto& keyInOrder : sortedKeys)
8808  {
8809  //skip scratch version
8810  if(keyInOrder.isScratchVersion())
8811  continue;
8812 
8813  ++diagNumVersionsConsidered;
8814 
8815  if(filterStartTime != 0 && filterEndTime != 0)
8816  {
8817  const auto diagLookupStartTime = std::chrono::steady_clock::now();
8818  try
8819  {
8820  // Note: neither helper stamps the version's lastAccessTime
8821  // ("Last Load"), so filtering does not corrupt Last Load times.
8822  // - "created" times are immutable and cached, so each version is
8823  // loaded from the database at most once per process lifetime.
8824  // - "loaded" times are in-memory only; 0 (never loaded by this
8825  // process, or evicted from cache) falls outside any range.
8826  time_t tableVersionTime =
8827  filterMode == "loaded"
8828  ? cfgMgr->getVersionLastAccessTime(tableName, keyInOrder)
8829  : cfgMgr->getVersionCreationTime(tableName, keyInOrder);
8830 
8831  ++diagNumTimeLookups;
8832  diagTimeLookupSec +=
8833  std::chrono::duration<double>(
8834  std::chrono::steady_clock::now() - diagLookupStartTime)
8835  .count();
8836 
8837  if(tableVersionTime < filterStartTime ||
8838  tableVersionTime > filterEndTime)
8839  {
8840  //Note: trace-level to avoid log flooding (one line per
8841  // filtered version can be thousands of lines)
8842  __COUTT__ << "Table '" << tableName << "' version v"
8843  << keyInOrder << " " << filterMode
8844  << " time is outside the filter range, so "
8845  "skipping."
8846  << __E__;
8847  continue;
8848  }
8849  }
8850  catch(const std::runtime_error&)
8851  {
8852  ++diagNumTimeLookups;
8853  diagTimeLookupSec +=
8854  std::chrono::duration<double>(
8855  std::chrono::steady_clock::now() - diagLookupStartTime)
8856  .count();
8857  __COUT__ << "Failed to get " << filterMode << " time for table '"
8858  << tableName << "' version v" << keyInOrder
8859  << ", so skipping." << __E__;
8860  continue;
8861  }
8862  }
8863 
8864  allVersionsFiltered = false;
8865  ++diagNumVersionsMatched;
8866 
8867  if(lo == size_t(-1)) //establish start of potential span
8868  {
8869  hi = lo = keyInOrder.version();
8870  continue;
8871  }
8872  else if(hi + 1 == keyInOrder.version()) //span is growing
8873  {
8874  hi = keyInOrder.version();
8875  continue;
8876  }
8877  //else jump by more than one, so close out span
8878 
8879  if(lo == hi) //single value
8880  xmlOut.addNumberElementToParent("Version", lo, configEl);
8881  else //span
8882  xmlOut.addTextElementToParent(
8883  "Version",
8884  "_" + std::to_string(lo) + "_" + std::to_string(hi),
8885  configEl);
8886  hi = lo = keyInOrder.version();
8887  }
8888 
8889  if(lo != size_t(-1)) //check if last one to do!
8890  {
8891  if(lo == hi) //single value
8892  xmlOut.addNumberElementToParent("Version", lo, configEl);
8893  else //span
8894  xmlOut.addTextElementToParent(
8895  "Version",
8896  "_" + std::to_string(lo) + "_" + std::to_string(hi),
8897  configEl);
8898  }
8899  return allVersionsFiltered;
8900  }; //end local lambda vSpanToXML()
8901 
8902  const auto diagTableStartTime = std::chrono::steady_clock::now();
8903  if(vSpanToXML(it->second.versions_,
8904  xmlOut,
8905  parentEl,
8906  it->first,
8907  cfgMgr,
8908  filterStartTime,
8909  filterEndTime,
8910  filterMode) &&
8911  filterStartTime != 0 && filterEndTime != 0)
8912  {
8913  // Only remove tables when a time filter is active; without a filter,
8914  // tables with no persistent versions (e.g. definition-only tables)
8915  // should still be listed, as in the unfiltered Table View.
8916  // Remove the pair we just added: TableVersions then TableName.
8917  unsigned int childCount = xmlOut.getChildrenCount();
8918  if(childCount >= 2)
8919  {
8920  xmlOut.removeDataElement(childCount - 1);
8921  xmlOut.removeDataElement(childCount - 2);
8922  }
8923  }
8924  else
8925  ++diagNumTablesFound;
8926 
8927  // diagnostics: report slow tables and periodic progress
8928  double diagTableSec = diagElapsedSec(diagTableStartTime);
8929  if(diagTableSec > diagSlowestTableSec)
8930  {
8931  diagSlowestTableSec = diagTableSec;
8932  diagSlowestTableName = it->first;
8933  }
8934  if(diagTableSec > 1.0)
8935  __SUP_COUT__ << "Slow table filter: '" << it->first << "' with "
8936  << it->second.versions_.size() << " versions took "
8937  << diagTableSec << " s" << __E__;
8938  ++diagTableCount;
8939  if(diagTableCount % 50 == 0)
8940  __SUP_COUT__ << "getTables filter progress: " << diagTableCount << " of "
8941  << orderedTableSet.size() << " tables in "
8942  << diagElapsedSec(diagStartTime) << " s (" << diagNumTimeLookups
8943  << " version time lookups taking " << diagTimeLookupSec
8944  << " s so far)" << __E__;
8945 
8946  } // end table loop
8947 
8948  // always return the table and version counts, even if everything was filtered out
8949  xmlOut.addTextElementToData("NumberOfTablesConsidered",
8950  std::to_string(allTableInfo.size()));
8951  xmlOut.addTextElementToData("NumberOfTablesFound",
8952  std::to_string(diagNumTablesFound));
8953  xmlOut.addTextElementToData("NumberOfVersionsConsidered",
8954  std::to_string(diagNumVersionsConsidered));
8955  xmlOut.addTextElementToData("NumberOfVersionsFound",
8956  std::to_string(diagNumVersionsMatched));
8957 
8958  __SUP_COUT__ << "getTables filter summary: mode=" << filterMode << " range=["
8959  << filterStartTime << "," << filterEndTime << "]"
8960  << " tables considered=" << allTableInfo.size()
8961  << " found=" << diagNumTablesFound
8962  << "; versions considered=" << diagNumVersionsConsidered
8963  << " matched=" << diagNumVersionsMatched << "; " << diagNumTimeLookups
8964  << " version time lookups took " << diagTimeLookupSec
8965  << " s; slowest table '" << diagSlowestTableName << "' took "
8966  << diagSlowestTableSec << " s; total " << diagElapsedSec(diagStartTime)
8967  << " s" << __E__;
8968 
8969 } // end handleTablesXML()
8970 
8971 //==============================================================================
8980 void ConfigurationGUISupervisor::handleGetArtdaqNodeRecordsXML(
8981  HttpXmlDocument& xmlOut,
8982  ConfigurationManagerRW* cfgMgr,
8983  const std::string& modifiedTables,
8984  const std::string& tableGroup,
8985  TableGroupKey tableGroupKey,
8986  const std::string& contextGroup,
8987  TableGroupKey contextGroupKey,
8988  bool suppressMultiNode)
8989 {
8990  __COUT__ << "Retrieving artdaq nodes..." << __E__;
8991 
8992  // setup active tables based on specified or active groups and modified tables
8993  setupActiveTablesXML(xmlOut,
8994  cfgMgr,
8995  tableGroup,
8996  tableGroupKey,
8997  modifiedTables,
8998  false /* refreshAll */);
8999 
9000  if(contextGroup != "" && !contextGroupKey.isInvalid())
9001  {
9002  cfgMgr->loadTableGroup(contextGroup, contextGroupKey, false /*doActivate*/);
9003  }
9004 
9005  {
9006  TableBase* ctxTableBase =
9007  cfgMgr->getTableByName(ConfigurationManager::XDAQ_CONTEXT_TABLE_NAME);
9008  XDAQContextTable* ctxTable = dynamic_cast<XDAQContextTable*>(ctxTableBase);
9009  if(!ctxTable)
9010  {
9011  __SUP_SS__ << "Failed to cast "
9012  << ConfigurationManager::XDAQ_CONTEXT_TABLE_NAME
9013  << " to XDAQContextTable." << __E__;
9014  __SS_THROW__;
9015  }
9016  ctxTable->extractContexts(cfgMgr);
9017  }
9018 
9019  std::map<std::string /*type*/,
9020  std::map<std::string /*record*/, std::vector<std::string /*property*/>>>
9021  nodeTypeToObjectMap;
9022  std::map<std::string /*subsystemName*/, std::string /*destinationSubsystemName*/>
9023  subsystemObjectMap;
9024 
9025  std::vector<std::string /*property*/> artdaqSupervisorInfo;
9026 
9027  std::string artdaqSupervisorName;
9028  const ARTDAQTableBase::ARTDAQInfo& info =
9030  nodeTypeToObjectMap,
9031  subsystemObjectMap,
9032  artdaqSupervisorInfo,
9033  suppressMultiNode);
9034 
9035  bool hasArtdaqSupervisor =
9036  (artdaqSupervisorInfo.size() == 4 /*expecting 4 artdaq Supervisor parameters*/);
9037 
9038  // parentEl anchors all child XML elements; create it from the artdaq supervisor
9039  // record when one exists, otherwise use a placeholder so that online-monitor
9040  // nodes (below) can still be emitted.
9041  xercesc::DOMElement* parentEl = nullptr;
9042  unsigned int paramIndex = 0;
9043  std::string typeString;
9044 
9045  if(hasArtdaqSupervisor)
9046  {
9047  paramIndex = 0;
9048  parentEl = xmlOut.addTextElementToData("artdaqSupervisor",
9049  artdaqSupervisorInfo[paramIndex++]);
9050  typeString = "artdaqSupervisor";
9051  xmlOut.addTextElementToParent(
9052  typeString + "-status", artdaqSupervisorInfo[paramIndex++], parentEl);
9053  xmlOut.addTextElementToParent(
9054  typeString + "-contextAddress", artdaqSupervisorInfo[paramIndex++], parentEl);
9055  xmlOut.addTextElementToParent(
9056  typeString + "-contextPort", artdaqSupervisorInfo[paramIndex++], parentEl);
9057 
9058  for(auto& subsystem : info.subsystems)
9059  {
9060  typeString = "subsystem";
9061 
9062  __SUP_COUT__ << "\t\t"
9063  << "Found " << typeString << " " << subsystem.first << " \t := '"
9064  << subsystem.second.label << "'" << __E__;
9065 
9066  xmlOut.addTextElementToParent(typeString, subsystem.second.label, parentEl);
9067  xmlOut.addTextElementToParent(
9068  typeString + "-id", std::to_string(subsystem.first), parentEl);
9069 
9070  xmlOut.addTextElementToParent(typeString + "-sourcesCount",
9071  std::to_string(subsystem.second.sources.size()),
9072  parentEl);
9073 
9074  xmlOut.addTextElementToParent(typeString + "-destination",
9075  std::to_string(subsystem.second.destination),
9076  parentEl);
9077  } // end subsystem handling
9078  }
9079  else
9080  {
9081  __SUP_COUT__ << "No artdaq supervisor found — scanning for online monitors."
9082  << __E__;
9083  parentEl = xmlOut.addTextElementToData("artdaqSupervisor", "");
9084  }
9085 
9086  __SUP_COUT__ << "========== "
9087  << "Found " << nodeTypeToObjectMap.size() << " process types." << __E__;
9088 
9089  for(auto& nameTypePair : nodeTypeToObjectMap)
9090  {
9091  typeString = nameTypePair.first;
9092 
9093  __SUP_COUT__ << "\t"
9094  << "Found " << nameTypePair.second.size() << " " << typeString
9095  << "(s)" << __E__;
9096 
9097  for(auto& artdaqNode : nameTypePair.second)
9098  {
9099  __SUP_COUT__ << "\t\t"
9100  << "Found '" << artdaqNode.first << "' " << typeString << __E__;
9101  __SUP_COUTV__(StringMacros::vectorToString(artdaqNode.second));
9102 
9103  if(artdaqNode.second.size() < 2)
9104  {
9105  __SUP_SS__ << "Impossible parameter size for node '" << artdaqNode.first
9106  << "' " << typeString << " - please notify admins!" << __E__;
9107  __SUP_SS_THROW__;
9108  }
9109 
9110  auto nodeEl =
9111  xmlOut.addTextElementToParent(typeString, artdaqNode.first, parentEl);
9112 
9113  paramIndex = 3; // start at 3 after subsystem parameter
9114  if(artdaqNode.second.size() > paramIndex)
9115  {
9116  __SUP_COUTT__ << "\t\t\t"
9117  << "-multinode: " << artdaqNode.second[paramIndex] << __E__;
9118  xmlOut.addTextElementToParent(
9119  typeString + "-multinode", artdaqNode.second[paramIndex++], nodeEl);
9120  }
9121  if(artdaqNode.second.size() > paramIndex)
9122  {
9123  __SUP_COUTT__ << "\t\t\t"
9124  << "-nodefixedwidth: " << artdaqNode.second[paramIndex]
9125  << __E__;
9126  xmlOut.addTextElementToParent(typeString + "-nodefixedwidth",
9127  artdaqNode.second[paramIndex++],
9128  nodeEl);
9129  }
9130  if(artdaqNode.second.size() > paramIndex)
9131  {
9132  __SUP_COUTT__ << "\t\t\t"
9133  << "-hostarray: " << artdaqNode.second[paramIndex] << __E__;
9134  xmlOut.addTextElementToParent(
9135  typeString + "-hostarray", artdaqNode.second[paramIndex++], nodeEl);
9136  }
9137  if(artdaqNode.second.size() > paramIndex)
9138  {
9139  __SUP_COUTT__ << "\t\t\t"
9140  << "-hostfixedwidth: " << artdaqNode.second[paramIndex]
9141  << __E__;
9142  xmlOut.addTextElementToParent(typeString + "-hostfixedwidth",
9143  artdaqNode.second[paramIndex++],
9144  nodeEl);
9145  }
9146 
9147  paramIndex = 0; // return to starting parameter
9148  __SUP_COUTT__ << "\t\t\t"
9149  << "-status: " << artdaqNode.second[paramIndex] << __E__;
9150  xmlOut.addTextElementToParent(
9151  typeString + "-status", artdaqNode.second[paramIndex++], parentEl);
9152  __SUP_COUTT__ << "\t\t\t"
9153  << "-hostname: " << artdaqNode.second[paramIndex] << __E__;
9154  xmlOut.addTextElementToParent(
9155  typeString + "-hostname", artdaqNode.second[paramIndex++], parentEl);
9156  __SUP_COUTT__ << "\t\t\t"
9157  << "-subsystem: " << artdaqNode.second[paramIndex] << __E__;
9158  xmlOut.addTextElementToParent(
9159  typeString + "-subsystem", artdaqNode.second[paramIndex], parentEl);
9160  }
9161  } // end processor type handling
9162 
9163  // Scan contexts for ARTDAQOnlineMonitorSupervisor applications.
9164  // These are standalone XDAQ apps (not children of the artdaq supervisor)
9165  // that run art-based online monitor jobs and generate their own FHiCL.
9166  {
9167  XDAQContextTable* ctxTable = dynamic_cast<XDAQContextTable*>(
9168  cfgMgr->getTableByName(ConfigurationManager::XDAQ_CONTEXT_TABLE_NAME));
9169 
9170  if(ctxTable)
9171  {
9172  typeString = "monitor";
9173  unsigned int monitorCount = 0;
9174 
9175  for(auto& ctx : ctxTable->getContexts())
9176  {
9177  if(!ctx.status_)
9178  continue;
9179 
9180  for(auto& app : ctx.applications_)
9181  {
9182  if(!app.status_)
9183  continue;
9184  if(app.class_.find("ARTDAQOnlineMonitorSupervisor") ==
9185  std::string::npos)
9186  continue;
9187 
9188  // Follow the supervisor config link to get the record UID
9189  // used in FHiCL filename generation (monitor-<uid>.fcl).
9190  std::string monitorLabel = app.applicationUID_;
9191  try
9192  {
9193  ConfigurationTree supNode =
9194  XDAQContextTable::getSupervisorConfigNode(
9195  cfgMgr, ctx.contextUID_, app.applicationUID_);
9196  monitorLabel = supNode.getValue();
9197  }
9198  catch(...)
9199  {
9200  __SUP_COUT_WARN__
9201  << "Could not follow supervisor config link for "
9202  << app.applicationUID_ << " in context " << ctx.contextUID_
9203  << "; using application UID as label." << __E__;
9204  }
9205 
9206  // Strip scheme from context address for hostname
9207  std::string hostname = ctx.address_;
9208  {
9209  auto pos = hostname.find("://");
9210  if(pos != std::string::npos)
9211  hostname = hostname.substr(pos + 3);
9212  }
9213 
9214  __SUP_COUT__ << "\t\tFound online monitor '" << monitorLabel
9215  << "' on " << hostname << __E__;
9216 
9217  xmlOut.addTextElementToParent(typeString, monitorLabel, parentEl);
9218  xmlOut.addTextElementToParent(typeString + "-status", "1", parentEl);
9219  xmlOut.addTextElementToParent(
9220  typeString + "-hostname",
9221  StringMacros::encodeURIComponent(hostname),
9222  parentEl);
9223  xmlOut.addTextElementToParent(
9224  typeString + "-subsystem", "", parentEl);
9225 
9226  ++monitorCount;
9227  }
9228  }
9229 
9230  __SUP_COUT__ << "Found " << monitorCount << " online monitor supervisor(s)."
9231  << __E__;
9232  }
9233  } // end online monitor handling
9234 
9235  __SUP_COUT__ << "Done retrieving artdaq nodes." << __E__;
9236 
9237 } // end handleGetArtdaqNodeRecordsXML()
9238 
9239 //==============================================================================
9246 void ConfigurationGUISupervisor::handleSaveArtdaqNodeRecordsXML(
9247  const std::string& nodeString,
9248  const std::string& subsystemString,
9249  HttpXmlDocument& xmlOut,
9250  ConfigurationManagerRW* cfgMgr,
9251  const std::string& modifiedTables)
9252 {
9253  __SUP_COUT__ << "Saving artdaq nodes..." << __E__;
9254 
9255  // setup active tables based on active groups and modified tables
9256  setupActiveTablesXML(
9257  xmlOut, cfgMgr, "", TableGroupKey(-1), modifiedTables, false /* refreshAll */);
9258 
9259  // start node object extraction from nodeString
9260  std::map<std::string /*type*/,
9261  std::map<std::string /*record*/, std::vector<std::string /*property*/>>>
9262  nodeTypeToObjectMap;
9263  {
9264  // nodeString format:
9265  // <type>:<nodeName>=<originalName>,<hostname>,<subsystemName>;<nodeName>=<originalName>,<hostname>,<subsystemName>;
9266  // ... |<type>:...|
9267  // repeat | separated types
9268  std::map<std::string /*type*/, std::string /*typeRecordSetString*/>
9269  nodeTypeToStringMap;
9270  StringMacros::getMapFromString(nodeString, nodeTypeToStringMap, {'|'}, {':'});
9271 
9272  __SUP_COUTV__(StringMacros::mapToString(nodeTypeToStringMap));
9273 
9274  for(auto& typePair : nodeTypeToStringMap)
9275  {
9276  if(typePair.first == "")
9277  continue; // skip empty names
9278 
9279  __SUP_COUTV__(StringMacros::decodeURIComponent(typePair.first));
9280 
9281  nodeTypeToObjectMap.emplace(
9282  std::make_pair(StringMacros::decodeURIComponent(typePair.first),
9283  std::map<std::string /*record*/,
9284  std::vector<std::string /*property*/>>()));
9285 
9286  std::map<std::string /*node*/, std::string /*nodeRecordSetString*/>
9287  nodeRecordToStringMap;
9288 
9290  typePair.second, nodeRecordToStringMap, {';'}, {'='});
9291 
9292  __SUP_COUTV__(StringMacros::mapToString(nodeRecordToStringMap));
9293 
9294  for(auto& nodePair : nodeRecordToStringMap)
9295  {
9296  if(nodePair.first == "")
9297  continue; // skip empty names
9298 
9299  __SUP_COUTV__(StringMacros::decodeURIComponent(nodePair.first));
9300 
9301  std::vector<std::string /*property*/> nodePropertyVector;
9302 
9304  nodePair.second, nodePropertyVector, {','});
9305 
9306  __SUP_COUTV__(StringMacros::vectorToString(nodePropertyVector));
9307 
9308  // decode all properties
9309  for(unsigned int i = 0; i < nodePropertyVector.size(); ++i)
9310  {
9311  __SUP_COUTV__(
9312  StringMacros::decodeURIComponent(nodePropertyVector[i]));
9313 
9314  nodePropertyVector[i] =
9315  StringMacros::decodeURIComponent(nodePropertyVector[i]);
9316  }
9317 
9318  nodeTypeToObjectMap[typePair.first].emplace(
9319  std::make_pair(StringMacros::decodeURIComponent(nodePair.first),
9320  nodePropertyVector));
9321  }
9322  }
9323  } // end node object extraction from nodeString
9324 
9325  // start subsystem object extraction from subsystemString
9326  std::map<std::string /*subsystemName*/, std::string /*destinationSubsystemName*/>
9327  subsystemObjectMap;
9328  {
9329  // subsystemString format:
9330  // <name>:<destination>;<name>:<destination>; ...;
9331  // repeat ; separated subsystems
9332 
9333  std::map<std::string /*subsystemName*/, std::string /*destinationSubsystemName*/>
9334  tmpSubsystemObjectMap;
9336  subsystemString, tmpSubsystemObjectMap, {';'}, {':'});
9337 
9338  __SUP_COUTV__(StringMacros::mapToString(tmpSubsystemObjectMap));
9339 
9340  // decode all values (probably unnecessary, but more future proof)
9341  for(auto& subsystemPair : tmpSubsystemObjectMap)
9342  {
9343  __SUP_COUTV__(StringMacros::decodeURIComponent(subsystemPair.first));
9344  __SUP_COUTV__(StringMacros::decodeURIComponent(subsystemPair.second));
9345 
9346  subsystemObjectMap.emplace(
9347  std::make_pair(StringMacros::decodeURIComponent(subsystemPair.first),
9348  StringMacros::decodeURIComponent(subsystemPair.second)));
9349  }
9350  } // end subsystem object extraction from subsystemString
9351 
9353  cfgMgr, nodeTypeToObjectMap, subsystemObjectMap);
9354 
9355  __SUP_COUT__ << "Done saving artdaq nodes." << __E__;
9356 } // end handleSaveArtdaqNodeRecordsXML()
9357 
9358 //==============================================================================
9365 void ConfigurationGUISupervisor::handleLoadArtdaqNodeLayoutXML(
9366  HttpXmlDocument& xmlOut,
9368  cfgMgr, //force read-only config manager to avoid requiring user-lock (i.e., not ConfigurationManagerRW)
9369  const std::string& contextGroupName /* = "" */,
9370  const TableGroupKey& contextGroupKey /* = INVALID */) const
9371 {
9372  bool usingActiveGroups = (contextGroupName == "" || contextGroupKey.isInvalid());
9373 
9374  //NOTE: must be same/similar code as otsdaq/otsdaq/TablePlugins/ARTDAQTableBase/ARTDAQTableBase.cc:2332
9375  const std::string& finalContextGroupName =
9376  usingActiveGroups
9377  ? cfgMgr->getActiveGroupName(ConfigurationManager::GroupType::CONTEXT_TYPE)
9378  : contextGroupName;
9379  const TableGroupKey& finalContextGroupKey =
9380  usingActiveGroups
9381  ? cfgMgr->getActiveGroupKey(ConfigurationManager::GroupType::CONTEXT_TYPE)
9382  : contextGroupKey;
9383  const std::string& finalConfigGroupName =
9384  cfgMgr->getActiveGroupName(ConfigurationManager::GroupType::CONFIGURATION_TYPE);
9385  const TableGroupKey& finalConfigGroupKey =
9386  cfgMgr->getActiveGroupKey(ConfigurationManager::GroupType::CONFIGURATION_TYPE);
9387 
9388  FILE* fp = nullptr;
9389  //first try context+config name only
9390  {
9391  std::stringstream layoutPath;
9392  layoutPath << ARTDAQTableBase::ARTDAQ_CONFIG_LAYOUTS_PATH << finalContextGroupName
9393  << "_" << finalContextGroupKey << "." << finalConfigGroupName << "_"
9394  << finalConfigGroupKey << ".dat";
9395 
9396  fp = fopen(layoutPath.str().c_str(), "r");
9397  if(!fp)
9398  {
9399  __SUP_COUT__ << "Layout file not found for '" << finalContextGroupName << "("
9400  << finalContextGroupKey << ") + " << finalConfigGroupName << "("
9401  << finalConfigGroupKey << ")': " << layoutPath.str() << __E__;
9402  // return; //try context only!
9403  }
9404  else
9405  __SUP_COUTV__(layoutPath.str());
9406  }
9407  //last try context name only
9408  if(!fp)
9409  {
9410  std::stringstream layoutPath;
9411  layoutPath << ARTDAQTableBase::ARTDAQ_CONFIG_LAYOUTS_PATH << finalContextGroupName
9412  << "_" << finalContextGroupKey << ".dat";
9413  __SUP_COUTV__(layoutPath.str());
9414 
9415  fp = fopen(layoutPath.str().c_str(), "r");
9416  if(!fp)
9417  {
9418  __SUP_COUT__ << "Layout file not found for '" << finalContextGroupName << "("
9419  << finalContextGroupKey << ")': " << layoutPath.str() << __E__;
9420  return;
9421  }
9422  else
9423  __SUP_COUTV__(layoutPath.str());
9424  }
9425 
9426  // file format is line by line
9427  // line 0 -- grid: <rows> <cols>
9428  // line 1-N -- node: <type> <name> <x-grid> <y-grid>
9429 
9430  const size_t maxLineSz = 1000;
9431  char line[maxLineSz];
9432  if(!fgets(line, maxLineSz, fp))
9433  {
9434  fclose(fp);
9435  return;
9436  }
9437  else
9438  {
9439  // extract grid
9440 
9441  unsigned int rows, cols;
9442 
9443  sscanf(line, "%u %u", &rows, &cols);
9444 
9445  __COUT__ << "Grid rows,cols = " << rows << "," << cols << __E__;
9446 
9447  xmlOut.addTextElementToData("grid-rows", std::to_string(rows));
9448  xmlOut.addTextElementToData("grid-cols", std::to_string(cols));
9449  }
9450 
9451  char name[maxLineSz];
9452  char type[maxLineSz];
9453  unsigned int x, y;
9454  while(fgets(line, maxLineSz, fp))
9455  {
9456  // extract node
9457  sscanf(line, "%s %s %u %u", type, name, &x, &y);
9458 
9459  xmlOut.addTextElementToData("node-type", type);
9460  xmlOut.addTextElementToData("node-name", name);
9461  xmlOut.addTextElementToData("node-x", std::to_string(x));
9462  xmlOut.addTextElementToData("node-y", std::to_string(y));
9463  } // end node extraction loop
9464 
9465  fclose(fp);
9466 
9467 } // end handleLoadArtdaqNodeLayoutXML()
9468 
9469 //==============================================================================
9476 void ConfigurationGUISupervisor::handleSaveArtdaqNodeLayoutXML(
9477  HttpXmlDocument& /*xmlOut*/,
9478  ConfigurationManagerRW* cfgMgr,
9479  const std::string& layoutString,
9480  const std::string& contextGroupName,
9481  const TableGroupKey& contextGroupKey)
9482 {
9483  bool usingActiveGroups = (contextGroupName == "" || contextGroupKey.isInvalid());
9484 
9485  const std::string& finalContextGroupName =
9486  usingActiveGroups
9487  ? cfgMgr->getActiveGroupName(ConfigurationManager::GroupType::CONTEXT_TYPE)
9488  : contextGroupName;
9489  const TableGroupKey& finalContextGroupKey =
9490  usingActiveGroups
9491  ? cfgMgr->getActiveGroupKey(ConfigurationManager::GroupType::CONTEXT_TYPE)
9492  : contextGroupKey;
9493  const std::string& finalConfigGroupName =
9494  cfgMgr->getActiveGroupName(ConfigurationManager::GroupType::CONFIGURATION_TYPE);
9495  const TableGroupKey& finalConfigGroupKey =
9496  cfgMgr->getActiveGroupKey(ConfigurationManager::GroupType::CONFIGURATION_TYPE);
9497 
9498  __SUP_COUTV__(layoutString);
9499 
9500  std::stringstream layoutPath;
9501  layoutPath << ARTDAQTableBase::ARTDAQ_CONFIG_LAYOUTS_PATH << finalContextGroupName
9502  << "_" << finalContextGroupKey << "." << finalConfigGroupName << "_"
9503  << finalConfigGroupKey << ".dat";
9504  __SUP_COUTV__(layoutPath.str());
9505 
9506  std::vector<std::string> fields = StringMacros::getVectorFromString(layoutString);
9507  __SUP_COUTV__(StringMacros::vectorToString(fields));
9508 
9509  if(fields.size() < 2 || (fields.size() - 2) % 4 != 0)
9510  {
9511  __SUP_SS__ << "Invalid layout string fields size of " << fields.size() << __E__;
9512  __SUP_SS_THROW__;
9513  }
9514 
9515  FILE* fp = fopen(layoutPath.str().c_str(), "w");
9516  if(!fp)
9517  {
9518  __SUP_SS__ << "Could not open layout file for writing for '"
9519  << finalContextGroupName << "(" << finalContextGroupKey << ") + "
9520  << finalConfigGroupName << "(" << finalConfigGroupKey
9521  << ")': " << layoutPath.str() << __E__;
9522  __SUP_SS_THROW__;
9523  }
9524 
9525  // match load code at ::handleLoadArtdaqNodeLayoutXML()
9526 
9527  // write grid
9528  fprintf(fp, "%s %s\n", fields[0].c_str(), fields[1].c_str());
9529 
9530  // write nodes
9531  for(unsigned int i = 2; i < fields.size(); i += 4)
9532  fprintf(fp,
9533  "%s %s %s %s\n",
9534  fields[i + 0].c_str(),
9535  fields[i + 1].c_str(),
9536  fields[i + 2].c_str(),
9537  fields[i + 3].c_str());
9538 
9539  fclose(fp);
9540 
9541 } // end handleSaveArtdaqNodeLayoutXML()
9542 
9543 //==============================================================================
9545 void ConfigurationGUISupervisor::handleOtherSubsystemActiveGroups(
9546  HttpXmlDocument& xmlOut,
9547  ConfigurationManagerRW* cfgMgr,
9548  bool getFullList,
9549  std::string targetSubsystem /* = "" */)
9550 try
9551 {
9552  try
9553  {
9554  ConfigurationTree node =
9555  cfgMgr->getNode(ConfigurationManager::CONTEXT_SUBSYSTEM_OPTIONAL_TABLE);
9556  auto children = node.getChildren();
9557 
9558  for(auto subsystem : children)
9559  {
9560  __SUP_COUTV__(subsystem.first);
9561  __SUP_COUTV__(
9562  StringMacros::vectorToString(subsystem.second.getChildrenNames()));
9563 
9564  std::string userPath =
9565  subsystem.second.getNode("SubsystemUserDataPath").getValue();
9566  __SUP_COUTV__(userPath);
9567  }
9568  }
9569  catch(const std::runtime_error& e)
9570  {
9571  __SUP_COUT__ << "Ignoring errors in handling other subsystem active groups "
9572  "(assuming the subsystem information map is not setup in "
9573  << ConfigurationManager::CONTEXT_SUBSYSTEM_OPTIONAL_TABLE
9574  << ") -- here is the error: \n"
9575  << e.what() << __E__;
9576  return; //ignore errors if subsystems not defined
9577  }
9578 
9579  //else subsystems are defined, so do not ignore errors!
9580 
9581  ConfigurationTree node =
9582  cfgMgr->getNode(ConfigurationManager::CONTEXT_SUBSYSTEM_OPTIONAL_TABLE);
9583  auto children = node.getChildren();
9584  for(auto subsystem : children)
9585  {
9586  if(targetSubsystem != "" && targetSubsystem != subsystem.first)
9587  continue; //skip non-target subsystem
9588 
9589  xercesc::DOMElement* parent =
9590  xmlOut.addTextElementToData("SubsystemName", subsystem.first);
9591 
9592  if(!getFullList)
9593  continue;
9594 
9595  std::string filename, userDataPath;
9596  std::string username, hostname;
9597 
9598  std::map<std::string /*groupType*/,
9599  std::pair<std::string /*groupName*/, TableGroupKey>>
9600  retMap = cfgMgr->getOtherSubsystemActiveTableGroups(
9601  subsystem.first, &userDataPath, &hostname, &username);
9602 
9603  for(const auto& retPair : retMap)
9604  {
9605  xmlOut.addTextElementToParent("CurrentlyActive" + retPair.first + "GroupName",
9606  retPair.second.first,
9607  parent);
9608  xmlOut.addTextElementToParent("CurrentlyActive" + retPair.first + "GroupKey",
9609  retPair.second.second.toString(),
9610  parent);
9611  }
9612 
9613  std::vector<std::string> filenameTypes = {"Configured",
9614  "Started",
9615  "ActivatedConfig",
9616  "ActivatedContext",
9617  "ActivatedBackbone",
9618  "ActivatedIterator"};
9619 
9620  std::vector<std::string> filenames = {
9621  ConfigurationManager::LAST_CONFIGURED_CONFIG_GROUP_FILE,
9622  ConfigurationManager::LAST_STARTED_CONFIG_GROUP_FILE,
9623  ConfigurationManager::LAST_ACTIVATED_CONFIG_GROUP_FILE,
9624  ConfigurationManager::LAST_ACTIVATED_CONTEXT_GROUP_FILE,
9625  ConfigurationManager::LAST_ACTIVATED_BACKBONE_GROUP_FILE,
9626  ConfigurationManager::LAST_ACTIVATED_ITERATE_GROUP_FILE};
9627 
9628  std::string userPath =
9629  subsystem.second.getNode("SubsystemUserDataPath").getValue();
9630  auto splitPath = StringMacros::getVectorFromString(userPath, {':'});
9631  std::string cmdResult;
9632  for(unsigned int i = 0; i < filenames.size(); ++i)
9633  {
9634  filename = userDataPath + "/ServiceData/RunControlData/" + filenames[i];
9635  __SUP_COUTV__(filename);
9636 
9637  std::string tmpSubsystemFilename =
9638  ConfigurationManager::LAST_TABLE_GROUP_SAVE_PATH + "/" + filenames[i] +
9639  "." + subsystem.first;
9640  __SUP_COUTV__(tmpSubsystemFilename);
9641 
9642  if(splitPath.size() == 2) //must scp
9643  {
9644  if(username.size()) //has username
9645  cmdResult = StringMacros::exec(
9646  ("rm " + tmpSubsystemFilename + " 2>/dev/null; scp " + username +
9647  "@" + hostname + ":" + filename + " " + tmpSubsystemFilename +
9648  " 2>&1; cat " + tmpSubsystemFilename + " 2>&1")
9649  .c_str());
9650  else
9651  cmdResult = StringMacros::exec(
9652  ("rm " + tmpSubsystemFilename + " 2>/dev/null; scp " + hostname +
9653  ":" + filename + " " + tmpSubsystemFilename + " 2>&1; cat " +
9654  tmpSubsystemFilename + " 2>&1")
9655  .c_str());
9656  }
9657  else if(splitPath.size() == 1) //then can just directly access the file
9658  {
9659  cmdResult = StringMacros::exec(("rm " + tmpSubsystemFilename +
9660  " 2>/dev/null; cp " + filename + " " +
9661  tmpSubsystemFilename + " 2>&1; cat " +
9662  tmpSubsystemFilename + " 2>&1")
9663  .c_str());
9664  }
9665 
9666  __SUP_COUTV__(cmdResult);
9667  std::string timeString;
9668  std::pair<std::string /*group name*/, TableGroupKey> theGroup =
9670  filenames[i] + "." + subsystem.first, timeString);
9671 
9672  // fill return parameters
9673  xmlOut.addTextElementToParent(
9674  "Last" + filenameTypes[i] + "GroupName", theGroup.first, parent);
9675  xmlOut.addTextElementToParent("Last" + filenameTypes[i] + "GroupKey",
9676  theGroup.second.toString(),
9677  parent);
9678  xmlOut.addTextElementToParent(
9679  "Last" + filenameTypes[i] + "GroupTime", timeString, parent);
9680  } // end active/recent filename handling
9681 
9682  } //end subsystem loop
9683 } // end getSubsytemTableGroups()
9684 catch(const std::runtime_error& e)
9685 {
9686  __SUP_SS__
9687  << "An error occurred handling subsystem active groups (Please check the "
9688  "subsystem user data path information map setup in the Context group table "
9689  << ConfigurationManager::CONTEXT_SUBSYSTEM_OPTIONAL_TABLE
9690  << ") -- here is the error: \n"
9691  << e.what() << __E__;
9692  __SUP_SS_THROW__;
9693 } // end getSubsytemTableGroups() catch
9694 
9695 //==============================================================================
9697 void ConfigurationGUISupervisor::handleSearchFieldInGroupXML(
9698  HttpXmlDocument& xmlOut,
9699  ConfigurationManagerRW* cfgMgr,
9700  const std::string& searchText,
9701  const bool activeGroupsOnly,
9702  const std::string& groupType,
9703  const std::string& optionGroups,
9704  const std::string& versionsToCheck)
9705 {
9706  // Validate searchText: empty string matches every cell and would be extremely expensive
9707  if(searchText.empty() || searchText.find_first_not_of(" \t\r\n") == std::string::npos)
9708  {
9709  __COUT_WARN__ << "searchText is empty or whitespace; aborting search." << __E__;
9710  xmlOut.addTextElementToData("Error", "Search text must not be empty.");
9711  return;
9712  }
9713 
9714  bool searchAllGroups = false;
9715  if(optionGroups.size() == 0)
9716  {
9717  __COUT__ << "No optionGroups specified, searching all groups of type "
9718  << groupType << __E__;
9719  searchAllGroups = true;
9720  }
9721  std::vector<std::string> optionGroupsList =
9722  StringMacros::getVectorFromString(optionGroups, {','});
9723 
9724  const std::map<std::string, GroupInfo>& allGroupInfo = cfgMgr->getAllGroupInfo();
9725 
9726  int searchMinThreshold = 0;
9727  int searchMaxThreshold = 0;
9728  size_t dashPos = -1;
9729 
9730  // Parse versionsToCheck for version range
9731  if(versionsToCheck.empty())
9732  {
9733  searchMaxThreshold = INT_MAX;
9734  }
9735  else if(versionsToCheck.find('-') != std::string::npos)
9736  {
9737  __COUT__ << "Parsing versionsToCheck for range: " << versionsToCheck << __E__;
9738  dashPos = versionsToCheck.find('-');
9739 
9740  if(dashPos != 0)
9741  {
9742  // Format: "N-M" means version N to version M
9743  std::string minStr = versionsToCheck.substr(0, dashPos);
9744  std::string maxStr = versionsToCheck.substr(dashPos + 1);
9745 
9746  searchMinThreshold = atoi(minStr.c_str());
9747  searchMaxThreshold = atoi(maxStr.c_str());
9748  }
9749  }
9750  else if(versionsToCheck != "")
9751  {
9752  // Single version specified
9753  searchMinThreshold = atoi(versionsToCheck.c_str());
9754  searchMaxThreshold = INT_MAX; // effectively no upper limit
9755  }
9756  else
9757  {
9758  __COUT__ << "Unable to set min and max thresholds for versionsToCheck: "
9759  << versionsToCheck << __E__;
9760  xmlOut.addTextElementToData(
9761  "Error",
9762  "Unable to set min and max thresholds for versionsToCheck: " +
9763  versionsToCheck);
9764  }
9765  __SUP_COUT__ << "versionsToCheck range: " << searchMinThreshold << " to "
9766  << searchMaxThreshold << __E__;
9767 
9768  auto parentEl = xmlOut.addTextElementToData("SearchResults", "");
9769 
9770  for(auto& groupInfo : allGroupInfo)
9771  {
9772  __COUT__ << "Checking group: " << groupInfo.first
9773  << " group type : " << groupInfo.second.getLatestKeyGroupTypeString()
9774  << __E__;
9775 
9776  if(groupInfo.second.getLatestKeyGroupTypeString() != groupType)
9777  continue;
9778 
9779  // Check if groupInfo.first is in optionGroups
9780  if(!searchAllGroups &&
9781  std::find(optionGroupsList.begin(), optionGroupsList.end(), groupInfo.first) ==
9782  optionGroupsList.end())
9783  {
9784  __COUT__ << "\tSkipping group not in optionGroups list." << __E__;
9785  continue; // skip this group
9786  }
9787 
9788  std::set<TableGroupKey /*key*/> allGroupKeys = groupInfo.second.getKeys();
9789  // convert set to vector for easier string handling
9790  std::vector<TableGroupKey> groupKeysVec(allGroupKeys.begin(), allGroupKeys.end());
9791  // convert vector to string for output
9792  std::string groupKeysStr = StringMacros::vectorToString(groupKeysVec, {','});
9793  __COUT__ << "Group keys:" << groupKeysStr << __E__;
9794 
9795  // Dash is first position: "-N" means (maxVersion - N) to maxVersion
9796  if(dashPos == 0)
9797  {
9798  std::string offsetStr = versionsToCheck.substr(1);
9799  int offset =
9800  atoi(offsetStr.c_str()) - 1; // -1 to include the max version itself
9801 
9802  __SUP_COUT__ << "versionsToCheck offset from end: " << offset << __E__;
9803 
9804  // Find the max version from this group
9805  if(groupKeysVec.size() > 0)
9806  {
9807  TableGroupKey maxVersion = groupKeysVec.at(
9808  groupKeysVec.size() - 1); // get the last (max) version
9809  int maxVersionNum = std::stoi(maxVersion.toString());
9810  int minVersionNum = std::max(0, maxVersionNum - offset);
9811 
9812  searchMinThreshold = minVersionNum;
9813  searchMaxThreshold = maxVersionNum;
9814 
9815  __SUP_COUT__ << "Version range: " << searchMinThreshold << " to "
9816  << searchMaxThreshold << __E__;
9817  }
9818  }
9819 
9820  std::set<TableGroupKey /*key*/> groupKeys;
9821  // make set of single active key for easier handling below
9822  if(activeGroupsOnly)
9823  {
9824  std::set<TableGroupKey> singleKeySet;
9825  singleKeySet.insert(groupInfo.second.getLatestKey());
9826  groupKeys = std::move(singleKeySet);
9827  }
9828  else
9829  groupKeys = std::move(allGroupKeys);
9830 
9831  for(const auto& groupKey : groupKeys)
9832  {
9833  __COUT__ << "\tProcessing group: " << groupInfo.first << " v" << groupKey
9834  << __E__;
9835 
9836  if(!activeGroupsOnly &&
9837  (std::stoi(groupKey.toString()) < searchMinThreshold ||
9838  std::stoi(groupKey.toString()) > searchMaxThreshold))
9839  {
9840  __COUT__ << "\tSkipping group version outside of versionsToCheck range."
9841  << __E__;
9842  continue; // skip this group version
9843  }
9844 
9845  // Get the member map for this group
9846  std::map<std::string /*name*/, TableVersion /*version*/> memberMap;
9847  try
9848  {
9849  cfgMgr->loadTableGroup(groupInfo.first,
9850  groupKey,
9851  false /*doActivate*/,
9852  &memberMap,
9853  0 /*progressBar*/,
9854  0 /*accumulateErrors*/,
9855  0 /*groupComment*/,
9856  0 /*groupAuthor*/,
9857  0 /*groupCreationTime*/,
9858  true /*doNotLoadMembers*/);
9859 
9860  // Process each table in the group
9861  for(auto& tablePair : memberMap)
9862  {
9863  __COUT__ << "\t\tTable: " << tablePair.first << " v"
9864  << tablePair.second << __E__;
9865 
9866  // Load the table for this specific version and search that exact view
9867  try
9868  {
9869  TableBase* memberTable = cfgMgr->getVersionedTableByName(
9870  tablePair.first, tablePair.second);
9871 
9872  // Get the table view for the requested version
9873  const TableView& tableView = memberTable->getView();
9874 
9875  // Loop through all rows and columns to search for searchText
9876  for(unsigned int row = 0; row < tableView.getNumberOfRows();
9877  ++row)
9878  {
9879  for(unsigned int col = 0;
9880  col < tableView.getNumberOfColumns();
9881  ++col)
9882  {
9883  const std::string& cellValue =
9884  tableView.getDataView()[row][col];
9885 
9886  // Check if searchText is found in cell value
9887  if(cellValue.find(searchText) != std::string::npos)
9888  {
9889  __COUT__ << "\t\t\tMatch found in " << tablePair.first
9890  << " v" << tablePair.second << " row " << row
9891  << " column " << col
9892  << " value: " << cellValue << __E__;
9893  // Separate match element by group/table for better organization
9894  auto groupTableEl = xmlOut.addTextElementToParent(
9895  "GroupTableMatch",
9896  groupInfo.first + "/" + tablePair.first,
9897  parentEl);
9898  xmlOut.addTextElementToParent(
9899  "MatchRow", std::to_string(row), groupTableEl);
9900  xmlOut.addTextElementToParent(
9901  "MatchColumn",
9902  tableView.getColumnInfo(col).getName(),
9903  groupTableEl);
9904  xmlOut.addTextElementToParent("MatchGroupVersion",
9905  groupKey.toString(),
9906  groupTableEl);
9907  xmlOut.addTextElementToParent(
9908  "MatchTableVersion",
9909  tablePair.second.toString(),
9910  groupTableEl);
9911  }
9912  } // end columns
9913  } // end rows
9914  } // end try load versioned table
9915  catch(const std::runtime_error& e)
9916  {
9917  __COUT_WARN__ << "Failed to search table " << tablePair.first
9918  << " v" << tablePair.second << ": " << e.what()
9919  << __E__;
9920  }
9921  } // end try load table group members
9922  }
9923  catch(const std::runtime_error& e)
9924  {
9925  __COUT_WARN__ << "Failed to load group members for " << groupInfo.first
9926  << ": " << e.what() << __E__;
9927  }
9928  }
9929  } // end groupInfo loop
9930 } // end handleSearchFieldInGroupXML()
9931 
9932 //==============================================================================
9934 void ConfigurationGUISupervisor::handleSearchFieldInTableXML(
9935  HttpXmlDocument& xmlOut,
9936  ConfigurationManagerRW* cfgMgr,
9937  const std::string& searchText,
9938  const std::string& tableName,
9939  const std::string& searchVersionToCheck,
9940  bool activeTablesOnly)
9941 {
9942  // Validate searchText: empty string matches every cell and would be extremely expensive
9943  if(searchText.empty() || searchText.find_first_not_of(" \t\r\n") == std::string::npos)
9944  {
9945  __COUT_WARN__ << "searchText is empty or whitespace; aborting search." << __E__;
9946  xmlOut.addTextElementToData("Error", "Search text must not be empty.");
9947  return;
9948  }
9949 
9950  const std::map<std::string, TableInfo>& allTableInfo = cfgMgr->getAllTableInfo();
9951  std::map<std::string, TableVersion> allActivePairs = cfgMgr->getActiveVersions();
9952 
9953  int searchMinThreshold = 0;
9954  int searchMaxThreshold = 0;
9955 
9956  // Parse searchVersionToCheck for version range
9957  if(searchVersionToCheck.empty())
9958  {
9959  searchMaxThreshold = INT_MAX;
9960  }
9961  else if(searchVersionToCheck.find('-') != std::string::npos)
9962  {
9963  size_t dashPos = searchVersionToCheck.find('-');
9964 
9965  if(dashPos == 0)
9966  {
9967  // Dash is first position: "-N" means (maxVersion - N) to maxVersion
9968  std::string offsetStr = searchVersionToCheck.substr(1);
9969  int offset =
9970  atoi(offsetStr.c_str()) - 1; // -1 to include the max version itself
9971 
9972  __SUP_COUT__ << "searchVersionToCheck offset from end: " << offset << __E__;
9973 
9974  // Find the max version from allTableInfo
9975  if(allTableInfo.find(tableName) != allTableInfo.end())
9976  {
9977  const TableInfo& tableInfo = allTableInfo.at(tableName);
9978  if(tableInfo.versions_.size() > 0)
9979  {
9980  auto maxVersionIter = tableInfo.versions_.rbegin();
9981  int maxVersionNum = maxVersionIter->version();
9982  int minVersionNum = std::max(0, maxVersionNum - offset);
9983 
9984  searchMinThreshold = minVersionNum;
9985  searchMaxThreshold = maxVersionNum;
9986 
9987  __SUP_COUT__ << "Version range: " << searchMinThreshold << " to "
9988  << searchMaxThreshold << __E__;
9989  }
9990  }
9991  }
9992  else
9993  {
9994  // Format: "N-M" means version N to version M
9995  std::string minStr = searchVersionToCheck.substr(0, dashPos);
9996  std::string maxStr = searchVersionToCheck.substr(dashPos + 1);
9997 
9998  searchMinThreshold = atoi(minStr.c_str());
9999  searchMaxThreshold = atoi(maxStr.c_str());
10000  }
10001  }
10002  else if(searchVersionToCheck != "")
10003  {
10004  // Single version specified
10005  searchMinThreshold = atoi(searchVersionToCheck.c_str());
10006  searchMaxThreshold = INT_MAX; // effectively no upper limit
10007  }
10008  else
10009  {
10010  __COUT__ << "Unable to set min and max thresholds for searchVersionToCheck: "
10011  << searchVersionToCheck << __E__;
10012  xmlOut.addTextElementToData(
10013  "Error",
10014  "Unable to set min and max thresholds for searchVersionToCheck: " +
10015  searchVersionToCheck);
10016  }
10017  __SUP_COUT__ << "searchVersionToCheck range: " << searchMinThreshold << " to "
10018  << searchMaxThreshold << __E__;
10019 
10020  try
10021  {
10022  if(allTableInfo.find(tableName) == allTableInfo.end())
10023  {
10024  __SUP_SS__ << "Table '" << tableName << "' not found." << __E__;
10025  xmlOut.addTextElementToData("Error", ss.str());
10026  return;
10027  }
10028 
10029  const TableInfo& tableInfo = allTableInfo.at(tableName);
10030  __COUT__ << "tableInfo versions length " << tableInfo.versions_.size() << __E__;
10031 
10032  unsigned int matchCount = 0;
10033  bool allowIllegalColumns = true;
10034  // Create a parent element for search results
10035  auto parentEl = xmlOut.addTextElementToData("SearchResults", "");
10036 
10037  // Loop through all versions of the table
10038  for(const auto& version : tableInfo.versions_)
10039  {
10040  try
10041  {
10042  auto activeIt = allActivePairs.find(tableName);
10043  bool isActiveVersion =
10044  (activeIt != allActivePairs.end() && activeIt->second == version);
10045  __COUT__ << "Active check for " << tableName << " v" << version << " => "
10046  << (isActiveVersion ? "active" : "inactive") << __E__;
10047  if(!isActiveVersion && activeTablesOnly)
10048  continue; // Skip versions that are not active
10049 
10050  if((std::stoi(version.toString()) < searchMinThreshold ||
10051  std::stoi(version.toString()) > searchMaxThreshold) &&
10052  activeTablesOnly == false /*ignore version threshold if active only*/)
10053  {
10054  __COUT__ << "Skipping version " << version << " out of range "
10055  << searchMinThreshold << " to " << searchMaxThreshold
10056  << __E__;
10057  continue;
10058  }
10059  TableBase* table = cfgMgr->getTableByName(tableName);
10060 
10061  // get view pointer
10062  TableView* tableViewPtr;
10063  if(version.isInvalid()) // use mock-up
10064  {
10065  __COUT__ << "Using mock-up for table " << tableName << " version "
10066  << version << __E__;
10067  tableViewPtr = table->getMockupViewP();
10068  }
10069  else // use view version
10070  {
10071  try
10072  {
10073  // locally accumulate 'manageable' errors getting the version to avoid
10074  // reverting to mockup
10075  std::string localAccumulatedErrors = "";
10076  tableViewPtr =
10077  cfgMgr
10079  tableName,
10080  version,
10081  allowIllegalColumns /*looseColumnMatching*/,
10082  &localAccumulatedErrors,
10083  false /*getRawData*/)
10084  ->getViewP();
10085 
10086  if(localAccumulatedErrors != "")
10087  xmlOut.addTextElementToData("Error", localAccumulatedErrors);
10088  }
10089  catch(const std::runtime_error& e)
10090  {
10091  __COUT__ << "Error loading version " << version << " for table "
10092  << tableName << ": " << e.what() << __E__;
10093  __SUP_COUT_WARN__ << "Could not load version " << version
10094  << " for table " << tableName << ": "
10095  << e.what() << __E__;
10096  // fallback to mockup
10097  tableViewPtr = table->getMockupViewP();
10098  }
10099  }
10100 
10101  __COUT__ << "Getting table " << tableName << " version " << version
10102  << __E__;
10103 
10104  // Search through all rows and columns
10105  for(unsigned int row = 0; row < tableViewPtr->getNumberOfRows(); ++row)
10106  {
10107  for(unsigned int col = 0; col < tableViewPtr->getNumberOfColumns();
10108  ++col)
10109  {
10110  const std::string& cellValue =
10111  tableViewPtr->getDataView()[row][col];
10112  // Check if searchText is found in cell value
10113  if(cellValue.find(searchText) != std::string::npos)
10114  {
10115  __COUT__ << "Match found in row " << row << " column " << col
10116  << " value: " << cellValue << __E__;
10117  auto matchEl = xmlOut.addTextElementToParent(
10118  "Match", cellValue, parentEl);
10119  xmlOut.addTextElementToParent(
10120  "MatchVersion", version.toString(), matchEl);
10121  xmlOut.addTextElementToParent(
10122  "MatchRow", std::to_string(row), matchEl);
10123  xmlOut.addTextElementToParent(
10124  "MatchColumn",
10125  tableViewPtr->getColumnInfo(col).getName(),
10126  matchEl);
10127 
10128  ++matchCount;
10129  }
10130  }
10131  } // end for rows and columns
10132  }
10133  catch(const std::runtime_error& e)
10134  {
10135  __COUT__ << "Error searching in version " << version << " for table "
10136  << tableName << ": " << e.what() << __E__;
10137  __SUP_COUT_WARN__ << "Could not load version " << version << " for table "
10138  << tableName << ": " << e.what() << __E__;
10139  }
10140  } //emd for version loop
10141 
10142  xmlOut.addTextElementToData("MatchCount", std::to_string(matchCount));
10143  }
10144  catch(std::runtime_error& e)
10145  {
10146  __SUP_SS__ << "Error searching in table '" << tableName << "'!\n\n " << e.what()
10147  << __E__;
10148  __SUP_COUT_ERR__ << ss.str();
10149  xmlOut.addTextElementToData("Error", ss.str());
10150  }
10151  catch(...)
10152  {
10153  __SUP_SS__ << "Error searching in table '" << tableName << "'!\n\n " << __E__;
10154  try
10155  {
10156  throw;
10157  }
10158  catch(const std::exception& e)
10159  {
10160  ss << "Exception message: " << e.what();
10161  }
10162  catch(...)
10163  {
10164  }
10165  __SUP_COUT_ERR__ << ss.str();
10166  xmlOut.addTextElementToData("Error", ss.str());
10167  }
10168 } // end handleSearchFieldInTableXML()
10169 
10170 //==============================================================================
10172 void ConfigurationGUISupervisor::handleGroupDiff(
10173  HttpXmlDocument& xmlOut,
10174  ConfigurationManagerRW* cfgMgr,
10175  const std::string& groupName,
10176  const TableGroupKey& groupKey,
10177  const TableGroupKey& diffKey /* = TableGroupKey() */,
10178  const std::string& diffGroupNameInput /* = "" */)
10179 {
10180  //Steps:
10181  // - Get group type and load table map
10182  // - Get match type active group table map
10183  // - For each table, compare
10184  std::string diffGroupName;
10185 
10186  if(diffKey.isInvalid())
10187  __SUP_COUT__ << "Differencing group " << groupName << "(" << groupKey
10188  << ") with the active group." << __E__;
10189  else
10190  {
10191  if(diffGroupNameInput == "")
10192  diffGroupName = groupName;
10193  else
10194  diffGroupName = diffGroupNameInput;
10195 
10196  __SUP_COUT__ << "Differencing group " << groupName << "(" << groupKey
10197  << ") with group " << diffGroupName << "(" << diffKey << ")"
10198  << __E__;
10199  }
10200 
10201  try
10202  {
10203  std::map<std::string /*name*/, TableVersion /*version*/> memberMap, diffMemberMap;
10204  std::string groupType, accumulateErrors;
10205  std::stringstream diffReport;
10206  bool noDifference = true;
10207 
10208  cfgMgr->loadTableGroup(
10209  groupName,
10210  groupKey,
10211  false /*doActivate*/,
10212  &memberMap /*groupMembers*/,
10213  0 /*progressBar*/,
10214  &accumulateErrors /*accumulateErrors*/,
10215  0 /*groupComment*/,
10216  0 /*groupAuthor*/,
10217  0 /*groupCreationTime*/,
10218  false /*doNotLoadMember*/,
10219  (diffKey.isInvalid()
10220  ? &groupType
10221  : 0)); //for specified diff group (not active), do not need groupType
10222 
10223  __SUP_COUTV__(StringMacros::mapToString(memberMap));
10224 
10225  std::map<std::string /* groupType */, std::pair<std::string, TableGroupKey>>
10226  activeGroups;
10227  if(diffKey.isInvalid())
10228  {
10229  activeGroups = cfgMgr->getActiveTableGroups();
10230 
10231  __SUP_COUTV__(StringMacros::mapToString(activeGroups));
10232  __SUP_COUTV__(groupType);
10233 
10234  if(activeGroups.find(groupType) == activeGroups.end() ||
10235  activeGroups.at(groupType).first == "" ||
10236  activeGroups.at(groupType).second.isInvalid())
10237  {
10238  __SUP_SS__ << "Could not find an active group of type '" << groupType
10239  << ".' Please check the expected active configuration groups "
10240  "for errors (going to 'System View' of the Config App may "
10241  "reveal errors)."
10242  << __E__;
10243  __SUP_SS_THROW__;
10244  }
10245 
10246  __SUP_COUT__ << "active " << groupType << " group is "
10247  << activeGroups.at(groupType).first << "("
10248  << activeGroups.at(groupType).second << ")" << __E__;
10249 
10250  diffReport << "This difference report is between " << groupType
10251  << " group <b>'" << groupName << "(" << groupKey << ")'</b>"
10252  << " and active group <b>'" << activeGroups.at(groupType).first
10253  << "(" << activeGroups.at(groupType).second << ")'</b>." << __E__;
10254 
10255  cfgMgr->loadTableGroup(activeGroups.at(groupType).first,
10256  activeGroups.at(groupType).second,
10257  false /*doActivate*/,
10258  &diffMemberMap /*groupMembers*/,
10259  0 /*progressBar*/,
10260  &accumulateErrors /*accumulateErrors*/,
10261  0 /*groupComment*/,
10262  0 /*groupAuthor*/,
10263  0 /*groupCreationTime*/,
10264  false /*doNotLoadMember*/);
10265 
10266  diffReport << "\n\n"
10267  << "'" << groupName << "(" << groupKey << ")' has <b>"
10268  << memberMap.size() << " member tables</b>, and "
10269  << "'" << activeGroups.at(groupType).first << "("
10270  << activeGroups.at(groupType).second << ")' has <b>"
10271  << diffMemberMap.size() << " member tables</b>." << __E__;
10272  }
10273  else //specified diff group (not active), so do not need groupType
10274  {
10275  diffReport << "This difference report is between group <b>'" << groupName
10276  << "(" << groupKey << ")'</b>"
10277  << " and group <b>'" << diffGroupName << "(" << diffKey
10278  << ")'</b>." << __E__;
10279 
10280  cfgMgr->loadTableGroup(diffGroupName,
10281  diffKey,
10282  false /*doActivate*/,
10283  &diffMemberMap /*groupMembers*/,
10284  0 /*progressBar*/,
10285  &accumulateErrors /*accumulateErrors*/,
10286  0 /*groupComment*/,
10287  0 /*groupAuthor*/,
10288  0 /*groupCreationTime*/,
10289  false /*doNotLoadMember*/);
10290 
10291  diffReport << "\n\n"
10292  << "'" << groupName << "(" << groupKey << ")' has <b>"
10293  << memberMap.size() << " member tables</b>, and "
10294  << "'" << diffGroupName << "(" << diffKey << ")' has <b>"
10295  << diffMemberMap.size() << " member tables</b>." << __E__;
10296  }
10297 
10298  __SUP_COUTV__(StringMacros::mapToString(diffMemberMap));
10299 
10300  diffReport << "<INDENT><ol>";
10301 
10302  unsigned int tableDifferences = 0;
10303 
10304  for(auto& member : memberMap)
10305  {
10306  if(diffMemberMap.find(member.first) == diffMemberMap.end())
10307  {
10308  diffReport << "\n\n<li>"
10309  << "Table <b>" << member.first << "-v" << member.second
10310  << "</b> not found in active group."
10311  << "</li>" << __E__;
10312  noDifference = false;
10313  ++tableDifferences;
10314  continue;
10315  }
10316 
10317  __SUP_COUTT__ << "Comparing " << member.first << "-v" << member.second
10318  << " ... " << member.first << "-v"
10319  << diffMemberMap.at(member.first) << __E__;
10320 
10321  if(member.second == diffMemberMap.at(member.first))
10322  continue;
10323 
10324  diffReport << "\n\n<li>"
10325  << "Table <b>" << member.first << " v" << member.second
10326  << "</b> in " << groupName << "(" << groupKey << ")' ...vs... "
10327  << " <b>v" << diffMemberMap.at(member.first) << "</b> in "
10328  << diffGroupName << "(" << diffKey << ")':" << __E__;
10329 
10330  TableBase* table = cfgMgr->getTableByName(member.first);
10331 
10332  diffReport << "<ul>";
10333  std::map<std::string /* uid */, std::vector<std::string /* colName */>>
10334  modifiedRecords; //useful for tree diff view display
10335  if(!table->diffTwoVersions(member.second,
10336  diffMemberMap.at(member.first),
10337  &diffReport,
10338  &modifiedRecords))
10339  {
10340  //difference found!
10341  noDifference = false;
10342  ++tableDifferences;
10343  auto parentEl =
10344  xmlOut.addTextElementToData("TableWithDiff", member.first);
10345  for(auto& modifiedRecord : modifiedRecords)
10346  {
10347  auto recordParentEl = xmlOut.addTextElementToParent(
10348  "RecordWithDiff", modifiedRecord.first, parentEl);
10349  for(auto& modifiedColumn : modifiedRecord.second)
10350  xmlOut.addTextElementToParent(
10351  "ColNameWithDiff", modifiedColumn, recordParentEl);
10352  }
10353  }
10354  diffReport << "</ul></li>";
10355 
10356  } //end member table comparison loop
10357 
10358  for(auto& diffMember : diffMemberMap)
10359  {
10360  if(memberMap.find(diffMember.first) == memberMap.end())
10361  {
10362  if(diffKey.isInvalid())
10363  diffReport << "\n\n<li>"
10364  << "Active Group Table <b>" << diffMember.first << "-v"
10365  << diffMember.second << "</b> not found in '" << groupName
10366  << "(" << groupKey << ")'."
10367  << "</li>" << __E__;
10368  else
10369  diffReport << "\n\n<li>" << diffGroupName << "(" << diffKey
10370  << ") Table <b>" << diffMember.first << "-v"
10371  << diffMember.second << "</b> not found in '" << groupName
10372  << "(" << groupKey << ")'."
10373  << "</li>" << __E__;
10374 
10375  noDifference = false;
10376  ++tableDifferences;
10377  continue;
10378  }
10379  }
10380  diffReport << "\n</ol></INDENT>";
10381 
10382  if(diffKey.isInvalid())
10383  {
10384  if(noDifference)
10385  diffReport << "\n\nNo difference found between "
10386  << "<b>'" << groupName << "(" << groupKey
10387  << ")'</b> and active group "
10388  << "<b>'" << activeGroups.at(groupType).first << "("
10389  << activeGroups.at(groupType).second << ")'</b>." << __E__;
10390  else
10391  diffReport << "\n\n<b>" << tableDifferences
10392  << "</b> member table differences identified between "
10393  << "<b>'" << groupName << "(" << groupKey
10394  << ")'</b> and active group "
10395  << "<b>'" << activeGroups.at(groupType).first << "("
10396  << activeGroups.at(groupType).second << ")'</b>." << __E__;
10397  }
10398  else
10399  {
10400  if(noDifference)
10401  diffReport << "\n\nNo difference found between "
10402  << "<b>'" << groupName << "(" << groupKey
10403  << ")'</b> and group "
10404  << "<b>'" << diffGroupName << "(" << diffKey << ")'</b>."
10405  << __E__;
10406  else
10407  diffReport << "\n\n<b>" << tableDifferences
10408  << "</b> member table differences identified between "
10409  << "<b>'" << groupName << "(" << groupKey
10410  << ")'</b> and group "
10411  << "<b>'" << diffGroupName << "(" << diffKey << ")'</b>."
10412  << __E__;
10413  }
10414 
10415  xmlOut.addTextElementToData("NoDifference", noDifference ? "1" : "0");
10416  xmlOut.addTextElementToData("DiffReport", diffReport.str());
10417  }
10418  catch(const std::runtime_error& e)
10419  {
10420  __SUP_COUT_ERR__ << "Caught error while differencing group " << groupName << "("
10421  << groupKey << ") with group " << diffGroupName << "(" << diffKey
10422  << ")" << __E__ << e.what() << __E__;
10423  throw; //rethrow
10424  }
10425 } // end handleGroupDiff()
10426 
10427 //==============================================================================
10429 void ConfigurationGUISupervisor::handleTableDiff(HttpXmlDocument& xmlOut,
10430  ConfigurationManagerRW* cfgMgr,
10431  const std::string& tableName,
10432  const TableVersion& vA,
10433  const TableVersion& vB)
10434 {
10435  __SUP_COUT__ << "Differencing tableName " << tableName << " v" << vA << " with v"
10436  << vB << __E__;
10437 
10438  //first make sure tables are loaded
10439  TableBase* table = cfgMgr->getTableByName(tableName);
10440 
10441  try
10442  {
10443  // locally accumulate 'manageable' errors getting the version to avoid
10444  // reverting to mockup
10445  std::string localAccumulatedErrors = "";
10446  cfgMgr->getVersionedTableByName(tableName,
10447  vA,
10448  false /*looseColumnMatching*/,
10449  &localAccumulatedErrors,
10450  false /*getRawData*/);
10451 
10452  if(localAccumulatedErrors != "")
10453  xmlOut.addTextElementToData("Error", localAccumulatedErrors);
10454  }
10455  catch(std::runtime_error& e) // default to mock-up for fail-safe in GUI editor
10456  {
10457  __SUP_SS__ << "Failed to get table " << tableName << " version " << vA;
10458  ss << "\n\n...Here is why it failed:\n\n" << e.what() << __E__;
10459  __SUP_COUT_ERR__ << "\n" << ss.str();
10460 
10461  xmlOut.addTextElementToData("Error", "Error getting view! " + ss.str());
10462  }
10463  catch(...) // default to mock-up for fail-safe in GUI editor
10464  {
10465  __SUP_SS__ << "Failed to get table " << tableName << " version: " << vA << __E__;
10466  try
10467  {
10468  throw;
10469  } //one more try to printout extra info
10470  catch(const std::exception& e)
10471  {
10472  ss << "Exception message: " << e.what();
10473  }
10474  catch(...)
10475  {
10476  }
10477 
10478  __SUP_COUT_ERR__ << "\n" << ss.str();
10479  xmlOut.addTextElementToData("Error", "Error getting view! " + ss.str());
10480  }
10481  try
10482  {
10483  // locally accumulate 'manageable' errors getting the version to avoid
10484  // reverting to mockup
10485  std::string localAccumulatedErrors = "";
10486  cfgMgr->getVersionedTableByName(tableName,
10487  vB,
10488  false /*looseColumnMatching*/,
10489  &localAccumulatedErrors,
10490  false /*getRawData*/);
10491 
10492  if(localAccumulatedErrors != "")
10493  xmlOut.addTextElementToData("Error", localAccumulatedErrors);
10494  }
10495  catch(std::runtime_error& e) // default to mock-up for fail-safe in GUI editor
10496  {
10497  __SUP_SS__ << "Failed to get table " << tableName << " version " << vB;
10498  ss << "\n\n...Here is why it failed:\n\n" << e.what() << __E__;
10499  __SUP_COUT_ERR__ << "\n" << ss.str();
10500 
10501  xmlOut.addTextElementToData("Error", "Error getting view! " + ss.str());
10502  }
10503  catch(...) // default to mock-up for fail-safe in GUI editor
10504  {
10505  __SUP_SS__ << "Failed to get table " << tableName << " version: " << vB << __E__;
10506  try
10507  {
10508  throw;
10509  } //one more try to printout extra info
10510  catch(const std::exception& e)
10511  {
10512  ss << "Exception message: " << e.what();
10513  }
10514  catch(...)
10515  {
10516  }
10517 
10518  __SUP_COUT_ERR__ << "\n" << ss.str();
10519  xmlOut.addTextElementToData("Error", "Error getting view! " + ss.str());
10520  }
10521 
10522  bool noDifference = true;
10523  std::stringstream diffReport;
10524 
10525  diffReport << "This difference report is between table " << tableName << " v" << vA
10526  << " and v" << vB << "</b>." << __E__;
10527 
10528  diffReport << "<INDENT>";
10529  diffReport << "<ul>";
10530  std::map<std::string /* uid */, std::vector<std::string /* colName */>>
10531  modifiedRecords; //useful for tree diff view display
10532  if(!table->diffTwoVersions(vA, vB, &diffReport))
10533  noDifference = false; //difference found!
10534  diffReport << "</ul></INDENT>";
10535 
10536  xmlOut.addTextElementToData("NoDifference", noDifference ? "1" : "0");
10537  xmlOut.addTextElementToData("DiffReport", diffReport.str());
10538 } // end handleTableDiff()
10539 
10540 //==============================================================================
10543 void ConfigurationGUISupervisor::testXDAQContext()
10544 {
10545  if(0) //keep for debugging
10546  {
10547  __COUT_INFO__ << "Hello0!";
10548  ConfigurationManagerRW cfgMgrInst("ExampleUser");
10549  __COUT_INFO__ << "Hello1!";
10550  ConfigurationManagerRW* cfgMgr = &cfgMgrInst;
10551  __COUT_INFO__ << "Hello2!";
10552  cfgMgr->testXDAQContext();
10553  __COUT_INFO__ << "Hello3!";
10554  return;
10555  }
10556 
10557  try
10558  {
10559  __SUP_COUT_INFO__ << "Attempting test activation of the context group." << __E__;
10561  cfgMgr; // create instance to activate saved context and backbone groups (not config group)
10562  }
10563  catch(const std::runtime_error& e)
10564  {
10565  __SUP_COUT_WARN__
10566  << "The test activation of the context group failed. Ignoring error: \n"
10567  << e.what() << __E__;
10568  return;
10569  }
10570  catch(...)
10571  {
10572  __SUP_COUT_WARN__ << "The test activation of the context group failed. Ignoring."
10573  << __E__;
10574  return;
10575  }
10576 
10577  __SUP_COUT_INFO__ << "Completed test activation of the context group." << __E__;
10578  return;
10579 
10581  // below has been used for debugging.
10582 
10583  // behave like a user
10584  // start with top level xdaq context
10585  // then add and delete rows proof-of-concept
10586  // export xml xdaq table file
10587 
10590  // behave like a new user
10591  //
10592  // ConfigurationManagerRW cfgMgrInst("ExampleUser");
10593 
10594  // ConfigurationManagerRW* cfgMgr =& cfgMgrInst;
10595 
10596  // // std::map<std::string, TableVersion> groupMembers;
10597  // // groupMembers["DesktopIcon"] = TableVersion(2);
10598  // // cfgMgr->saveNewTableGroup("test",
10599  // // groupMembers, "test comment");
10600 
10601  // //
10602  // const std::map<std::string, TableInfo>& allTableInfo =
10603  // cfgMgr->getAllTableInfo(true /* refresh*/);
10604  // __SUP_COUT__ << "allTableInfo.size() = " << allTableInfo.size() << __E__;
10605  // for(auto& mapIt : allTableInfo)
10606  // {
10607  // __SUP_COUT__ << "Table Name: " << mapIt.first << __E__;
10608  // __SUP_COUT__ << "\t\tExisting Versions: " << mapIt.second.versions_.size()
10609  // <<
10610  // __E__;
10611 
10612  // //get version key for the current system table key
10613  // for (auto& v:mapIt.second.versions_)
10614  // {
10615  // __SUP_COUT__ << "\t\t" << v << __E__;
10616  // }
10617  // }
10618  // __SUP_COUTT__ << "Group Info end runtime=" << cfgMgr->runTimeSeconds() << __E__;
10619  // testXDAQContext just a test bed for navigating the new config tree
10620  // cfgMgr->testXDAQContext();
10621 
10624 } // end testXDAQContext()
static void setAndActivateARTDAQSystem(ConfigurationManagerRW *cfgMgr, const std::map< std::string, std::map< std::string, std::vector< std::string >>> &nodeTypeToObjectMap, const std::map< std::string, std::string > &subsystemObjectMap)
static const ARTDAQInfo & getARTDAQSystem(ConfigurationManagerRW *cfgMgr, std::map< std::string, std::map< std::string, std::vector< std::string >>> &nodeTypeToObjectMap, std::map< std::string, std::string > &subsystemObjectMap, std::vector< std::string > &artdaqSupervisoInfo, bool suppressMultiNode=false)
static std::string postData(cgicc::Cgicc &cgi, const std::string &needle)
static std::string getData(cgicc::Cgicc &cgi, const std::string &needle)
virtual void forceSupervisorPropertyValues(void) override
override to force supervisor property values (and ignore user settings)
virtual void setSupervisorPropertyDefaults(void) override
ConfigurationGUISupervisor(xdaq::ApplicationStub *s)
static xdaq::Application * instantiate(xdaq::ApplicationStub *s)
TableVersion saveNewTable(const std::string &tableName, TableVersion temporaryVersion=TableVersion(), bool makeTemporary=false)
const GroupInfo & getGroupInfo(const std::string &groupName, bool attemptToReloadKeys=false)
const std::map< std::string, TableInfo > & getAllTableInfo(bool refresh=false, std::string *accumulatedWarnings=0, const std::string &errorFilterName="", bool getGroupKeys=false, bool getGroupInfo=false, bool initializeActiveGroups=false)
void loadTableGroup(const std::string &tableGroupName, const TableGroupKey &tableGroupKey, bool doActivate=false, std::map< std::string, TableVersion > *groupMembers=0, ProgressBar *progressBar=0, std::string *accumulateWarnings=0, std::string *groupComment=0, std::string *groupAuthor=0, std::string *groupCreateTime=0, bool doNotLoadMember=false, std::string *groupTypeString=0, std::map< std::string, std::string > *groupAliases=0, ConfigurationManager::LoadGroupType groupTypeToLoad=ConfigurationManager::LoadGroupType::ALL_TYPES, bool ignoreVersionTracking=false)
TableVersion copyViewToCurrentColumns(const std::string &tableName, TableVersion sourceVersion)
TableGroupKey saveNewTableGroup(const std::string &groupName, std::map< std::string, TableVersion > &groupMembers, const std::string &groupComment=TableViewColumnInfo::DATATYPE_COMMENT_DEFAULT, std::map< std::string, std::string > *groupAliases=0)
void activateTableGroup(const std::string &tableGroupName, TableGroupKey tableGroupKey, std::string *accumulatedTreeErrors=0, std::string *groupTypeString=0)
void clearCachedVersions(const std::string &tableName)
const std::string & getUsername(void) const
std::map< std::string, std::map< std::string, TableVersion > > getVersionAliases(void) const
void eraseTemporaryVersion(const std::string &tableName, TableVersion targetVersion=TableVersion())
void preloadVersionCreationTimes(void)
static void loadTableGroupThread(ConfigurationManagerRW *cfgMgr, std::string groupName, ots::TableGroupKey groupKey, std::shared_ptr< ots::GroupInfo > theGroupInfo, std::shared_ptr< std::atomic< bool >> theThreadDone)
void restoreActiveTableGroups(bool throwErrors=false, const std::string &pathToActiveGroupsFile="", ConfigurationManager::LoadGroupType onlyLoadIfBackboneOrContext=ConfigurationManager::LoadGroupType::ALL_TYPES, std::string *accumulatedWarnings=0)
std::map< std::string, std::pair< std::string, TableGroupKey > > getActiveTableGroups(void) const
std::map< std::string, TableVersion > getActiveVersions(void) const
void copyTableGroupFromCache(const ConfigurationManager &cacheConfigMgr, const std::map< std::string, TableVersion > &groupMembers, const std::string &configGroupName="", const TableGroupKey &tableGroupKey=TableGroupKey(TableGroupKey::INVALID), bool doActivate=false, bool ignoreVersionTracking=false)
ConfigurationTree getNode(const std::string &nodeString, bool doNotThrowOnBrokenUIDLinks=false) const
TableBase * getVersionedTableByName(const std::string &tableName, TableVersion version, bool looseColumnMatching=false, std::string *accumulatedErrors=0, bool getRawData=false, bool touchLastAccessTime=true)
void init(std::string *accumulatedErrors=0, bool initForWriteAccess=false, std::string *accumulatedWarnings=0)
static const std::string & getTypeNameOfGroup(const std::map< std::string, TableVersion > &memberMap)
void destroyTableGroup(const std::string &theGroup="", bool onlyDeactivate=false)
std::vector< std::pair< std::string, ConfigurationTree > > getChildren(std::map< std::string, TableVersion > *memberMap=0, std::string *accumulatedTreeErrors=0) const
std::map< std::string, ConfigurationTree > getChildrenMap(std::map< std::string, TableVersion > *memberMap=0, std::string *accumulatedTreeErrors=0) const
static std::vector< std::map< std::string, std::string > > loadGroupHistory(const std::string &groupAction, const std::string &groupType, bool formatTime=false)
TableGroupKey loadConfigurationBackbone(void)
const TableBase * getTableByName(const std::string &configurationName) const
static std::pair< std::string, TableGroupKey > loadGroupNameAndKey(const std::string &fileName, std::string &returnedTimeString)
static void getConfigurationStatusXML(HttpXmlDocument &xmlOut, ConfigurationManagerRW *cfgMgr, const std::string &username)
static void handleGetTableGroupXML(HttpXmlDocument &xmlOut, ConfigurationManagerRW *cfgMgr, const std::string &groupName, TableGroupKey groupKey, bool ignoreWarnings=false, bool cacheOnly=false)
static TableVersion saveModifiedVersionXML(HttpXmlDocument &xmlOut, ConfigurationManagerRW *cfgMgr, const std::string &tableName, TableVersion originalVersion, bool makeTemporary, TableBase *config, TableVersion temporaryModifiedVersion, bool ignoreDuplicates=false, bool lookForEquivalent=false)
static void handleCreateTableXML(HttpXmlDocument &xmlOut, ConfigurationManagerRW *cfgMgr, const std::string &tableName, TableVersion version, bool makeTemporary, const std::string &data, const int &dataOffset, const std::string &author, const std::string &comment, bool sourceTableAsIs, bool lookForEquivalent)
static void handleCreateTableGroupXML(HttpXmlDocument &xmlOut, ConfigurationManagerRW *cfgMgr, const std::string &groupName, const std::string &configList, bool allowDuplicates=false, bool ignoreWarnings=false, const std::string &groupComment="", bool lookForEquivalent=false)
bool isUIDNode(void) const
const TableVersion & getTableVersion(void) const
bool isDisconnected(void) const
const std::string & getAuthor(void) const
const std::string & getComment(void) const
std::vector< std::string > getChildrenNames(bool byPriority=false, bool onlyStatusTrue=false) const
bool isEnabled(void) const
const std::string & getTableName(void) const
const unsigned int & getFieldRow(void) const
std::map< std::string, ConfigurationTree > getChildrenMap(std::map< std::string, std::string > filterMap=std::map< std::string, std::string >(), bool onlyStatusTrue=false) const
const std::string & getValueName(void) const
const std::string & getValueAsString(bool returnLinkTableValue=false) const
const std::string & getChildLinkIndex(void) const
void getValue(T &value) const
const std::string & getDisconnectedTableName(void) const
std::vector< std::pair< std::string, ConfigurationTree > > getChildren(std::map< std::string, std::string > filterMap=std::map< std::string, std::string >(), bool byPriority=false, bool onlyStatusTrue=false) const
std::vector< std::string > getFixedChoices(void) const
bool isLinkNode(void) const
std::vector< ConfigurationTree::RecordField > getCommonFields(const std::vector< std::string > &recordList, const std::vector< std::string > &fieldAcceptList, const std::vector< std::string > &fieldRejectList, unsigned int depth=-1, bool autoSelectFilterFields=false) const
bool isValueNode(void) const
std::set< std::string > getUniqueValuesForField(const std::vector< std::string > &recordList, const std::string &fieldName, std::string *fieldGroupIDChildLinkIndex=0) const
const std::string & getValueType(void) const
bool isGroupLinkNode(void) const
const std::string & getFieldTableName(void) const
const std::string & getDisconnectedLinkID(void) const
const std::string & getParentTableName(void) const
bool isUIDLinkNode(void) const
const unsigned int & getFieldColumn(void) const
static std::string convertToCaps(std::string &str, bool isConfigName=false)
std::string toString(void) const
bool isInvalid(void) const
std::string str() const
static std::string getFullGroupString(const std::string &groupName, const TableGroupKey &key, const std::string &preKey="_v", const std::string &postKey="")
std::string toString(void) const
unsigned int version(void) const
static const std::string DATATYPE_NUMBER
static std::map< std::pair< std::string, std::string >, std::string > getAllDefaultsForGUI(void)
bool isChildLink(void) const
static const std::string & getDefaultDefaultValue(const std::string &type, const std::string &dataType)
static const std::string & getMaxDefaultValue(const std::string &dataType)
static const std::string & getMinDefaultValue(const std::string &dataType)
unsigned int findRow(unsigned int col, const T &value, unsigned int offsetRow=0, bool doNotThrow=false) const
bool isEntryInGroup(const unsigned int &row, const std::string &childLinkIndex, const std::string &groupNeedle) const
void setValueAsString(const std::string &value, unsigned int row, unsigned int col)
void deleteRow(int r)
unsigned int getColStatus(void) const
unsigned int getLinkGroupIDColumn(const std::string &childLinkIndex) const
bool removeRowFromGroup(const unsigned int &row, const unsigned int &col, const std::string &groupID, bool deleteRowIfNoGroupLeft=false)
bool getChildLink(const unsigned int &col, bool &isGroup, std::pair< unsigned int, unsigned int > &linkPair) const
void addRowToGroup(const unsigned int &row, const unsigned int &col, const std::string &groupID)
void init(void)
std::string getValueAsString(unsigned int row, unsigned int col, bool convertEnvironmentVariables=true) const
void getValue(T &value, unsigned int row, unsigned int col, bool doConvertEnvironmentVariables=true) const
unsigned int getColUID(void) const
bool setURIEncodedValue(const std::string &value, const unsigned int &row, const unsigned int &col, const std::string &author="")
unsigned int findCol(const std::string &name) const
void setValue(const T &value, unsigned int row, unsigned int col)
void setURIEncodedComment(const std::string &uriComment)
unsigned int addRow(const std::string &author="", unsigned char incrementUniqueData=false, const std::string &baseNameAutoUID="", unsigned int rowToAdd=(unsigned int) -1, std::string childLinkIndex="", std::string groupId="")
void extractContexts(ConfigurationManager *configManager)
xercesc::DOMElement * addTextElementToParent(const std::string &childName, const std::string &childText, xercesc::DOMElement *parent)
void INIT_MF(const char *name)
static std::string getTimestampString(const std::string &linuxTimeInSeconds)
static void getVectorFromString(const std::string &inputString, std::vector< std::string > &listToReturn, const std::set< char > &delimiter={',', '|', '&'}, const std::set< char > &whitespace={' ', '\t', '\n', '\r'}, std::vector< char > *listOfDelimiters=0, bool decodeURIComponents=false)
static std::string exec(const char *cmd)
static std::string setToString(const std::set< T > &setToReturn, const std::string &delimeter=", ")
static std::string escapeString(std::string inString, bool allowWhiteSpace=false, bool forHtml=false)
static std::string vectorToString(const std::vector< T > &setToReturn, const std::string &delimeter=", ")
static std::string convertEnvironmentVariables(const std::string &data)
static bool isNumber(const std::string &stringToCheck)
static std::string mapToString(const std::map< std::string, T > &mapToReturn, const std::string &primaryDelimeter=", ", const std::string &secondaryDelimeter=": ")
static void getMapFromString(const std::string &inputString, std::map< S, T > &mapToReturn, const std::set< char > &pairPairDelimiter={',', '|', '&'}, const std::set< char > &nameValueDelimiter={'=', ':'}, const std::set< char > &whitespace={' ', '\t', '\n', '\r'})
static std::string decodeURIComponent(const std::string &data)