otsdaq-utilities  3.09.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 
5120  // create table-edit struct for each table that an iterate command type can use
5121  //if two command types have same table, TableEditStruct returns the same temporary version of the table, but then modified_
5122  // will be maintained separately and saving the table becomes a mess.
5123  std::map<std::string /* table name */, TableEditStruct> commandTableToEditMap;
5124  for(const auto& commandPair : IterateTable::commandToTableMap_)
5125  if(commandPair.second != "") // skip tables with no parameters
5126  commandTableToEditMap.emplace(std::pair<std::string, TableEditStruct>(
5127  commandPair.second, TableEditStruct(commandPair.second, cfgMgr)));
5128 
5129  // try to catch any errors while editing..
5130  // if errors delete temporary plan view (if created here)
5131  try
5132  {
5133  // Steps:
5134  // Reset plan commands
5135  // Remove all commands in group "<plan>-Plan"
5136  // Delete linked command parameters row (in separate table)
5137  // If no group remaining, then delete row.
5138  //
5139  // Save plan commands (if modified)
5140  // Create rows and add them to group "<plan>-Plan"
5141  // create row for command paramaters and add to proper table
5142 
5143  std::string groupName = planName + "-Plan";
5144  __SUP_COUT__ << "Handling commands for group " << groupName << __E__;
5145 
5146  unsigned int groupIdCol =
5147  planTable.tableView_->findCol(IterateTable::planTableCols_.GroupID_);
5148  unsigned int cmdTypeCol =
5149  planTable.tableView_->findCol(IterateTable::planTableCols_.CommandType_);
5150 
5151  unsigned int targetGroupIdCol =
5152  targetTable.tableView_->findCol(IterateTable::targetCols_.GroupID_);
5153  unsigned int targetTableCol =
5154  targetTable.tableView_->findCol(IterateTable::targetCols_.TargetLink_);
5155  unsigned int targetUIDCol =
5156  targetTable.tableView_->findCol(IterateTable::targetCols_.TargetLinkUID_);
5157 
5158  std::string groupLinkIndex =
5159  planTable.tableView_->getColumnInfo(groupIdCol).getChildLinkIndex();
5160  __SUP_COUT__ << "groupLinkIndex: " << groupLinkIndex << __E__;
5161 
5162  std::pair<unsigned int /*link col*/, unsigned int /*link id col*/> commandUidLink;
5163  {
5164  bool isGroup; // local because we know is uid link
5165  planTable.tableView_->getChildLink(
5166  planTable.tableView_->findCol(IterateTable::planTableCols_.CommandLink_),
5167  isGroup,
5168  commandUidLink);
5169  }
5170 
5171  unsigned int cmdRow, cmdCol;
5172  std::string targetGroupName;
5173 
5174  // Reset existing plan commands
5175  {
5176  std::string targetUID, cmdType;
5177 
5178  for(unsigned int row = 0; row < planTable.tableView_->getNumberOfRows();
5179  ++row)
5180  {
5181  targetUID = planTable.tableView_
5182  ->getDataView()[row][planTable.tableView_->getColUID()];
5183  __SUP_COUT__ << "targetUID: " << targetUID << __E__;
5184 
5185  // remove command from plan group.. if no more groups, delete
5186  if(planTable.tableView_->isEntryInGroup(row, groupLinkIndex, groupName))
5187  {
5188  __SUP_COUT__ << "Removing." << __E__;
5189 
5190  // delete linked command
5191  // find linked UID in table (mapped by type)
5192  cmdType = planTable.tableView_->getDataView()[row][cmdTypeCol];
5193  auto cmdTypeTableIt = IterateTable::commandToTableMap_.find(cmdType);
5194  if(cmdTypeTableIt != IterateTable::commandToTableMap_.end() &&
5195  cmdTypeTableIt->second !=
5196  "") // skip if invalid command type or if no command parameter table
5197  {
5198  TableEditStruct& cmdTypeTableEdit =
5199  commandTableToEditMap.at(cmdTypeTableIt->second);
5200  cmdRow = cmdTypeTableEdit.tableView_->findRow(
5201  cmdTypeTableEdit.tableView_->getColUID(),
5202  planTable.tableView_
5203  ->getDataView()[row][commandUidLink.second]);
5204 
5205  // before deleting row...
5206  // look for target group
5207  // remove all targets in group
5208  try
5209  {
5210  cmdCol = cmdTypeTableEdit.tableView_->findCol(
5211  IterateTable::commandTargetCols_.TargetsLinkGroupID_);
5212  targetGroupName = cmdTypeTableEdit.tableView_
5213  ->getDataView()[cmdRow][cmdCol];
5214 
5215  for(unsigned int trow = 0;
5216  trow < targetTable.tableView_->getNumberOfRows();
5217  ++trow)
5218  {
5219  // remove command from target group..
5220  if(targetTable.tableView_->isEntryInGroup(
5221  trow,
5222  cmdTypeTableEdit.tableView_->getColumnInfo(cmdCol)
5223  .getChildLinkIndex(),
5224  targetGroupName))
5225  {
5226  __SUP_COUT__ << "Removing target." << __E__;
5227  // remove command entry in plan table
5228  if(targetTable.tableView_->removeRowFromGroup(
5229  trow,
5230  targetGroupIdCol,
5231  targetGroupName,
5232  true /*deleteRowIfNoGroup*/))
5233  --trow; // since row was deleted, go back!
5234  }
5235  }
5236  }
5237  catch(...)
5238  {
5239  __SUP_COUT__ << "No targets." << __E__;
5240  }
5241 
5242  // now no more targets, delete row
5243 
5244  cmdTypeTableEdit.tableView_->deleteRow(cmdRow);
5245 
5246  cmdTypeTableEdit.modified_ = true;
5247  }
5248 
5249  // remove command entry in plan table
5250  if(planTable.tableView_->removeRowFromGroup(
5251  row, groupIdCol, groupName, true /*deleteRowIfNoGroup*/))
5252  --row; // since row was deleted, go back!
5253  }
5254  }
5255  }
5256 
5257  // Done resetting existing plan
5258  // Now save new commands
5259 
5260  std::vector<IterateTable::Command> commands;
5261 
5262  // extract command sequence and add to table
5263  // into vector with type, and params
5264  {
5265  std::istringstream f(commandString);
5266  std::string commandSubString, paramSubString, paramValue;
5267  int i;
5268  while(getline(f, commandSubString, ';'))
5269  {
5270  __SUP_COUTT__ << "commandSubString " << commandSubString << __E__;
5271  std::istringstream g(commandSubString);
5272 
5273  i = 0;
5274  while(getline(g, paramSubString, ','))
5275  {
5276  __SUP_COUTT__ << "paramSubString " << paramSubString << __E__;
5277  if(i == 0) // type
5278  {
5279  if(paramSubString != "type")
5280  {
5281  __SUP_SS__ << "Invalid command sequence" << __E__;
5282  __SS_THROW__;
5283  }
5284  // create command object
5285  commands.push_back(IterateTable::Command());
5286 
5287  getline(g, paramValue, ',');
5288  ++i;
5289  __SUP_COUTT__ << "paramValue " << paramValue << __E__;
5290  commands.back().type_ = paramValue;
5291  }
5292  else // params
5293  {
5294  getline(g, paramValue, ',');
5295  ++i;
5296  __SUP_COUTT__ << "paramValue " << paramValue << __E__;
5297 
5298  commands.back().params_.emplace(
5299  std::pair<std::string /*param name*/,
5300  std::string /*param value*/>(
5301  paramSubString,
5302  StringMacros::decodeURIComponent(paramValue)));
5303  }
5304 
5305  ++i;
5306  }
5307  }
5308 
5309  } // end extract command sequence
5310 
5311  __SUP_COUT__ << "commands size " << commands.size() << __E__;
5312 
5313  // at this point, have extracted commands
5314 
5315  // now save commands to plan group
5316  // group should be "<plan>-Plan"
5317 
5318  unsigned int row, tgtRow;
5319  unsigned int targetIndex;
5320  std::string targetStr, cmdUID;
5321 
5322  for(auto& command : commands)
5323  {
5324  __SUP_COUT__ << "command " << command.type_ << __E__;
5325  __SUP_COUT__ << "table " << IterateTable::commandToTableMap_.at(command.type_)
5326  << __E__;
5327 
5328  // create command entry at plan level
5329  row = planTable.tableView_->addRow(
5330  author, true /*incrementUniqueData*/, "planCommand");
5331  planTable.tableView_->addRowToGroup(row, groupIdCol, groupName);
5332 
5333  // set command type
5334  planTable.tableView_->setURIEncodedValue(command.type_, row, cmdTypeCol);
5335 
5336  // set command status true
5337  planTable.tableView_->setValueAsString(
5338  "1", row, planTable.tableView_->getColStatus());
5339 
5340  // create command specifics
5341  auto cmdTypeTableIt = IterateTable::commandToTableMap_.find(command.type_);
5342  if(cmdTypeTableIt != IterateTable::commandToTableMap_.end() &&
5343  cmdTypeTableIt->second !=
5344  "") // skip if invalid command type or if no command parameter table
5345  {
5346  TableEditStruct& cmdTypeTableEdit =
5347  commandTableToEditMap.at(cmdTypeTableIt->second);
5348  __SUP_COUT__ << "table " << cmdTypeTableEdit.tableName_ << __E__;
5349 
5350  // at this point have table, tempVersion, and createdFlag
5351 
5352  // create command parameter entry at command level
5353  cmdRow = cmdTypeTableEdit.tableView_->addRow(
5354  author, true /*incrementUniqueData*/, command.type_ + "_COMMAND_");
5355 
5356  // parameters are linked
5357  // now set value of all parameters
5358  // find parameter column, and set value
5359  // if special target parameter, extract targets
5360  for(auto& param : command.params_)
5361  {
5362  __SUP_COUT__ << "\t param " << param.first << " : " << param.second
5363  << __E__;
5364 
5365  if(param.first == IterateTable::targetParams_.Tables_)
5366  {
5367  __SUP_COUT__ << "\t\t found target tables" << __E__;
5368  std::istringstream f(param.second);
5369 
5370  targetIndex = 0;
5371  while(getline(f, targetStr, '='))
5372  {
5373  __SUP_COUT__ << "\t\t targetStr = " << targetStr << __E__;
5374  if(!command.targets_.size() ||
5375  command.targets_.back().table_ != "")
5376  {
5377  __SUP_COUT__ << "\t\t make targetStr = " << targetStr
5378  << __E__;
5379  // make new target
5380  command.addTarget();
5381  command.targets_.back().table_ = targetStr;
5382  }
5383  else // file existing target
5384  command.targets_[targetIndex++].table_ = targetStr;
5385  }
5386 
5387  continue; // go to next parameter
5388  }
5389 
5390  if(param.first == IterateTable::targetParams_.UIDs_)
5391  {
5392  __SUP_COUT__ << "\t\t found target UIDs" << __E__;
5393  std::istringstream f(param.second);
5394 
5395  targetIndex = 0;
5396  while(getline(f, targetStr, '='))
5397  {
5398  __SUP_COUT__ << "\t\t targetStr = " << targetStr << __E__;
5399  if(!command.targets_.size() ||
5400  command.targets_.back().UID_ != "")
5401  {
5402  __SUP_COUT__ << "\t\t make targetStr = " << targetStr
5403  << __E__;
5404  // make new target
5405  command.addTarget();
5406  command.targets_.back().UID_ = targetStr;
5407  }
5408  else // file existing target
5409  command.targets_[targetIndex++].UID_ = targetStr;
5410  }
5411  continue;
5412  }
5413 
5414  cmdCol = cmdTypeTableEdit.tableView_->findCol(param.first);
5415 
5416  __SUP_COUT__ << "param col " << cmdCol << __E__;
5417 
5418  cmdTypeTableEdit.tableView_->setURIEncodedValue(
5419  param.second, cmdRow, cmdCol);
5420  } // end parameter loop
5421 
5422  cmdUID =
5423  cmdTypeTableEdit.tableView_
5424  ->getDataView()[cmdRow][cmdTypeTableEdit.tableView_->getColUID()];
5425 
5426  if(command.targets_.size())
5427  {
5428  // if targets, create group in target table
5429 
5430  __SUP_COUT__ << "targets found for command UID=" << cmdUID << __E__;
5431 
5432  // create link from command table to target
5433  cmdCol = cmdTypeTableEdit.tableView_->findCol(
5434  IterateTable::commandTargetCols_.TargetsLink_);
5435  cmdTypeTableEdit.tableView_->setValueAsString(
5436  IterateTable::TARGET_TABLE, cmdRow, cmdCol);
5437 
5438  cmdCol = cmdTypeTableEdit.tableView_->findCol(
5439  IterateTable::commandTargetCols_.TargetsLinkGroupID_);
5440  cmdTypeTableEdit.tableView_->setValueAsString(
5441  cmdUID + "_Targets", cmdRow, cmdCol);
5442 
5443  // create row(s) for each target in target table with correct groupID
5444 
5445  for(const auto& target : command.targets_)
5446  {
5447  __SUP_COUT__ << target.table_ << " " << target.UID_ << __E__;
5448 
5449  // create target entry in target table in group
5450  tgtRow = targetTable.tableView_->addRow(
5451  author, true /*incrementUniqueData*/, "commandTarget");
5452  targetTable.tableView_->addRowToGroup(
5453  tgtRow, targetGroupIdCol, cmdUID + "_Targets");
5454 
5455  // set target table
5456  targetTable.tableView_->setValueAsString(
5457  target.table_, tgtRow, targetTableCol);
5458 
5459  // set target UID
5460  targetTable.tableView_->setValueAsString(
5461  target.UID_, tgtRow, targetUIDCol);
5462  }
5463  } // end target handling
5464 
5465  // add link at plan level to created UID
5466  planTable.tableView_->setValueAsString(
5467  cmdTypeTableEdit.tableName_, row, commandUidLink.first);
5468  planTable.tableView_->setValueAsString(
5469  cmdUID, row, commandUidLink.second);
5470 
5471  __SUP_COUT__ << "linked to uid = " << cmdUID << __E__;
5472 
5473  cmdTypeTableEdit.modified_ = true;
5474  } // done with command specifics
5475 
5476  } // end command loop
5477 
5478  // commands are created in the temporary tables
5479  // validate with init
5480 
5481  planTable.tableView_->print();
5482  planTable.tableView_->init(); // verify new table (throws runtime_errors)
5483 
5484  __SUP_COUT__ << "requestType tables:" << __E__;
5485 
5486  for(auto& modifiedConfig : commandTableToEditMap)
5487  {
5488  __SUP_COUTV__(modifiedConfig.second.modified_);
5489  modifiedConfig.second.tableView_->print();
5490  modifiedConfig.second.tableView_->init();
5491  }
5492 
5493  targetTable.tableView_->print();
5494  targetTable.tableView_->init(); // verify new table (throws runtime_errors)
5495 
5496  } // end try for plan
5497  catch(...)
5498  {
5499  __SUP_COUT__ << "Handling command table errors while saving. Erasing all newly "
5500  "created versions."
5501  << __E__;
5502 
5503  // erase all temporary tables if created here
5504 
5505  if(planTable.createdTemporaryVersion_) // if temporary version created here
5506  {
5507  __SUP_COUT__ << "Erasing temporary version " << planTable.tableName_ << "-v"
5508  << planTable.temporaryVersion_ << __E__;
5509  // erase with proper version management
5510  cfgMgr->eraseTemporaryVersion(planTable.tableName_,
5511  planTable.temporaryVersion_);
5512  }
5513 
5514  if(targetTable.createdTemporaryVersion_) // if temporary version created here
5515  {
5516  __SUP_COUT__ << "Erasing temporary version " << targetTable.tableName_ << "-v"
5517  << targetTable.temporaryVersion_ << __E__;
5518  // erase with proper version management
5519  cfgMgr->eraseTemporaryVersion(targetTable.tableName_,
5520  targetTable.temporaryVersion_);
5521  }
5522 
5523  for(auto& modifiedConfig : commandTableToEditMap)
5524  {
5525  if(modifiedConfig.second
5526  .createdTemporaryVersion_) // if temporary version created here
5527  {
5528  __SUP_COUT__ << "Erasing temporary version "
5529  << modifiedConfig.second.tableName_ << "-v"
5530  << modifiedConfig.second.temporaryVersion_ << __E__;
5531  // erase with proper version management
5532  cfgMgr->eraseTemporaryVersion(modifiedConfig.second.tableName_,
5533  modifiedConfig.second.temporaryVersion_);
5534  }
5535  }
5536 
5537  throw; // re-throw
5538  }
5539 
5540  // all edits are complete and tables verified
5541  // need to save all edits properly
5542  // if not modified, discard
5543 
5545  xmlOut,
5546  cfgMgr,
5547  planTable.tableName_,
5548  planTable.originalVersion_,
5549  true /*make temporary*/,
5550  planTable.table_,
5551  planTable.temporaryVersion_,
5552  true /*ignoreDuplicates*/); // save temporary version properly
5553 
5554  __SUP_COUT__ << "Final plan version is " << planTable.tableName_ << "-v"
5555  << finalVersion << __E__;
5556 
5558  xmlOut,
5559  cfgMgr,
5560  targetTable.tableName_,
5561  targetTable.originalVersion_,
5562  true /*make temporary*/,
5563  targetTable.table_,
5564  targetTable.temporaryVersion_,
5565  true /*ignoreDuplicates*/); // save temporary version properly
5566 
5567  __SUP_COUT__ << "Final target version is " << targetTable.tableName_ << "-v"
5568  << finalVersion << __E__;
5569 
5570  for(auto& modifiedConfig : commandTableToEditMap)
5571  {
5572  if(!modifiedConfig.second.modified_)
5573  {
5574  if(modifiedConfig.second
5575  .createdTemporaryVersion_) // if temporary version created here
5576  {
5577  __SUP_COUT__ << "Erasing unmodified temporary version "
5578  << modifiedConfig.second.tableName_ << "-v"
5579  << modifiedConfig.second.temporaryVersion_ << __E__;
5580  // erase with proper version management
5581  cfgMgr->eraseTemporaryVersion(modifiedConfig.second.tableName_,
5582  modifiedConfig.second.temporaryVersion_);
5583  }
5584  continue;
5585  }
5586 
5588  xmlOut,
5589  cfgMgr,
5590  modifiedConfig.second.tableName_,
5591  modifiedConfig.second.originalVersion_,
5592  true /*make temporary*/,
5593  modifiedConfig.second.table_,
5594  modifiedConfig.second.temporaryVersion_,
5595  true /*ignoreDuplicates*/); // save temporary version properly
5596 
5597  __SUP_COUT__ << "Final version is " << modifiedConfig.second.tableName_ << "-v"
5598  << finalVersion << __E__;
5599  }
5600 
5601  handleFillModifiedTablesXML(xmlOut, cfgMgr);
5602 } // end handleSavePlanCommandSequenceXML()
5603 catch(std::runtime_error& e)
5604 {
5605  __SUP_SS__ << "Error detected saving Iteration Plan!\n\n " << e.what() << __E__;
5606  __SUP_COUT_ERR__ << "\n" << ss.str() << __E__;
5607  xmlOut.addTextElementToData("Error", ss.str());
5608 }
5609 catch(...)
5610 {
5611  __SUP_SS__ << "Error detected saving Iteration Plan!\n\n " << __E__;
5612  try
5613  {
5614  throw;
5615  } //one more try to printout extra info
5616  catch(const std::exception& e)
5617  {
5618  ss << "Exception message: " << e.what();
5619  }
5620  catch(...)
5621  {
5622  }
5623  __SUP_COUT_ERR__ << "\n" << ss.str() << __E__;
5624  xmlOut.addTextElementToData("Error", ss.str());
5625 } // end handleSavePlanCommandSequenceXML() catch
5626 
5627 //==============================================================================
5637 void ConfigurationGUISupervisor::handleSaveTreeNodeEditXML(HttpXmlDocument& xmlOut,
5638  ConfigurationManagerRW* cfgMgr,
5639  const std::string& tableName,
5640  TableVersion version,
5641  const std::string& type,
5642  const std::string& uid,
5643  const std::string& colName,
5644  const std::string& newValue,
5645  const std::string& author)
5646 try
5647 {
5648  __SUP_COUT__ << "Editing table " << tableName << "(" << version << ") uid=" << uid
5649  << " type=" << type << __E__;
5650 
5651  // get the current table/version
5652  // check if the value is new
5653  // if new edit value (in a temporary version only)
5654 
5655  // get table and activate target version
5656  TableBase* table = cfgMgr->getTableByName(tableName);
5657  try
5658  {
5659  table->setActiveView(version);
5660  }
5661  catch(...)
5662  {
5663  if(version.isTemporaryVersion())
5664  throw; // if temporary, there is no hope to find lost version
5665 
5666  __SUP_COUT__ << "Failed to find stored version, so attempting to load version: "
5667  << version << __E__;
5668  cfgMgr->getVersionedTableByName(tableName, version);
5669  }
5670 
5671  __SUP_COUT__ << "Active version is " << table->getViewVersion() << __E__;
5672  __SUP_COUTTV__(table->getView().getComment());
5673 
5674  if(version != table->getViewVersion())
5675  {
5676  __SUP_SS__ << "Target table version (" << version
5677  << ") is not the currently active version (" << table->getViewVersion()
5678  << "). Try refreshing the tree." << __E__;
5679  __SS_THROW__;
5680  }
5681 
5682  unsigned int col = -1;
5683  if(type == "uid" || type == "delete-uid" || type == "tree-copy")
5684  col = table->getView().getColUID();
5685  else if(type == "node-comment")
5686  col = table->getView().findCol(TableViewColumnInfo::COL_NAME_COMMENT);
5687  else if(type == "link-UID" || type == "link-GroupID" || type == "value" ||
5688  type == "value-groupid" || type == "value-bool" || type == "value-bitmap")
5689  col = table->getView().findCol(colName);
5690  else if(type == "table" || type == "link-comment" || type == "table-newGroupRow" ||
5691  type == "table-newUIDRow" || type == "table-newRow")
5692  ; // column N/A
5693  else
5694  {
5695  __SUP_SS__ << "Impossible! Unrecognized edit type: " << type << __E__;
5696  __SS_THROW__;
5697  }
5698 
5699  // check if the comment value is new before making temporary version
5700  if(type == "table" || type == "link-comment")
5701  {
5702  // editing comment, so check if comment is different
5703  if(table->getView().isURIEncodedCommentTheSame(newValue))
5704  {
5705  __SUP_SS__ << "Comment '" << StringMacros::decodeURIComponent(newValue)
5706  << "' is the same as the current comment. No need to save change."
5707  << __E__;
5708  __SS_THROW__;
5709  }
5710  }
5711 
5712  // version handling:
5713  // always make a new temporary-version from source-version
5714  // edit temporary-version
5715  // if edit fails
5716  // delete temporary-version
5717  // else
5718  // return new temporary-version
5719  // if source-version was temporary
5720  // then delete source-version
5721 
5722  TableVersion temporaryVersion = table->createTemporaryView(version);
5723 
5724  __SUP_COUT__ << "Created temporary version " << temporaryVersion << __E__;
5725 
5726  TableView* cfgView = table->getTemporaryView(temporaryVersion);
5727  cfgView->init(); // prepare maps
5728 
5729  __SUP_COUTTV__(table->getView().getComment());
5730 
5731  // edit/verify new table (throws runtime_errors)
5732  try
5733  {
5734  // have view so edit it
5735  if(type == "table" || type == "link-comment")
5736  {
5737  // edit comment
5738  cfgView->setURIEncodedComment(newValue);
5739  }
5740  else if(type == "table-newRow" || type == "table-newUIDRow")
5741  {
5742  // add row
5743  unsigned int row = cfgView->addRow(
5744  author, true /*incrementUniqueData*/, newValue /*baseNameAutoUID*/);
5745 
5746  // if TableViewColumnInfo::COL_NAME_STATUS exists, set it to true
5747  try
5748  {
5749  col = cfgView->getColStatus();
5750  cfgView->setValueAsString("1", row, col);
5751  }
5752  catch(...)
5753  {
5754  } // if not, ignore
5755 
5756  // set UID value
5757  cfgView->setURIEncodedValue(newValue, row, cfgView->getColUID());
5758  }
5759  else if(type == "table-newGroupRow")
5760  {
5761  // get index value and group id value
5762  unsigned int csvIndex = newValue.find(',');
5763 
5764  std::string linkIndex = newValue.substr(0, csvIndex);
5765  std::string groupId = newValue.substr(csvIndex + 1);
5766 
5767  // get new row UID value from second part of string
5768  csvIndex = groupId.find(',');
5769  std::string newRowUID = groupId.substr(csvIndex + 1);
5770  groupId = groupId.substr(0, csvIndex);
5771 
5772  __SUP_COUT__ << "newValue " << linkIndex << "," << groupId << "," << newRowUID
5773  << __E__;
5774 
5775  // add row
5776  unsigned int row = cfgView->addRow(author,
5777  true /*incrementUniqueData*/,
5778  newRowUID /*baseNameAutoID*/,
5779  -1 /* rowToAdd */,
5780  linkIndex,
5781  groupId);
5782 
5783  // set UID value
5784  cfgView->setURIEncodedValue(newRowUID, row, cfgView->getColUID());
5785 
5786  // find groupId column from link index
5787  col = cfgView->getLinkGroupIDColumn(linkIndex);
5788 
5789  // set group id
5790  cfgView->setURIEncodedValue(groupId, row, col);
5791 
5792  // if TableViewColumnInfo::COL_NAME_STATUS exists, set it to true
5793  try
5794  {
5795  col = cfgView->getColStatus();
5796  cfgView->setValueAsString("1", row, col);
5797  }
5798  catch(...)
5799  {
5800  } // if not, ignore
5801  }
5802  else if(type == "delete-uid")
5803  {
5804  // delete row
5805  unsigned int row = cfgView->findRow(col, uid);
5806  cfgView->deleteRow(row);
5807  }
5808  else if(type == "tree-copy")
5809  {
5810  // recursively copy to depth
5811  __COUTV__(newValue);
5812  std::vector<std::string> paramArray =
5814  __COUTV__(StringMacros::vectorToString(paramArray));
5815 
5816  // accept either 2 params (count, depth) or 3 params (count, depth, customUID).
5817  // A non-empty customUID is honored only when copying a single instance.
5818  if(paramArray.size() < 2 || paramArray.size() > 3)
5819  {
5820  __SS__ << "Illegal parameters for tree copy request: must be number of "
5821  "copy instances & depth of copy (optionally followed by a "
5822  "custom UID for the new record)."
5823  << __E__;
5824  __SS_THROW__;
5825  }
5826 
5827  unsigned int row = cfgView->findRow(col, uid);
5828  __COUTV__(uid);
5829  __COUTV__(row);
5830  unsigned int numberOfInstances = atoi(paramArray[0].c_str());
5831  unsigned int depth = atoi(paramArray[1].c_str());
5832  std::string customUID = paramArray.size() == 3
5833  ? StringMacros::decodeURIComponent(paramArray[2])
5834  : std::string("");
5835  __COUTV__(depth);
5836  __COUTV__(numberOfInstances);
5837  __COUTV__(customUID);
5838  if(numberOfInstances > 1000)
5839  {
5840  __SS__ << "Illegal parameters - the maximum number of copy instances is "
5841  "1000. Number of instances provided was "
5842  << numberOfInstances << __E__;
5843  __SS_THROW__;
5844  }
5845  if(!customUID.empty() && numberOfInstances != 1)
5846  {
5847  __SS__ << "A custom UID can only be supplied when copying a single "
5848  "instance. Requested instances: "
5849  << numberOfInstances << __E__;
5850  __SS_THROW__;
5851  }
5852 
5853  std::map<std::string /*modified table*/, TableVersion /* modified version */>
5854  modifiedTablesMap = cfgMgr->getActiveVersions(); // handling copied from
5855  // ConfigurationGUISupervisor::handleFillModifiedTablesXML()
5856 
5857  unsigned int rowsBeforeCopy = cfgView->getNumberOfRows();
5858  ConfigurationSupervisorBase::recursiveCopyTreeUIDNode(xmlOut,
5859  cfgMgr,
5860  modifiedTablesMap,
5861  depth,
5862  depth,
5863  numberOfInstances,
5864  cfgView,
5865  uid);
5866 
5867  // if a custom UID was provided, rename the newly-copied row
5868  if(!customUID.empty() && cfgView->getNumberOfRows() > rowsBeforeCopy)
5869  {
5870  unsigned int uidCol = cfgView->getColUID();
5871  unsigned int newRow = cfgView->getNumberOfRows() - 1;
5872  cfgView->setValueAsString(customUID, newRow, uidCol);
5873  xmlOut.addTextElementToData("CopiedRecordUID", customUID);
5874  }
5875  }
5876  else if(type == "uid" || type == "value" || type == "value-groupid" ||
5877  type == "value-bool" || type == "value-bitmap" || type == "node-comment")
5878  {
5879  unsigned int row = cfgView->findRow(cfgView->getColUID(), uid);
5880  if(!cfgView->setURIEncodedValue(newValue, row, col, author))
5881  {
5882  // no change! so discard
5883  __SUP_SS__ << "Value '" << newValue
5884  << "' is the same as the current value. No need to save "
5885  "change to tree node."
5886  << __E__;
5887  __SS_THROW__;
5888  }
5889  }
5890  else if(type == "link-UID" || type == "link-GroupID")
5891  {
5892  bool isGroup;
5893  std::pair<unsigned int /*link col*/, unsigned int /*link id col*/> linkPair;
5894  if(!cfgView->getChildLink(col, isGroup, linkPair))
5895  {
5896  // not a link ?!
5897  __SUP_SS__ << "Col '" << colName << "' is not a link column." << __E__;
5898  __SS_THROW__;
5899  }
5900 
5901  __SUP_COUT__ << "linkPair " << linkPair.first << "," << linkPair.second
5902  << __E__;
5903 
5904  std::string linkIndex = cfgView->getColumnInfo(col).getChildLinkIndex();
5905 
5906  __SUP_COUT__ << "linkIndex " << linkIndex << __E__;
5907 
5908  // find table value and id value
5909  unsigned int csvIndexStart = 0, csvIndex = newValue.find(',');
5910 
5911  std::string newTable = newValue.substr(csvIndexStart, csvIndex);
5912  csvIndexStart = csvIndex + 1;
5913  csvIndex = newValue.find(',', csvIndexStart);
5914  std::string newLinkId = newValue.substr(
5915  csvIndexStart,
5916  csvIndex -
5917  csvIndexStart); // if no more commas will take the rest of string
5918 
5919  __SUP_COUT__ << "newValue " << newTable << "," << newLinkId << __E__;
5920 
5921  // change target table in two parts
5922  unsigned int row = cfgView->findRow(cfgView->getColUID(), uid);
5923  bool changed = false;
5924  bool needSecondaryChange = (type == "link-GroupID");
5925 
5926  if(!cfgView->setURIEncodedValue(newTable, row, linkPair.first, author))
5927  {
5928  // no change
5929  __SUP_COUT__ << "Value '" << newTable
5930  << "' is the same as the current value." << __E__;
5931  }
5932  else
5933  {
5934  changed = true;
5935  // do NOT need secondary change for UID
5936  }
5937 
5938  std::string originalValue = cfgView->getValueAsString(row, linkPair.second);
5939  if(!cfgView->setURIEncodedValue(newLinkId, row, linkPair.second, author))
5940  {
5941  // no change
5942  __SUP_COUT__ << "Value '" << newLinkId
5943  << "' is the same as the current value." << __E__;
5944  }
5945  else
5946  {
5947  if(!changed)
5948  needSecondaryChange =
5949  true; // if table was unchanged, then need secondary change for
5950  // UID (groupID is already assumed needed)
5951  changed = true;
5952  }
5953 
5954  if(needSecondaryChange) // do secondary changes to child table target
5955  {
5956  bool secondaryChanged = false;
5957  bool defaultIsInGroup =
5958  false; // use to indicate if a recent new member was created
5959 
5960  // first close out main target table
5961  if(!changed) // if no changes throw out new version
5962  {
5963  __SUP_COUT__ << "No changes to primary view. Erasing temporary table."
5964  << __E__;
5965  table->eraseView(temporaryVersion);
5966  }
5967  else // if changes, save it
5968  {
5969  try
5970  {
5971  cfgView->init(); // verify new table (throws runtime_errors)
5972 
5974  xmlOut,
5975  cfgMgr,
5976  tableName,
5977  version,
5978  true /*make temporary*/,
5979  table,
5980  temporaryVersion,
5981  true /*ignoreDuplicates*/); // save
5982  // temporary
5983  // version
5984  // properly
5985  }
5986  catch(std::runtime_error&
5987  e) // erase temporary view before re-throwing error
5988  {
5989  __SUP_COUT__ << "Caught error while editing main table. Erasing "
5990  "temporary version."
5991  << __E__;
5992  table->eraseView(temporaryVersion);
5993  changed = false; // undo changed bool
5994 
5995  // send warning so that, secondary table can still be changed
5996  xmlOut.addTextElementToData(
5997  "Warning",
5998  "Error saving primary tree node! " + std::string(e.what()));
5999  }
6000  }
6001 
6002  // now, onto linked table
6003 
6004  // get the current linked table/version
6005  // check if the value is new
6006  // if new edit value (in a temporary version only)
6007 
6008  __SUP_COUTV__(newValue);
6009  csvIndexStart = csvIndex + 1;
6010  csvIndex = newValue.find(',', csvIndexStart);
6011  version = TableVersion(newValue.substr(
6012  csvIndexStart, csvIndex - csvIndexStart)); // if no more commas will
6013  // take the rest of string
6014 
6015  if(newTable == TableViewColumnInfo::DATATYPE_LINK_DEFAULT)
6016  {
6017  // done, since init was already tested
6018  // the result should be purposely DISCONNECTED link
6019  return;
6020  }
6021 
6022  // get table and activate target version
6023  table = cfgMgr->getTableByName(newTable);
6024  try
6025  {
6026  table->setActiveView(version);
6027  }
6028  catch(...)
6029  {
6030  if(version.isTemporaryVersion())
6031  throw; // if temporary, there is no hope to find lost version
6032 
6033  __SUP_COUT__ << "Failed to find stored version, so attempting to "
6034  "load version: "
6035  << newTable << " v" << version << __E__;
6036  cfgMgr->getVersionedTableByName(newTable, version);
6037  }
6038 
6039  __SUP_COUT__ << newTable << " active version is "
6040  << table->getViewVersion() << __E__;
6041 
6042  if(version != table->getViewVersion())
6043  {
6044  __SUP_SS__;
6045  if(version.isMockupVersion())
6046  ss << "Target table '" << newTable
6047  << "' is likely not a member of the current table group "
6048  << "since the mock-up version was not successfully loaded. "
6049  << "\n\n"
6050  <<
6051  // same as ConfigurationGUI.html L:9833
6052  (std::string("") +
6053  "To add a table to a group, click the group name to go to "
6054  "the " +
6055  "group view, then click 'Add/Remove/Modify Member Tables.' "
6056  "You " +
6057  "can then add or remove tables and save the new group." +
6058  "\n\n" +
6059  "OR!!! Click the following button to add the table '" +
6060  newTable +
6061  "' to the currently active Configuration Group: " +
6062  "<input type='button' style='color:black !important;' " +
6063  "title='Click to add table to the active Configuration "
6064  "Group' " +
6065  "onclick='addTableToConfigurationGroup(\"" + newTable +
6066  "\"); Debug.closeErrorPop();event.stopPropagation();' "
6067  "value='Add Table'>" +
6068  "</input>")
6069  << __E__;
6070  else
6071  ss << "Target table version (" << version
6072  << ") is not the currently active version ("
6073  << table->getViewVersion() << "). Try refreshing the tree."
6074  << __E__;
6075  __SS_THROW__;
6076  }
6077 
6078  // create temporary version for editing
6079  temporaryVersion = table->createTemporaryView(version);
6080 
6081  __SUP_COUT__ << "Created temporary version " << temporaryVersion << __E__;
6082 
6083  cfgView = table->getTemporaryView(temporaryVersion);
6084 
6085  cfgView->init(); // prepare column lookup map
6086 
6087  if(type == "link-UID")
6088  {
6089  // handle UID links slightly differently
6090  // when editing link-UID,.. if specified name does not exist in child
6091  // table, then change the UID in the child table (rename target
6092  // record).
6093  // Otherwise, it is impossible to rename unique links targets in the
6094  // tree-view GUI.
6095 
6096  col = cfgView->getColUID();
6097  __SUP_COUT__ << "target col " << col << __E__;
6098 
6099  unsigned int row = -1;
6100  try
6101  {
6102  row = cfgView->findRow(col, newLinkId);
6103  }
6104  catch(...) // ignore not found error
6105  {
6106  }
6107  if(row == (unsigned int)-1) // if row not found then add a row
6108  {
6109  __SUP_COUT__ << "New link UID '" << newLinkId
6110  << "' was not found, so attempting to change UID of "
6111  "target record '"
6112  << originalValue << "'" << __E__;
6113  try
6114  {
6115  row = cfgView->findRow(col, originalValue);
6116  if(cfgView->setURIEncodedValue(newLinkId, row, col, author))
6117  {
6118  secondaryChanged = true;
6119  __SUP_COUT__ << "Original target record '"
6120  << originalValue << "' was changed to '"
6121  << newLinkId << "'" << __E__;
6122  }
6123  }
6124  catch(...) // ignore not found error
6125  {
6126  __SUP_COUT__ << "Original target record '" << originalValue
6127  << "' not found." << __E__;
6128  }
6129  }
6130  }
6131  else if(type == "link-GroupID")
6132  {
6133  // handle groupID links slightly differently
6134  // have to look at changing link table too!
6135  // if group ID, set all in member list to be members of group
6136 
6137  col = cfgView->getLinkGroupIDColumn(linkIndex);
6138 
6139  __SUP_COUT__ << "target col " << col << __E__;
6140 
6141  // extract vector of members to be
6142  std::vector<std::string> memberUIDs;
6143  do
6144  {
6145  csvIndexStart = csvIndex + 1;
6146  csvIndex = newValue.find(',', csvIndexStart);
6147  memberUIDs.push_back(
6148  newValue.substr(csvIndexStart, csvIndex - csvIndexStart));
6149  __SUP_COUT__ << "memberUIDs: " << memberUIDs.back() << __E__;
6150  } while(csvIndex !=
6151  (unsigned int)std::string::npos); // no more commas
6152 
6153  // for each row,
6154  // check if should be in group
6155  // if should be but is not
6156  // add to group, CHANGE
6157  // if should not be but is
6158  // remove from group, CHANGE
6159  //
6160 
6161  std::string targetUID;
6162  bool shouldBeInGroup;
6163  bool isInGroup;
6164 
6165  for(unsigned int row = 0; row < cfgView->getNumberOfRows(); ++row)
6166  {
6167  targetUID = cfgView->getDataView()[row][cfgView->getColUID()];
6168  __SUP_COUT__ << "targetUID: " << targetUID << __E__;
6169 
6170  shouldBeInGroup = false;
6171  for(unsigned int i = 0; i < memberUIDs.size(); ++i)
6172  if(targetUID == memberUIDs[i])
6173  {
6174  // found in member uid list
6175  shouldBeInGroup = true;
6176  break;
6177  }
6178 
6179  isInGroup = cfgView->isEntryInGroup(row, linkIndex, newLinkId);
6180 
6181  // if should be but is not
6182  if(shouldBeInGroup && !isInGroup)
6183  {
6184  __SUP_COUT__ << "Changed to YES: " << row << __E__;
6185  secondaryChanged = true;
6186 
6187  cfgView->addRowToGroup(row, col, newLinkId);
6188 
6189  } // if should not be but is
6190  else if(!shouldBeInGroup && isInGroup)
6191  {
6192  __SUP_COUT__ << "Changed to NO: " << row << __E__;
6193  secondaryChanged = true;
6194 
6195  cfgView->removeRowFromGroup(row, col, newLinkId);
6196  }
6197  else if(targetUID ==
6198  cfgView
6199  ->getDefaultRowValues()[cfgView->getColUID()] &&
6200  isInGroup)
6201  {
6202  // use to indicate if a recent new member was created
6203  defaultIsInGroup = true;
6204  }
6205  }
6206  } // end (type == "link-GroupID")
6207 
6208  // first close out main target table
6209  if(!secondaryChanged) // if no changes throw out new version
6210  {
6211  __SUP_COUT__
6212  << "No changes to secondary view. Erasing temporary table."
6213  << __E__;
6214  table->eraseView(temporaryVersion);
6215  }
6216  else // if changes, save it
6217  {
6218  try
6219  {
6220  cfgView->init(); // verify new table (throws runtime_errors)
6221 
6223  xmlOut,
6224  cfgMgr,
6225  newTable,
6226  version,
6227  true /*make temporary*/,
6228  table,
6229  temporaryVersion,
6230  true /*ignoreDuplicates*/); // save
6231  // temporary
6232  // version
6233  // properly
6234  }
6235  catch(std::runtime_error&
6236  e) // erase temporary view before re-throwing error
6237  {
6238  __SUP_COUT__ << "Caught error while editing secondary table. "
6239  "Erasing temporary version."
6240  << __E__;
6241  table->eraseView(temporaryVersion);
6242  secondaryChanged = false; // undo changed bool
6243 
6244  // send warning so that, secondary table can still be changed
6245  xmlOut.addTextElementToData(
6246  "Warning",
6247  "Error saving secondary tree node! " + std::string(e.what()));
6248  }
6249  }
6250 
6251  // Block error message if default is in group, assume new member was just
6252  // created. Blocked because its hard to detect if changes were recently
6253  // made (one idea: to check if all other values are defaults, to assume it
6254  // was just created)
6255  if(0 && !changed && !secondaryChanged && !defaultIsInGroup)
6256  {
6257  __SUP_SS__ << "Link to table '" << newTable << "', linkID '"
6258  << newLinkId
6259  << "', and selected group members are the same as the "
6260  "current value. "
6261  << "No need to save changes to tree." << __E__;
6262  __SS_THROW__;
6263  }
6264 
6265  return; // exit since table inits were already tested
6266  }
6267  else if(0 && !changed) // '0 &&' to block error message because sometimes
6268  // things get setup twice depending on the path of the
6269  // user (e.g. when editing links in tree-view)
6270  { // '0 &&' to block error message also because versions are temporary at
6271  // this point anyway, might as well abuse temporary versions
6272  __SUP_SS__ << "Link to table '" << newTable << "' and linkID '"
6273  << newLinkId
6274  << "' are the same as the current values. No need to save "
6275  "change to tree node."
6276  << __E__;
6277  __SS_THROW__;
6278  }
6279  }
6280 
6281  cfgView->init(); // verify new table (throws runtime_errors)
6282  }
6283  catch(...) // erase temporary view before re-throwing error
6284  {
6285  __SUP_COUT__ << "Caught error while editing. Erasing temporary version." << __E__;
6286  table->eraseView(temporaryVersion);
6287  throw;
6288  }
6289 
6291  xmlOut,
6292  cfgMgr,
6293  tableName,
6294  version,
6295  true /*make temporary*/,
6296  table,
6297  temporaryVersion,
6298  true /*ignoreDuplicates*/); // save temporary version properly
6299 } //end handleSaveTreeNodeEditXML()
6300 catch(std::runtime_error& e)
6301 {
6302  __SUP_SS__ << "Error saving tree node! " << e.what() << __E__;
6303  __SUP_COUT_ERR__ << "\n" << ss.str() << __E__;
6304  xmlOut.addTextElementToData("Error", ss.str());
6305 }
6306 catch(...)
6307 {
6308  __SUP_SS__ << "Unknown Error saving tree node! " << __E__;
6309  try
6310  {
6311  throw;
6312  } //one more try to printout extra info
6313  catch(const std::exception& e)
6314  {
6315  ss << "Exception message: " << e.what();
6316  }
6317  catch(...)
6318  {
6319  }
6320  __SUP_COUT_ERR__ << "\n" << ss.str() << __E__;
6321  xmlOut.addTextElementToData("Error", ss.str());
6322 } //end handleSaveTreeNodeEditXML() catch
6323 
6324 //==============================================================================
6365 void ConfigurationGUISupervisor::handleGetTableXML(HttpXmlDocument& xmlOut,
6366  ConfigurationManagerRW* cfgMgr,
6367  const std::string& tableName,
6368  TableVersion version,
6369  bool allowIllegalColumns /* = false */,
6370  bool getRawData /* = false */,
6371  bool descriptionOnly /* = false */)
6372 try
6373 {
6374  xercesc::DOMElement *parentEl, *subparentEl;
6375 
6376  std::string accumulatedErrors = "";
6377 
6378  if(allowIllegalColumns)
6379  xmlOut.addTextElementToData("allowIllegalColumns", "1");
6380 
6381  const std::map<std::string, TableInfo>& allTableInfo = cfgMgr->getAllTableInfo(
6382  allowIllegalColumns /* if allowIllegalColumns, then also refresh */,
6383  allowIllegalColumns ? &accumulatedErrors : 0,
6384  tableName); // filter errors by tableName
6385 
6386  TableBase* table = cfgMgr->getTableByName(tableName);
6387 
6388  if(!getRawData)
6389  {
6390  // send all table names along with
6391  // and check for specific version
6392  xmlOut.addTextElementToData("ExistingTableNames",
6393  TableViewColumnInfo::DATATYPE_LINK_DEFAULT);
6394  for(auto& configPair : allTableInfo)
6395  {
6396  xmlOut.addTextElementToData("ExistingTableNames", configPair.first);
6397  if(configPair.first == tableName && // check that version exists
6398  configPair.second.versions_.find(version) ==
6399  configPair.second.versions_.end())
6400  {
6401  __SUP_COUT__ << "Version not found, so using mockup." << __E__;
6402  version = TableVersion(); // use INVALID
6403  }
6404  }
6405  }
6406 
6407  xmlOut.addTextElementToData("TableName", tableName); // table name
6408  xmlOut.addTextElementToData("TableDescription",
6409  table->getTableDescription()); // table name
6410 
6411  if(descriptionOnly)
6412  return;
6413 
6414  // existing table versions
6415  if(!getRawData)
6416  {
6417  // get version aliases for translation
6418  std::map<
6419  std::string /*table name*/,
6420  std::map<std::string /*version alias*/, TableVersion /*aliased version*/>>
6421  versionAliases;
6422  try
6423  {
6424  // use whatever backbone is currently active
6425  versionAliases = cfgMgr->getVersionAliases();
6426  for(const auto& aliases : versionAliases)
6427  for(const auto& alias : aliases.second)
6428  __SUP_COUTT__ << "ALIAS: " << aliases.first << " " << alias.first
6429  << " ==> " << alias.second << __E__;
6430  }
6431  catch(const std::runtime_error& e)
6432  {
6433  __SUP_COUT__ << "Could not get backbone information for version aliases: "
6434  << e.what() << __E__;
6435  }
6436 
6437  auto tableIterator = versionAliases.find(tableName);
6438 
6439  parentEl = xmlOut.addTextElementToData("TableVersions", "");
6440  //add lo and hi spans, instead of each individual value
6441  TableVersion lo, hi;
6442  for(const TableVersion& v : allTableInfo.at(tableName).versions_)
6443  {
6444  //Steps:
6445  // 1. check for version aliases
6446  // 2. if version aliases, leave as standalone version
6447  // else stack spans of versions for faster xml transfer
6448  std::vector<std::string> aliases;
6449  if(tableIterator != versionAliases.end())
6450  {
6451  // check if this version has one or many aliases
6452  for(const auto& aliasPair : tableIterator->second)
6453  {
6454  if(v == aliasPair.second)
6455  {
6456  __SUP_COUT__ << "Found Alias " << aliasPair.second << " --> "
6457  << aliasPair.first << __E__;
6458  aliases.push_back(aliasPair.first);
6459  }
6460  }
6461  }
6462  //now have version aliases or not
6463 
6464  if(aliases.size()) //keep versions with aliases standalone
6465  {
6466  __SUP_COUT__ << "Handling version w/aliases" << __E__;
6467  }
6468  else if(lo.version() ==
6469  TableVersion::INVALID) //establish start of potential span
6470  {
6471  hi = lo = v;
6472  continue;
6473  }
6474  else if(hi.version() + 1 == v.version()) //span is growing
6475  {
6476  hi = v;
6477  continue;
6478  }
6479  //else jump by more than one, so close out span
6480 
6481  if(lo.version() != TableVersion::INVALID)
6482  {
6483  if(lo == hi) //single value
6484  xmlOut.addTextElementToParent("Version", lo.toString(), parentEl);
6485  else //span
6486  xmlOut.addTextElementToParent(
6487  "Version", "_" + lo.toString() + "_" + hi.toString(), parentEl);
6488  }
6489  hi = lo = v.version();
6490 
6491  if(versionAliases.size()) //keep versions with aliases standalone
6492  {
6493  subparentEl =
6494  xmlOut.addTextElementToParent("Version", v.toString(), parentEl);
6495  for(const auto& alias : aliases)
6496  xmlOut.addTextElementToParent("VersionAlias", alias, subparentEl);
6497  hi = lo = TableVersion::INVALID; //invalidate for fresh start
6498  } //end version alias handling
6499 
6500  } //end version loop
6501 
6502  if(lo.version() != TableVersion::INVALID) //check if last one to do!
6503  {
6504  if(lo == hi) //single value
6505  xmlOut.addTextElementToParent("Version", lo.toString(), parentEl);
6506  else //span
6507  xmlOut.addTextElementToParent(
6508  "Version", "_" + lo.toString() + "_" + hi.toString(), parentEl);
6509  }
6510  } //end existing table version handling
6511 
6512  // table columns and then rows (from table view)
6513 
6514  // get view pointer
6515  TableView* tableViewPtr;
6516  if(version.isInvalid()) // use mock-up
6517  {
6518  tableViewPtr = table->getMockupViewP();
6519  }
6520  else // use view version
6521  {
6522  try
6523  {
6524  // locally accumulate 'manageable' errors getting the version to avoid
6525  // reverting to mockup
6526  std::string localAccumulatedErrors = "";
6527  tableViewPtr =
6528  cfgMgr
6529  ->getVersionedTableByName(tableName,
6530  version,
6531  allowIllegalColumns /*looseColumnMatching*/,
6532  &localAccumulatedErrors,
6533  getRawData)
6534  ->getViewP();
6535 
6536  if(getRawData)
6537  {
6538  xmlOut.addTextElementToData("TableRawData",
6539  tableViewPtr->getSourceRawData());
6540 
6541  const std::set<std::string>& srcColNames =
6542  tableViewPtr->getSourceColumnNames();
6543  for(auto& srcColName : srcColNames)
6544  xmlOut.addTextElementToData("ColumnHeader", srcColName);
6545 
6546  if(!version.isTemporaryVersion())
6547  {
6548  // if version is temporary, view is already ok
6549  table->eraseView(
6550  version); // clear so that the next get will fill the table
6551  tableViewPtr = cfgMgr
6553  tableName,
6554  version,
6555  allowIllegalColumns /*looseColumnMatching*/,
6556  &localAccumulatedErrors,
6557  false /* getRawData */)
6558  ->getViewP();
6559  }
6560  } // end rawData handling
6561 
6562  if(localAccumulatedErrors != "")
6563  xmlOut.addTextElementToData("Error", localAccumulatedErrors);
6564  }
6565  catch(std::runtime_error& e) // default to mock-up for fail-safe in GUI editor
6566  {
6567  __SUP_SS__ << "Failed to get table " << tableName << " version " << version
6568  << "... defaulting to mock-up! " << __E__;
6569  ss << "\n\n...Here is why it failed:\n\n" << e.what() << __E__;
6570 
6571  __SUP_COUT_ERR__ << "\n" << ss.str();
6572  version = TableVersion();
6573  tableViewPtr = table->getMockupViewP();
6574 
6575  xmlOut.addTextElementToData("Error", "Error getting view! " + ss.str());
6576  }
6577  catch(...) // default to mock-up for fail-safe in GUI editor
6578  {
6579  __SUP_SS__ << "Failed to get table " << tableName << " version: " << version
6580  << "... defaulting to mock-up! "
6581  << "(You may want to try again to see what was partially loaded "
6582  "into cache before failure. "
6583  << "If you think, the failure is due to a column name change, "
6584  << "you can also try to Copy the failing view to the new column "
6585  "names using "
6586  << "'Copy and Move' functionality.)" << __E__;
6587  try
6588  {
6589  throw;
6590  } //one more try to printout extra info
6591  catch(const std::exception& e)
6592  {
6593  ss << "Exception message: " << e.what();
6594  }
6595  catch(...)
6596  {
6597  }
6598 
6599  __SUP_COUT_ERR__ << "\n" << ss.str();
6600  version = TableVersion();
6601  tableViewPtr = table->getMockupViewP();
6602 
6603  xmlOut.addTextElementToData("Error", "Error getting view! " + ss.str());
6604  }
6605  }
6606  xmlOut.addTextElementToData("TableVersion", version.toString()); // table version
6607 
6608  if(getRawData)
6609  return; // no need to go further for rawData handling
6610 
6611  // get 'columns' of view
6612  xercesc::DOMElement* choicesParentEl;
6613  parentEl = xmlOut.addTextElementToData("CurrentVersionColumnHeaders", "");
6614 
6615  std::vector<TableViewColumnInfo> colInfo = tableViewPtr->getColumnsInfo();
6616 
6617  for(int i = 0; i < (int)colInfo.size(); ++i) // column headers and types
6618  {
6619  xmlOut.addTextElementToParent("ColumnHeader", colInfo[i].getName(), parentEl);
6620  xmlOut.addTextElementToParent("ColumnType", colInfo[i].getType(), parentEl);
6621  xmlOut.addTextElementToParent(
6622  "ColumnDataType", colInfo[i].getDataType(), parentEl);
6623 
6624  // NOTE!! ColumnDefaultValue defaults may be unique to this version of the table,
6625  // whereas DefaultRowValue are the defaults for the mockup
6626  xmlOut.addTextElementToParent(
6627  "ColumnDefaultValue", colInfo[i].getDefaultValue(), parentEl);
6628 
6629  choicesParentEl = xmlOut.addTextElementToParent("ColumnChoices", "", parentEl);
6630  // add data choices if necessary
6631  if(colInfo[i].getType() == TableViewColumnInfo::TYPE_FIXED_CHOICE_DATA ||
6632  colInfo[i].getType() == TableViewColumnInfo::TYPE_BITMAP_DATA ||
6633  colInfo[i].isChildLink())
6634  {
6635  for(auto& choice : colInfo[i].getDataChoices())
6636  xmlOut.addTextElementToParent("ColumnChoice", choice, choicesParentEl);
6637  }
6638 
6639  xmlOut.addTextElementToParent(
6640  "ColumnMinValue", colInfo[i].getMinValue(), parentEl);
6641  xmlOut.addTextElementToParent(
6642  "ColumnMaxValue", colInfo[i].getMaxValue(), parentEl);
6643  }
6644 
6645  // verify mockup columns after columns are posted to xmlOut
6646  try
6647  {
6648  if(version.isInvalid())
6649  tableViewPtr->init();
6650  }
6651  catch(std::runtime_error& e)
6652  {
6653  // append accumulated errors, because they may be most useful
6654  __THROW__(e.what() + std::string("\n\n") + accumulatedErrors);
6655  }
6656  catch(...)
6657  {
6658  throw;
6659  }
6660 
6661  parentEl = xmlOut.addTextElementToData("CurrentVersionRows", "");
6662 
6663  int numRows = (int)tableViewPtr->getNumberOfRows();
6664  int numCols = (int)tableViewPtr->getNumberOfColumns();
6665 
6666  for(int c = 0; c < numCols; ++c)
6667  {
6668  std::string csvStr;
6669  csvStr.reserve(numRows * 20);
6670  for(int r = 0; r < numRows; ++r)
6671  {
6672  if(r > 0)
6673  csvStr += ",";
6674  if(colInfo[c].getDataType() == TableViewColumnInfo::DATATYPE_TIME)
6675  {
6676  std::string timeAsString;
6677  tableViewPtr->getValue(timeAsString, r, c);
6678  csvStr += StringMacros::encodeURIComponent(timeAsString);
6679  }
6680  else
6681  csvStr +=
6682  StringMacros::encodeURIComponent(tableViewPtr->getDataView()[r][c]);
6683  }
6684  xmlOut.addTextElementToParent("ColCSV", csvStr, parentEl);
6685  }
6686 
6687  // add "other" fields associated with configView
6688  xmlOut.addTextElementToData("TableComment", tableViewPtr->getComment());
6689  xmlOut.addTextElementToData("TableAuthor", tableViewPtr->getAuthor());
6690  xmlOut.addTextElementToData("TableCreationTime",
6691  std::to_string(tableViewPtr->getCreationTime()));
6692  xmlOut.addTextElementToData("TableLastAccessTime",
6693  std::to_string(tableViewPtr->getLastAccessTime()));
6694 
6695  // add to xml the default row values
6696  // NOTE!! ColumnDefaultValue defaults may be unique to this version of the table,
6697  // whereas DefaultRowValue are the defaults for the mockup
6698  std::vector<std::string> defaultRowValues =
6699  table->getMockupViewP()->getDefaultRowValues();
6700  // don't give author and time.. force default author, let JS fill time
6701  for(unsigned int c = 0; c < defaultRowValues.size() - 2; ++c)
6702  {
6703  xmlOut.addTextElementToData("DefaultRowValue", defaultRowValues[c]);
6704  }
6705 
6706  const std::set<std::string> srcColNames = tableViewPtr->getSourceColumnNames();
6707 
6708  if(accumulatedErrors != "") // add accumulated errors to xmlOut
6709  {
6710  __SUP_SS__ << (std::string("Column errors were allowed for this request, so "
6711  "perhaps you can ignore this, ") +
6712  "but please note the following warnings:\n" + accumulatedErrors)
6713  << __E__;
6714  __SUP_COUT_ERR__ << ss.str();
6715  xmlOut.addTextElementToData("TableWarnings", ss.str());
6716  }
6717  else if(!version.isTemporaryVersion() && // not temporary (these are not filled from
6718  // interface source)
6719  (srcColNames.size() != tableViewPtr->getNumberOfColumns() ||
6720  tableViewPtr->getSourceColumnMismatch() !=
6721  0)) // check for column size mismatch
6722  {
6723  __SUP_SS__ << "\n\nThere were warnings found when loading the table " << tableName
6724  << ":v" << version << ". Please see the details below:\n\n"
6725  << tableViewPtr->getMismatchColumnInfo();
6726 
6727  __SUP_COUT__ << "\n" << ss.str();
6728  xmlOut.addTextElementToData("TableWarnings", ss.str());
6729  }
6730 
6731 } // end handleGetTableXML()
6732 catch(std::runtime_error& e)
6733 {
6734  __SUP_SS__ << "Error getting table view!\n\n " << e.what() << __E__;
6735  __SUP_COUT_ERR__ << ss.str();
6736  xmlOut.addTextElementToData("Error", ss.str());
6737 }
6738 catch(...)
6739 {
6740  __SUP_SS__ << "Error getting table view!\n\n " << __E__;
6741  try
6742  {
6743  throw;
6744  } //one more try to printout extra info
6745  catch(const std::exception& e)
6746  {
6747  ss << "Exception message: " << e.what();
6748  }
6749  catch(...)
6750  {
6751  }
6752  __SUP_COUT_ERR__ << ss.str();
6753  xmlOut.addTextElementToData("Error", ss.str());
6754 } // end handleGetTableXML() catch
6755 
6756 //==============================================================================
6763 ConfigurationManagerRW* ConfigurationGUISupervisor::refreshUserSession(
6764  std::string username, bool refresh)
6765 {
6766  uint64_t sessionIndex =
6767  0; // make session by username for now! (may never want to change back)
6768 
6769  std::stringstream ssMapKey;
6770  ssMapKey << username << ":" << sessionIndex;
6771  std::string mapKey = ssMapKey.str();
6772  __SUP_COUTT__ << "Using Config Session " << mapKey
6773  << " ... Total Session Count: " << userConfigurationManagers_.size()
6774  << " refresh=" << refresh << __E__;
6775 
6776  time_t now = time(0);
6777 
6778  // create new table mgr if not one for active session index
6779 
6780  if(TTEST(1))
6781  {
6782  for(auto& pair : userConfigurationManagers_)
6783  __SUP_COUTTV__(pair.first);
6784  }
6785 
6786  const std::string preLoadCfgMgrName = ":0";
6787  if(userConfigurationManagers_.size() == 1 &&
6788  userConfigurationManagers_.find(preLoadCfgMgrName) !=
6789  userConfigurationManagers_.end())
6790  {
6791  __SUP_COUT__ << "Using pre-loaded Configuration Manager. time=" << time(0) << " "
6792  << clock() << " Setting author from "
6793  << userConfigurationManagers_.at(preLoadCfgMgrName)->getUsername()
6794  << " to " << username << __E__;
6795  userConfigurationManagers_[mapKey] =
6796  userConfigurationManagers_.at(preLoadCfgMgrName);
6797  userLastUseTime_[mapKey] = userLastUseTime_.at(preLoadCfgMgrName);
6798  //also set author!
6799  userConfigurationManagers_.at(mapKey)->setUsername(username);
6800  }
6801 
6802  if(userConfigurationManagers_.find(mapKey) == userConfigurationManagers_.end())
6803  {
6804  __SUP_COUT__ << "Creating new Configuration Manager. time=" << time(0) << " "
6805  << clock() << __E__;
6806  userConfigurationManagers_[mapKey] = new ConfigurationManagerRW(username);
6807 
6808  // update table info for each new configuration manager
6809  // IMPORTANTLY this also fills all configuration manager pointers with instances,
6810  // so we are not dealing with changing pointers later on
6811  userConfigurationManagers_[mapKey]->getAllTableInfo(
6812  true /* refresh */, // load empty instance of everything important
6813  0 /* accumulatedWarnings */,
6814  "" /* errorFilterName */,
6815  true /* getGroupKeys */,
6816  false /* getGroupInfo */,
6817  true /* initializeActiveGroups */);
6818  }
6819  else if(userLastUseTime_.find(mapKey) == userLastUseTime_.end())
6820  {
6821  __SUP_SS__ << "Fatal error managing user sessions! Check the logs for "
6822  "Configuration Interface failure."
6823  << __E__;
6824  __SUP_COUT_ERR__ << "\n" << ss.str();
6825  __SS_THROW__;
6826  }
6827  else if(
6828  refresh ||
6829  (now - userLastUseTime_[mapKey]) >
6830  CONFIGURATION_MANAGER_REFRESH_THRESHOLD) // check if should refresh all table info
6831  {
6832  __SUP_COUT__ << "Refreshing all table info." << __E__;
6833  userConfigurationManagers_[mapKey]->getAllTableInfo(
6834  true /* refresh */,
6835  0 /* accumulatedWarnings */,
6836  "" /* errorFilterName */,
6837  false /* getGroupKeys */,
6838  false /* getGroupInfo */,
6839  true /* initializeActiveGroups */);
6840  }
6841  __SUP_COUTT__ << "Configuration Manager for author="
6842  << userConfigurationManagers_[mapKey]->getUsername()
6843  << " ready. time=" << time(0) << " " << clock() << " runTimeSeconds()="
6844  << userConfigurationManagers_[mapKey]->runTimeSeconds() << __E__;
6845 
6846  // update active sessionIndex last use time
6847  userLastUseTime_[mapKey] = now;
6848 
6849  // check for stale sessions and remove them (so table user maps do not grow forever)
6850  for(std::map<std::string, time_t>::iterator it = userLastUseTime_.begin();
6851  it != userLastUseTime_.end();
6852  ++it)
6853  if(now - it->second > CONFIGURATION_MANAGER_EXPIRATION_TIME) // expired!
6854  {
6855  __SUP_COUT__ << now << ":" << it->second << " = " << now - it->second
6856  << __E__;
6857  delete userConfigurationManagers_[it->first]; // call destructor
6858  if(!(userConfigurationManagers_.erase(it->first))) // erase by key
6859  {
6860  __SUP_SS__ << "Fatal error erasing configuration manager by key!"
6861  << __E__;
6862  __SUP_COUT_ERR__ << "\n" << ss.str();
6863  __SS_THROW__;
6864  }
6865  userLastUseTime_.erase(it); // erase by iterator
6866 
6867  it =
6868  userLastUseTime_
6869  .begin(); // fail safe.. reset it, to avoid trying to understand what happens with the next iterator
6870  }
6871 
6872  return userConfigurationManagers_[mapKey];
6873 } //end refreshUserSession()
6874 
6875 //==============================================================================
6880 void ConfigurationGUISupervisor::handleDeleteTableInfoXML(HttpXmlDocument& xmlOut,
6881  ConfigurationManagerRW* cfgMgr,
6882  std::string& tableName)
6883 {
6884  if(0 == rename((TABLE_INFO_PATH + tableName + TABLE_INFO_EXT).c_str(),
6885  (TABLE_INFO_PATH + tableName + TABLE_INFO_EXT + ".unused").c_str()))
6886  __SUP_COUT_INFO__ << ("Table Info File successfully renamed: " +
6887  (TABLE_INFO_PATH + tableName + TABLE_INFO_EXT + ".unused"))
6888  << __E__;
6889  else
6890  {
6891  __SUP_COUT_ERR__ << ("Error renaming file to " +
6892  (TABLE_INFO_PATH + tableName + TABLE_INFO_EXT + ".unused"))
6893  << __E__;
6894 
6895  xmlOut.addTextElementToData(
6896  "Error",
6897  ("Error renaming Table Info File to " +
6898  (TABLE_INFO_PATH + tableName + TABLE_INFO_EXT + ".unused")));
6899  return;
6900  }
6901 
6902  // reload all with refresh to remove new table
6903  cfgMgr->getAllTableInfo(true /* refresh */);
6904 } // end handleDeleteTableInfoXML()
6905 
6906 //==============================================================================
6913 void ConfigurationGUISupervisor::handleSaveTableInfoXML(
6914  HttpXmlDocument& xmlOut,
6915  ConfigurationManagerRW* cfgMgr,
6916  std::string& tableName,
6917  const std::string& data,
6918  const std::string& tableDescription,
6919  const std::string& columnChoicesCSV,
6920  bool allowOverwrite)
6921 {
6922  // create all caps name and validate
6923  // only allow alpha-numeric names with "Table" at end
6924  std::string capsName;
6925  try
6926  {
6927  capsName = TableBase::convertToCaps(tableName, true);
6928  }
6929  catch(std::runtime_error& e)
6930  { // error! non-alpha
6931  xmlOut.addTextElementToData("Error", e.what());
6932  return;
6933  }
6934 
6935  if(!allowOverwrite)
6936  {
6937  FILE* fp = fopen((TABLE_INFO_PATH + tableName + TABLE_INFO_EXT).c_str(), "r");
6938  if(fp)
6939  {
6940  fclose(fp);
6941  xmlOut.addTextElementToData("TableName", tableName);
6942  xmlOut.addTextElementToData("OverwriteError", "1");
6943  xmlOut.addTextElementToData(
6944  "Error",
6945  "File already exists! ('" +
6946  (TABLE_INFO_PATH + tableName + TABLE_INFO_EXT) + "')");
6947  return;
6948  }
6949  }
6950 
6951  __SUP_COUT__ << "capsName=" << capsName << __E__;
6952  __SUP_COUT__ << "tableName=" << tableName << __E__;
6953  __SUP_COUT__ << "tableDescription=" << tableDescription << __E__;
6954  __SUP_COUT__ << "columnChoicesCSV=" << columnChoicesCSV << __E__;
6955 
6956  // create preview string to validate column info before write to file
6957  std::stringstream outss;
6958 
6959  outss << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n";
6960  outss << "\t<ROOT xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
6961  "xsi:noNamespaceSchemaLocation=\"TableInfo.xsd\">\n";
6962  outss << "\t\t<TABLE Name=\"" << tableName << "\">\n";
6963  outss << "\t\t\t<VIEW Name=\"" << capsName
6964  << "\" Type=\"File,Database,DatabaseTest\" Description=\"" << tableDescription
6965  << "\">\n";
6966 
6967  // each column is represented by 4 fields or 6
6968  // - type, name, dataType, defaultValue, minValue, maxValue
6969 
6970  std::istringstream columnChoicesISS(columnChoicesCSV);
6971  std::string columnChoicesString;
6972  std::string columnDefaultValue, columnMinValue, columnMaxValue;
6973  std::vector<std::string> columnParameters;
6974  std::vector<std::string> columnData =
6975  StringMacros::getVectorFromString(data, {';'} /*delimiter*/);
6976 
6977  std::set<std::string> childLinkIndices, childLinkUIDIndices, childLinkGroupIDIndices;
6978 
6979  for(unsigned int c = 0; c < columnData.size() - 1; ++c)
6980  {
6981  columnParameters =
6982  StringMacros::getVectorFromString(columnData[c], {','} /*delimiter*/);
6983  __COUT__ << "Column #" << c << ": "
6984  << StringMacros::vectorToString(columnParameters) << __E__;
6985  for(unsigned int p = 0; p < columnParameters.size(); ++p)
6986  {
6987  __COUT__ << "\t Parameter #" << p << ": " << columnParameters[p] << __E__;
6988  }
6989  __COUT__ << "\t creating the new xml" << __E__;
6990 
6991  std::string& columnType = columnParameters[0];
6992  std::string& columnName = columnParameters[1];
6993  std::string& columnDataType = columnParameters[2];
6994  const std::string columnStorageName =
6995  TableBase::convertToCaps(columnName); // now caps;
6996 
6997  outss << "\t\t\t\t<COLUMN Type=\"";
6998  outss << columnType;
6999  outss << "\" \t Name=\"";
7000  outss << columnName;
7001  outss << "\" \t StorageName=\"";
7002  try
7003  {
7004  outss << columnStorageName;
7005  }
7006  catch(std::runtime_error& e)
7007  { // error! non-alpha
7008  xmlOut.addTextElementToData(
7009  "Error",
7010  std::string("For column name '") + columnName + "' - " + e.what());
7011  return;
7012  }
7013  outss << "\" \t DataType=\"";
7014  outss << columnDataType;
7015 
7016  columnDefaultValue = StringMacros::decodeURIComponent(columnParameters[3]);
7017 
7018  std::string* columnDefaultValuePtr = nullptr;
7019  if(columnDefaultValue !=
7020  TableViewColumnInfo::getDefaultDefaultValue(columnType, columnDataType))
7021  {
7022  __SUP_COUT__ << "FOUND user spec'd default value '" << columnDefaultValue
7023  << "'" << __E__;
7024  outss << "\" \t DefaultValue=\"";
7025  outss << columnParameters[3];
7026  columnDefaultValuePtr = &columnParameters[3];
7027  }
7028  getline(columnChoicesISS, columnChoicesString, ';');
7029  outss << "\" \t DataChoices=\"";
7030  outss << columnChoicesString;
7031 
7032  std::string* columnMinValuePtr = nullptr;
7033  std::string* columnMaxValuePtr = nullptr;
7034 
7035  if(columnParameters.size() > 4 &&
7036  columnDataType == TableViewColumnInfo::DATATYPE_NUMBER)
7037  {
7038  columnMinValue = StringMacros::decodeURIComponent(columnParameters[4]);
7039  if(columnMinValue != "")
7040  {
7041  if(columnMinValue !=
7043  {
7044  __SUP_COUT__ << "FOUND user spec'd min value '" << columnParameters[4]
7045  << "'" << __E__;
7048  {
7049  __SS__ << "Inavlid user spec'd min value '" << columnParameters[4]
7050  << "' which evaluates to '" << columnMinValue
7051  << "' and is not a valid number. The minimum value must "
7052  "be a number (environment variables and math "
7053  "operations are allowed)."
7054  << __E__;
7055  __SS_THROW__;
7056  }
7057  outss << "\" \t MinValue=\"" << columnParameters[4];
7058  columnMinValuePtr = &columnParameters[4];
7059  }
7060  }
7061 
7062  columnMaxValue = StringMacros::decodeURIComponent(columnParameters[5]);
7063  if(columnMaxValue != "")
7064  {
7065  if(columnMaxValue !=
7067  {
7068  __SUP_COUT__ << "FOUND user spec'd max value = " << columnMaxValue
7069  << __E__;
7072  {
7073  __SS__ << "Inavlid user spec'd max value '" << columnParameters[5]
7074  << "' which evaluates to '" << columnMaxValue
7075  << "' and is not a valid number. The maximum value must "
7076  "be a number (environment variables and math "
7077  "operations are allowed)."
7078  << __E__;
7079  __SS_THROW__;
7080  }
7081  outss << "\" \t MaxValue=\"" << columnParameters[5];
7082  columnMaxValuePtr = &columnParameters[5];
7083  }
7084  }
7085  }
7086 
7087  //validate each column before saving a bad table file
7088  try
7089  {
7090  TableViewColumnInfo testCol(columnType,
7091  columnName,
7092  columnStorageName,
7093  columnDataType,
7094  columnDefaultValuePtr,
7095  columnChoicesString,
7096  columnMinValuePtr,
7097  columnMaxValuePtr,
7098  nullptr //capturedExceptionString
7099  );
7100  }
7101  catch(const std::runtime_error& e)
7102  {
7103  __SS__ << "Error identified with Column #" << c << ": \n" << e.what();
7104  __SS_THROW__;
7105  }
7106 
7107  if(TableViewColumnInfo::isChildLink(columnType))
7108  childLinkIndices.insert(columnType.substr(sizeof("ChildLink-") - 1));
7109  else if(columnType.find("ChildLinkUID-") == 0)
7110  childLinkUIDIndices.insert(columnType.substr(sizeof("ChildLinkUID-") - 1));
7111  else if(columnType.find("ChildLinkGroupID-") == 0)
7112  childLinkGroupIDIndices.insert(
7113  columnType.substr(sizeof("ChildLinkGroupID-") - 1));
7114 
7115  outss << "\"/>\n";
7116  }
7117 
7118  // Cross-column validation: every ChildLinkUID and ChildLinkGroupID must
7119  // have a matching ChildLink with the same index.
7120  for(const auto& idx : childLinkUIDIndices)
7121  if(childLinkIndices.find(idx) == childLinkIndices.end())
7122  {
7123  __SS__ << "Column type 'ChildLinkUID-" << idx
7124  << "' has no matching 'ChildLink-" << idx
7125  << "' column. A ChildLinkUID column must be paired with a "
7126  "ChildLink column using the same link index."
7127  << __E__;
7128  __SS_THROW__;
7129  }
7130  for(const auto& idx : childLinkGroupIDIndices)
7131  if(childLinkIndices.find(idx) == childLinkIndices.end())
7132  {
7133  __SS__ << "Column type 'ChildLinkGroupID-" << idx
7134  << "' has no matching 'ChildLink-" << idx
7135  << "' column. A ChildLinkGroupID column must be paired with a "
7136  "ChildLink column using the same link index. "
7137  "Did you intend to make a target of a Group Link? "
7138  "If so, use the 'GroupID' column type instead."
7139  << __E__;
7140  __SS_THROW__;
7141  }
7142  for(const auto& idx : childLinkIndices)
7143  if(childLinkUIDIndices.find(idx) == childLinkUIDIndices.end() &&
7144  childLinkGroupIDIndices.find(idx) == childLinkGroupIDIndices.end())
7145  {
7146  __SS__ << "Column type 'ChildLink-" << idx
7147  << "' has no matching 'ChildLinkUID-" << idx
7148  << "' or 'ChildLinkGroupID-" << idx
7149  << "' column. A ChildLink column must be paired with either a "
7150  "ChildLinkUID or ChildLinkGroupID column using the same link index."
7151  << __E__;
7152  __SS_THROW__;
7153  }
7154 
7155  outss << "\t\t\t</VIEW>\n";
7156  outss << "\t\t</TABLE>\n";
7157  outss << "\t</ROOT>\n";
7158 
7159  __SUP_COUT__ << outss.str() << __E__;
7160 
7161  FILE* fp = fopen((TABLE_INFO_PATH + tableName + TABLE_INFO_EXT).c_str(), "w");
7162  if(!fp)
7163  {
7164  xmlOut.addTextElementToData("Error",
7165  "Failed to open destination Table Info file:" +
7166  (TABLE_INFO_PATH + tableName + TABLE_INFO_EXT));
7167  return;
7168  }
7169 
7170  fprintf(fp, "%s", outss.str().c_str());
7171  fclose(fp);
7172 
7173  __SUP_COUT_INFO__ << "Finished saving Table Info for '" << tableName
7174  << ".' Looking for errors in all table column info..." << __E__;
7175 
7176  // reload all table info with refresh AND reset to pick up possibly new table
7177  // check for errors related to this tableName
7178  std::string accumulatedErrors = "";
7179  cfgMgr->getAllTableInfo(true /* refresh */, &accumulatedErrors, tableName);
7180 
7181  // if errors associated with this table name stop and report
7182  if(accumulatedErrors != "")
7183  {
7184  __SUP_SS__ << ("The new version of the '" + tableName +
7185  "' table column info was saved, however errors were detected "
7186  "reading back the table '" +
7187  tableName + "' after the save attempt:\n\n" + accumulatedErrors)
7188  << __E__;
7189 
7190  __SUP_COUT_ERR__ << ss.str() << __E__;
7191  xmlOut.addTextElementToData("Error", ss.str());
7192 
7193  return;
7194  }
7195 
7196  // return the new table info
7197  handleGetTableXML(xmlOut, cfgMgr, tableName, TableVersion());
7198 
7199  // After save, debug all table column info
7200  const std::map<std::string, TableInfo>& allTableInfo = cfgMgr->getAllTableInfo();
7201 
7202  // give a print out of currently illegal table column info
7203  for(const auto& cfgInfo : allTableInfo)
7204  {
7205  try
7206  {
7207  cfgMgr->getTableByName(cfgInfo.first)->getMockupViewP()->init();
7208  }
7209  catch(std::runtime_error& e)
7210  {
7211  __SUP_COUT_WARN__ << "\n\n##############################################\n"
7212  << "Error identified in column info of table '"
7213  << cfgInfo.first << "':\n\n"
7214  << e.what() << "\n\n"
7215  << __E__;
7216  }
7217  }
7218 } // end handleSaveTableInfoXML()
7219 
7220 //==============================================================================
7228 void ConfigurationGUISupervisor::handleSetGroupAliasInBackboneXML(
7229  HttpXmlDocument& xmlOut,
7230  ConfigurationManagerRW* cfgMgr,
7231  const std::string& groupAliasCSV,
7232  const std::string& groupNameCSV,
7233  const std::string& groupKeyCSV,
7234  const std::string& author)
7235 try
7236 {
7237  cfgMgr->loadConfigurationBackbone();
7238  std::map<std::string, TableVersion> activeVersions = cfgMgr->getActiveVersions();
7239 
7240  const std::string groupAliasesTableName =
7241  ConfigurationManager::GROUP_ALIASES_TABLE_NAME;
7242  if(activeVersions.find(groupAliasesTableName) == activeVersions.end())
7243  {
7244  __SUP_SS__ << "Active version of " << groupAliasesTableName << " missing!"
7245  << __E__;
7246  xmlOut.addTextElementToData("Error", ss.str());
7247  return;
7248  }
7249 
7250  // put all old backbone versions in xmlOut
7251  const std::set<std::string> backboneMembers = cfgMgr->getBackboneMemberNames();
7252  for(auto& memberName : backboneMembers)
7253  {
7254  __SUP_COUT__ << "activeVersions[\"" << memberName
7255  << "\"]=" << activeVersions[memberName] << __E__;
7256 
7257  xmlOut.addTextElementToData("oldBackboneName", memberName);
7258  xmlOut.addTextElementToData("oldBackboneVersion",
7259  activeVersions[memberName].toString());
7260  }
7261 
7262  // make a temporary version from active view
7263  // modify the chosen groupAlias row
7264  // save as new version
7265 
7266  TableBase* table = cfgMgr->getTableByName(groupAliasesTableName);
7267  TableVersion originalVersion = activeVersions[groupAliasesTableName];
7268  TableVersion temporaryVersion = table->createTemporaryView(originalVersion);
7269 
7270  __SUP_COUT__ << "\t\t temporaryVersion: " << temporaryVersion << __E__;
7271  bool isDifferent = false;
7272 
7273  try
7274  {
7275  TableView* configView = table->getTemporaryView(temporaryVersion);
7276 
7277  unsigned int col = configView->findCol("GroupKeyAlias");
7278  unsigned int ccol = configView->findCol(TableViewColumnInfo::COL_NAME_COMMENT);
7279  unsigned int ncol = configView->findCol("GroupName");
7280  unsigned int kcol = configView->findCol("GroupKey");
7281 
7282  // only make a new version if we are changing compared to active backbone
7283  std::vector<std::string> groupAliases =
7284  StringMacros::getVectorFromString(groupAliasCSV);
7285  std::vector<std::string> groupNames =
7286  StringMacros::getVectorFromString(groupNameCSV);
7287  std::vector<std::string> groupKeys =
7288  StringMacros::getVectorFromString(groupKeyCSV);
7289  __SUP_COUTV__(StringMacros::vectorToString(groupAliases));
7290  __SUP_COUTV__(StringMacros::vectorToString(groupNames));
7291  __SUP_COUTV__(StringMacros::vectorToString(groupKeys));
7292 
7293  size_t i = 0;
7294  for(const auto& groupAlias : groupAliases)
7295  {
7296  if(groupAlias == "" || groupNames[i] == "" || groupKeys[i] == "")
7297  {
7298  //skip empty aliases
7299  __SUP_COUT_WARN__ << "Empty alias parameter found [" << i << "] = {"
7300  << groupAlias << ", " << groupNames[i] << "("
7301  << groupKeys[i] << ")}" << __E__;
7302  ++i;
7303  continue;
7304  }
7305 
7306  bool localIsDifferent = false;
7307  const std::string& groupName = groupNames[i];
7308  const TableGroupKey groupKey(groupKeys[i]);
7309  ++i;
7310 
7311  unsigned int row = -1;
7312  // find groupAlias row
7313  try
7314  {
7315  row = configView->findRow(col, groupAlias);
7316  }
7317  catch(...) // ignore not found error
7318  {
7319  }
7320 
7321  if(row == (unsigned int)-1) // if row not found then add a row
7322  {
7323  localIsDifferent = true;
7324  row = configView->addRow();
7325 
7326  // set all columns in new row
7327  configView->setValue(
7328  "This Group Alias was automatically setup by the server.", row, ccol);
7329  configView->setValue(groupAlias, row, col);
7330  }
7331 
7332  __SUP_COUT__ << "\t\t row: " << row << __E__;
7333 
7334  __SUP_COUT__ << "\t\t groupName: " << groupName << " vs "
7335  << configView->getDataView()[row][ncol] << __E__;
7336  if(groupName != configView->getDataView()[row][ncol])
7337  {
7338  configView->setValue(groupName, row, ncol);
7339  localIsDifferent = true;
7340  }
7341 
7342  __SUP_COUT__ << "\t\t groupKey: " << groupKey << " vs "
7343  << configView->getDataView()[row][kcol] << __E__;
7344  if(groupKey.toString() != configView->getDataView()[row][kcol])
7345  {
7346  configView->setValue(groupKey.toString(), row, kcol);
7347  localIsDifferent = true;
7348  }
7349 
7350  if(localIsDifferent) // set author/time of new record if different
7351  {
7352  configView->setValue(
7353  author,
7354  row,
7355  configView->findCol(TableViewColumnInfo::COL_NAME_AUTHOR));
7356  configView->setValue(
7357  time(0),
7358  row,
7359  configView->findCol(TableViewColumnInfo::COL_NAME_CREATION));
7360  isDifferent = true;
7361  }
7362  } //end group alias modify loop
7363  }
7364  catch(...)
7365  {
7366  __SUP_COUT_ERR__ << "Error editing Group Alias view!" << __E__;
7367 
7368  // delete temporaryVersion
7369  table->eraseView(temporaryVersion);
7370  throw;
7371  }
7372 
7373  TableVersion newAssignedVersion;
7374  if(isDifferent) // make new version if different
7375  {
7376  __SUP_COUT__ << "\t\t**************************** Save as new table version"
7377  << __E__;
7378 
7379  // save or find equivalent
7381  xmlOut,
7382  cfgMgr,
7383  table->getTableName(),
7384  originalVersion,
7385  false /*makeTemporary*/,
7386  table,
7387  temporaryVersion,
7388  false /*ignoreDuplicates*/,
7389  true /*lookForEquivalent*/);
7390  }
7391  else // use existing version
7392  {
7393  __SUP_COUT__
7394  << "\t\t**************************** Using the existing table version"
7395  << __E__;
7396 
7397  // delete temporaryVersion
7398  table->eraseView(temporaryVersion);
7399  newAssignedVersion = activeVersions[groupAliasesTableName];
7400 
7401  xmlOut.addTextElementToData("savedName", groupAliasesTableName);
7402  xmlOut.addTextElementToData("savedVersion", newAssignedVersion.toString());
7403  }
7404 
7405  __SUP_COUT__ << "\t\t newAssignedVersion: " << newAssignedVersion << __E__;
7406 } //end handleSetGroupAliasInBackboneXML()
7407 catch(std::runtime_error& e)
7408 {
7409  __SUP_SS__ << "Error saving new Group Alias view!\n\n " << e.what() << __E__;
7410  __SUP_COUT_ERR__ << ss.str();
7411  xmlOut.addTextElementToData("Error", ss.str());
7412 }
7413 catch(...)
7414 {
7415  __SUP_SS__ << "Error saving new Group Alias view!\n\n " << __E__;
7416  try
7417  {
7418  throw;
7419  } //one more try to printout extra info
7420  catch(const std::exception& e)
7421  {
7422  ss << "Exception message: " << e.what();
7423  }
7424  catch(...)
7425  {
7426  }
7427  __SUP_COUT_ERR__ << ss.str();
7428  xmlOut.addTextElementToData("Error", ss.str());
7429 } //end handleSetGroupAliasInBackboneXML() catch
7430 
7431 //==============================================================================
7439 void ConfigurationGUISupervisor::handleSetTableAliasInBackboneXML(
7440  HttpXmlDocument& xmlOut,
7441  ConfigurationManagerRW* cfgMgr,
7442  const std::string& tableAlias,
7443  const std::string& tableName,
7444  TableVersion version,
7445  const std::string& author)
7446 try
7447 {
7448  cfgMgr->loadConfigurationBackbone();
7449  std::map<std::string, TableVersion> activeVersions = cfgMgr->getActiveVersions();
7450 
7451  const std::string versionAliasesTableName =
7452  ConfigurationManager::VERSION_ALIASES_TABLE_NAME;
7453  if(activeVersions.find(versionAliasesTableName) == activeVersions.end())
7454  {
7455  __SUP_SS__ << "Active version of " << versionAliasesTableName << " missing!"
7456  << __E__;
7457  xmlOut.addTextElementToData("Error", ss.str());
7458  return;
7459  }
7460 
7461  // put all old backbone versions in xmlOut
7462  const std::set<std::string> backboneMembers = cfgMgr->getBackboneMemberNames();
7463  for(auto& memberName : backboneMembers)
7464  {
7465  __SUP_COUT__ << "activeVersions[\"" << memberName
7466  << "\"]=" << activeVersions[memberName] << __E__;
7467 
7468  xmlOut.addTextElementToData("oldBackboneName", memberName);
7469  xmlOut.addTextElementToData("oldBackboneVersion",
7470  activeVersions[memberName].toString());
7471  }
7472 
7473  // make a temporary version from active view
7474  // modify the chosen versionAlias row
7475  // save as new version
7476 
7477  TableBase* table = cfgMgr->getTableByName(versionAliasesTableName);
7478  TableVersion originalVersion = activeVersions[versionAliasesTableName];
7479  TableVersion temporaryVersion = table->createTemporaryView(originalVersion);
7480 
7481  __SUP_COUT__ << "\t\t temporaryVersion: " << temporaryVersion << __E__;
7482 
7483  bool isDifferent = false;
7484 
7485  try
7486  {
7487  TableView* configView = table->getTemporaryView(temporaryVersion);
7488 
7489  unsigned int col;
7490  unsigned int col2 = configView->findCol("VersionAlias");
7491  unsigned int col3 = configView->findCol("TableName");
7492 
7493  // only make a new version if we are changing compared to active backbone
7494 
7495  unsigned int row = -1;
7496  // find tableName, versionAlias pair
7497  // NOTE: only accept the first pair, repeats are ignored.
7498  try
7499  {
7500  unsigned int tmpRow = -1;
7501  do
7502  { // start looking from beyond last find
7503  tmpRow = configView->findRow(col3, tableName, tmpRow + 1);
7504  } while(configView->getDataView()[tmpRow][col2] != tableAlias);
7505  // at this point the first pair was found! (else exception was thrown)
7506  row = tmpRow;
7507  }
7508  catch(...)
7509  {
7510  }
7511  if(row == (unsigned int)-1) // if row not found then add a row
7512  {
7513  isDifferent = true;
7514  row = configView->addRow();
7515 
7516  // set all columns in new row
7517  col = configView->findCol(TableViewColumnInfo::COL_NAME_COMMENT);
7518  configView->setValue(
7519  std::string("Entry was added by server in ") +
7520  "ConfigurationGUISupervisor::setTableAliasInActiveBackbone().",
7521  row,
7522  col);
7523 
7524  col = configView->findCol("VersionAliasUID");
7525  configView->setValue(
7526  tableName.substr(0, tableName.rfind("Table")) + tableAlias, row, col);
7527 
7528  configView->setValue(tableAlias, row, col2);
7529  configView->setValue(tableName, row, col3);
7530  }
7531 
7532  __SUP_COUT__ << "\t\t row: " << row << __E__;
7533 
7534  col = configView->findCol("Version");
7535  __SUP_COUT__ << "\t\t version: " << version << " vs "
7536  << configView->getDataView()[row][col] << __E__;
7537  if(version.toString() != configView->getDataView()[row][col])
7538  {
7539  configView->setValue(version.toString(), row, col);
7540  isDifferent = true;
7541  }
7542 
7543  if(isDifferent) // set author/time of new version if different
7544  {
7545  configView->setValue(
7546  author, row, configView->findCol(TableViewColumnInfo::COL_NAME_AUTHOR));
7547  configView->setValue(
7548  time(0),
7549  row,
7550  configView->findCol(TableViewColumnInfo::COL_NAME_CREATION));
7551  }
7552  }
7553  catch(...)
7554  {
7555  __SUP_COUT_ERR__ << "Error editing Version Alias view!" << __E__;
7556 
7557  // delete temporaryVersion
7558  table->eraseView(temporaryVersion);
7559  throw;
7560  }
7561 
7562  TableVersion newAssignedVersion;
7563  if(isDifferent) // make new version if different
7564  {
7565  __SUP_COUT__ << "\t\t**************************** Save as new table version"
7566  << __E__;
7567 
7569  xmlOut,
7570  cfgMgr,
7571  table->getTableName(),
7572  originalVersion,
7573  false /*makeTemporary*/,
7574  table,
7575  temporaryVersion,
7576  false /*ignoreDuplicates*/,
7577  true /*lookForEquivalent*/);
7578  }
7579  else // use existing version
7580  {
7581  __SUP_COUT__ << "\t\t**************************** Using existing table version"
7582  << __E__;
7583 
7584  // delete temporaryVersion
7585  table->eraseView(temporaryVersion);
7586  newAssignedVersion = activeVersions[versionAliasesTableName];
7587 
7588  xmlOut.addTextElementToData("savedName", versionAliasesTableName);
7589  xmlOut.addTextElementToData("savedVersion", newAssignedVersion.toString());
7590  }
7591 
7592  __SUP_COUT__ << "\t\t newAssignedVersion: " << newAssignedVersion << __E__;
7593 } // end handleSetVersionAliasInBackboneXML()
7594 catch(std::runtime_error& e)
7595 {
7596  __SUP_SS__ << "Error saving new Version Alias view!\n\n " << e.what() << __E__;
7597  __SUP_COUT_ERR__ << ss.str();
7598  xmlOut.addTextElementToData("Error", ss.str());
7599 }
7600 catch(...)
7601 {
7602  __SUP_SS__ << "Error saving new Version Alias view!\n\n " << __E__;
7603  try
7604  {
7605  throw;
7606  } //one more try to printout extra info
7607  catch(const std::exception& e)
7608  {
7609  ss << "Exception message: " << e.what();
7610  }
7611  catch(...)
7612  {
7613  }
7614  __SUP_COUT_ERR__ << ss.str();
7615  xmlOut.addTextElementToData("Error", ss.str());
7616 } // end handleSetVersionAliasInBackboneXML() catch
7617 
7618 //==============================================================================
7624 void ConfigurationGUISupervisor::handleAliasGroupMembersInBackboneXML(
7625  HttpXmlDocument& xmlOut,
7626  ConfigurationManagerRW* cfgMgr,
7627  const std::string& versionAlias,
7628  const std::string& groupName,
7629  TableGroupKey groupKey,
7630  const std::string& author)
7631 try
7632 {
7633  cfgMgr->loadConfigurationBackbone();
7634  std::map<std::string, TableVersion> activeVersions = cfgMgr->getActiveVersions();
7635 
7636  const std::string versionAliasesTableName =
7637  ConfigurationManager::VERSION_ALIASES_TABLE_NAME;
7638  if(activeVersions.find(versionAliasesTableName) == activeVersions.end())
7639  {
7640  __SUP_SS__ << "Active version of " << versionAliasesTableName << " missing!"
7641  << __E__;
7642  xmlOut.addTextElementToData("Error", ss.str());
7643  return;
7644  }
7645 
7646  // put all old backbone versions in xmlOut
7647  const std::set<std::string> backboneMembers = cfgMgr->getBackboneMemberNames();
7648  for(auto& memberName : backboneMembers)
7649  {
7650  __SUP_COUT__ << "activeVersions[\"" << memberName
7651  << "\"]=" << activeVersions[memberName] << __E__;
7652 
7653  xmlOut.addTextElementToData("oldBackboneName", memberName);
7654  xmlOut.addTextElementToData("oldBackboneVersion",
7655  activeVersions[memberName].toString());
7656  }
7657 
7658  // make a temporary version from active view
7659  // modify the chosen versionAlias row
7660  // save as new version
7661 
7662  TableBase* table = cfgMgr->getTableByName(versionAliasesTableName);
7663  TableVersion temporaryVersion =
7664  table->createTemporaryView(activeVersions[versionAliasesTableName]);
7665 
7666  __SUP_COUT__ << "\t\t temporaryVersion: " << temporaryVersion << __E__;
7667 
7668  TableView* configView = table->getTemporaryView(temporaryVersion);
7669 
7670  // only make a new version if we are changing compared to active backbone
7671  bool isDifferent = false;
7672 
7673  // get member names and versions
7674  std::map<std::string /*name*/, TableVersion /*version*/> memberMap;
7675  try
7676  {
7677  cfgMgr->loadTableGroup(groupName,
7678  groupKey,
7679  false /*doActivate*/,
7680  &memberMap,
7681  0,
7682  0,
7683  0,
7684  0,
7685  0, // defaults
7686  true /*doNotLoadMember*/);
7687  }
7688  catch(...)
7689  {
7690  xmlOut.addTextElementToData(
7691  "Error",
7692  "Table group \"" + TableGroupKey::getFullGroupString(groupName, groupKey) +
7693  "\" can not be retrieved!");
7694  return;
7695  }
7696 
7697  unsigned int col;
7698  unsigned int col2 = configView->findCol("VersionAlias");
7699  unsigned int col3 = configView->findCol("TableName");
7700 
7701  for(auto& memberPair : memberMap)
7702  {
7703  bool thisMemberIsDifferent = false;
7704  unsigned int row = -1;
7705 
7706  __SUP_COUT__ << "Adding alias for " << memberPair.first << "_v"
7707  << memberPair.second << " to " << versionAlias << __E__;
7708 
7709  // find tableName, versionAlias pair
7710  // NOTE: only accept the first pair, repeats are ignored.
7711  try
7712  {
7713  unsigned int tmpRow = -1;
7714  do
7715  { // start looking from beyond last find
7716  tmpRow = configView->findRow(col3, memberPair.first, tmpRow + 1);
7717  } while(configView->getDataView()[tmpRow][col2] != versionAlias);
7718  // at this point the first pair was found! (else exception was thrown)
7719  row = tmpRow;
7720  }
7721  catch(...)
7722  {
7723  }
7724  if(row == (unsigned int)-1) // if row not found then add a row
7725  {
7726  thisMemberIsDifferent = true;
7727  row = configView->addRow();
7728 
7729  // set all columns in new row
7730  col = configView->findCol(TableViewColumnInfo::COL_NAME_COMMENT);
7731  configView->setValue(
7732  std::string("Entry was added by server in ") +
7733  "ConfigurationGUISupervisor::setTableAliasInActiveBackbone().",
7734  row,
7735  col);
7736 
7737  col = configView->getColUID();
7738  configView->setValue(
7739  memberPair.first.substr(0, memberPair.first.rfind("Table")) +
7740  versionAlias,
7741  row,
7742  col);
7743 
7744  configView->setValue(versionAlias, row, col2);
7745  configView->setValue(memberPair.first, row, col3);
7746  }
7747 
7748  col = configView->findCol("Version");
7749 
7750  if(memberPair.second.toString() != configView->getDataView()[row][col])
7751  {
7752  configView->setValue(memberPair.second.toString(), row, col);
7753  thisMemberIsDifferent = true;
7754  }
7755 
7756  if(thisMemberIsDifferent) // change author and time if row is different
7757  {
7758  configView->setValue(
7759  author, row, configView->findCol(TableViewColumnInfo::COL_NAME_AUTHOR));
7760  configView->setValue(
7761  time(0),
7762  row,
7763  configView->findCol(TableViewColumnInfo::COL_NAME_CREATION));
7764  }
7765 
7766  if(thisMemberIsDifferent)
7767  isDifferent = true;
7768  }
7769 
7770  // configView->print();
7771 
7772  TableVersion newAssignedVersion;
7773  if(isDifferent) // make new version if different
7774  {
7775  __SUP_COUT__ << "\t\t**************************** Save v" << temporaryVersion
7776  << " as new table version" << __E__;
7777 
7778  newAssignedVersion =
7779  cfgMgr->saveNewTable(versionAliasesTableName, temporaryVersion);
7780  }
7781  else // use existing version
7782  {
7783  __SUP_COUT__ << "\t\t**************************** Using existing table version"
7784  << __E__;
7785 
7786  // delete temporaryVersion
7787  table->eraseView(temporaryVersion);
7788  newAssignedVersion = activeVersions[versionAliasesTableName];
7789  }
7790 
7791  xmlOut.addTextElementToData("savedName", versionAliasesTableName);
7792  xmlOut.addTextElementToData("savedVersion", newAssignedVersion.toString());
7793  __SUP_COUT__ << "\t\t Resulting Version: " << newAssignedVersion << __E__;
7794 } // end handleAliasGroupMembersInBackboneXML()
7795 catch(std::runtime_error& e)
7796 {
7797  __SUP_SS__ << "Error saving new Version Alias view!\n\n " << e.what() << __E__;
7798  __SUP_COUT_ERR__ << ss.str();
7799  xmlOut.addTextElementToData("Error", ss.str());
7800 }
7801 catch(...)
7802 {
7803  __SUP_SS__ << "Error saving new Version Alias view!\n\n " << __E__;
7804  try
7805  {
7806  throw;
7807  } //one more try to printout extra info
7808  catch(const std::exception& e)
7809  {
7810  ss << "Exception message: " << e.what();
7811  }
7812  catch(...)
7813  {
7814  }
7815  __SUP_COUT_ERR__ << ss.str();
7816  xmlOut.addTextElementToData("Error", ss.str());
7817 } // end handleAliasGroupMembersInBackboneXML() catch
7818 
7819 //==============================================================================
7830 void ConfigurationGUISupervisor::handleGroupAliasesXML(HttpXmlDocument& xmlOut,
7831  ConfigurationManagerRW* cfgMgr)
7832 {
7833  cfgMgr->loadConfigurationBackbone();
7834  std::map<std::string, TableVersion> activeVersions = cfgMgr->getActiveVersions();
7835 
7836  std::string groupAliasesTableName = ConfigurationManager::GROUP_ALIASES_TABLE_NAME;
7837  if(activeVersions.find(groupAliasesTableName) == activeVersions.end())
7838  {
7839  __SUP_SS__ << "\nActive version of " << groupAliasesTableName << " missing! "
7840  << groupAliasesTableName
7841  << " is a required member of the Backbone table group."
7842  << "\n\nLikely you need to activate a valid Backbone table group."
7843  << __E__;
7844  __SUP_COUT__ << ss.str(); // just output findings, and return empty xml to avoid
7845  // infinite error loops in GUI
7846  // xmlOut.addTextElementToData("Error", ss.str());
7847  return;
7848  }
7849  __SUP_COUT__ << "activeVersions[\"" << groupAliasesTableName
7850  << "\"]=" << activeVersions[groupAliasesTableName] << __E__;
7851  xmlOut.addTextElementToData("GroupAliasesTableName", groupAliasesTableName);
7852  xmlOut.addTextElementToData("GroupAliasesTableVersion",
7853  activeVersions[groupAliasesTableName].toString());
7854 
7855  std::vector<std::pair<std::string, ConfigurationTree>> aliasNodePairs =
7856  cfgMgr->getNode(groupAliasesTableName).getChildren();
7857 
7858  const int numOfThreads = StringMacros::getConcurrencyCount() / 2;
7859  __SUP_COUT__ << " getConcurrencyCount " << StringMacros::getConcurrencyCount()
7860  << " ==> " << numOfThreads << " threads for alias group loads." << __E__;
7861 
7862  if(numOfThreads < 2) // no multi-threading
7863  {
7864  std::string groupName, groupKey, groupComment, groupAuthor, groupCreateTime,
7865  groupType;
7866  for(auto& aliasNodePair : aliasNodePairs)
7867  {
7868  groupName = aliasNodePair.second.getNode("GroupName").getValueAsString();
7869  groupKey = aliasNodePair.second.getNode("GroupKey").getValueAsString();
7870 
7871  xmlOut.addTextElementToData("GroupAlias", aliasNodePair.first);
7872  xmlOut.addTextElementToData("GroupName", groupName);
7873  xmlOut.addTextElementToData("GroupKey", groupKey);
7874  xmlOut.addTextElementToData(
7875  "AliasComment",
7876  aliasNodePair.second.getNode(TableViewColumnInfo::COL_NAME_COMMENT)
7877  .getValueAsString());
7878 
7879  // get group comment
7880  groupComment =
7881  ConfigurationManager::UNKNOWN_INFO; // clear just in case failure
7882  groupType = ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN;
7883  try
7884  {
7885  cfgMgr->loadTableGroup(groupName,
7886  TableGroupKey(groupKey),
7887  false /* doActivate */,
7888  0 /* groupMembers */,
7889  0 /* progressBar */,
7890  0 /* accumulatedWarnings */,
7891  &groupComment,
7892  &groupAuthor,
7893  &groupCreateTime,
7894  true /*doNotLoadMembers*/,
7895  &groupType);
7896  }
7897  catch(...)
7898  {
7899  __SUP_COUT_WARN__ << "Failed to load group '" << groupName << "("
7900  << groupKey << ")' to extract group comment and type."
7901  << __E__;
7902  }
7903  xmlOut.addTextElementToData("GroupComment", groupComment);
7904  xmlOut.addTextElementToData("GroupType", groupType);
7905  } // end alias pair loop
7906  }
7907  else //multi-threading
7908  {
7909  int threadsLaunched = 0;
7910  int foundThreadIndex = 0;
7911  std::vector<std::shared_ptr<std::atomic<bool>>> threadDone;
7912  for(int i = 0; i < numOfThreads; ++i)
7913  threadDone.push_back(std::make_shared<std::atomic<bool>>(true));
7914 
7915  std::vector<std::shared_ptr<ots::GroupInfo>> sharedGroupInfoPtrs;
7916  std::string groupName, groupKey;
7917 
7918  for(auto& aliasNodePair : aliasNodePairs)
7919  {
7920  //make temporary group info for thread
7921  sharedGroupInfoPtrs.push_back(std::make_shared<ots::GroupInfo>());
7922 
7923  groupName = aliasNodePair.second.getNode("GroupName").getValueAsString();
7924  groupKey = aliasNodePair.second.getNode("GroupKey").getValueAsString();
7925 
7926  if(threadsLaunched >= numOfThreads)
7927  {
7928  //find availableThreadIndex
7929  foundThreadIndex = -1;
7930  while(foundThreadIndex == -1)
7931  {
7932  for(int i = 0; i < numOfThreads; ++i)
7933  if(*(threadDone[i]))
7934  {
7935  foundThreadIndex = i;
7936  break;
7937  }
7938  if(foundThreadIndex == -1)
7939  {
7940  __SUP_COUTT__ << "Waiting for available thread..." << __E__;
7941  usleep(10000);
7942  }
7943  } //end thread search loop
7944  threadsLaunched = numOfThreads - 1;
7945  }
7946  __SUP_COUTT__ << "Starting load group thread... " << groupName << "("
7947  << groupKey << ")" << __E__;
7948  *(threadDone[foundThreadIndex]) = false;
7949 
7950  std::thread(
7951  [](ConfigurationManagerRW* theCfgMgr,
7952  std::string theGroupName,
7953  ots::TableGroupKey theGroupKey,
7954  std::shared_ptr<ots::GroupInfo> theGroupInfo,
7955  std::shared_ptr<std::atomic<bool>> theThreadDone) {
7957  theGroupName,
7958  theGroupKey,
7959  theGroupInfo,
7960  theThreadDone);
7961  },
7962  cfgMgr,
7963  groupName,
7964  TableGroupKey(groupKey),
7965  sharedGroupInfoPtrs.back(),
7966  threadDone[foundThreadIndex])
7967  .detach();
7968 
7969  ++threadsLaunched;
7970  ++foundThreadIndex;
7971 
7972  } //end alias group thread loop
7973 
7974  //check for all threads done
7975  do
7976  {
7977  foundThreadIndex = -1;
7978  for(int i = 0; i < numOfThreads; ++i)
7979  if(!*(threadDone[i]))
7980  {
7981  foundThreadIndex = i;
7982  break;
7983  }
7984  if(foundThreadIndex != -1)
7985  {
7986  __SUP_COUTT__ << "Waiting for thread to finish... " << foundThreadIndex
7987  << __E__;
7988  usleep(10000);
7989  }
7990  } while(foundThreadIndex != -1); //end thread done search loop
7991 
7992  //threads done now, so copy group info
7993  size_t i = 0;
7994  for(auto& aliasNodePair : aliasNodePairs)
7995  {
7996  groupName = aliasNodePair.second.getNode("GroupName").getValueAsString();
7997  groupKey = aliasNodePair.second.getNode("GroupKey").getValueAsString();
7998 
7999  if(groupKey != sharedGroupInfoPtrs[i]->getLatestKey().toString())
8000  {
8001  __SUP_SS__ << "Error loading group information for the group alias '"
8002  << aliasNodePair.first << "' mapping to group '" << groupName
8003  << "(" << groupKey << ")" << __E__;
8004  __SUP_SS_THROW__;
8005  }
8006 
8007  xmlOut.addTextElementToData("GroupAlias", aliasNodePair.first);
8008  xmlOut.addTextElementToData("GroupName", groupName);
8009  xmlOut.addTextElementToData(
8010  "GroupKey", sharedGroupInfoPtrs[i]->getLatestKey().toString());
8011  xmlOut.addTextElementToData(
8012  "AliasComment",
8013  aliasNodePair.second.getNode(TableViewColumnInfo::COL_NAME_COMMENT)
8014  .getValueAsString());
8015 
8016  xmlOut.addTextElementToData(
8017  "GroupComment", sharedGroupInfoPtrs[i]->getLatestKeyGroupComment());
8018  xmlOut.addTextElementToData(
8019  "GroupAuthor", sharedGroupInfoPtrs[i]->getLatestKeyGroupAuthor());
8020  xmlOut.addTextElementToData(
8021  "GroupCreationTime",
8022  sharedGroupInfoPtrs[i]->getLatestKeyGroupCreationTime());
8023  xmlOut.addTextElementToData(
8024  "GroupType", sharedGroupInfoPtrs[i]->getLatestKeyGroupTypeString());
8025  // xmlOut.addTextElementToData("GroupMemberMap", sharedGroupInfoPtrs[i]->latestKeyMemberMap_);
8026  ++i;
8027  } //end copy group info loop
8028 
8029  } //end multi-thread handling
8030 } // end handleGroupAliasesXML
8031 
8032 //==============================================================================
8043 void ConfigurationGUISupervisor::handleVersionAliasesXML(HttpXmlDocument& xmlOut,
8044  ConfigurationManagerRW* cfgMgr)
8045 {
8046  cfgMgr->loadConfigurationBackbone();
8047  std::map<std::string, TableVersion> activeVersions = cfgMgr->getActiveVersions();
8048 
8049  std::string versionAliasesTableName =
8050  ConfigurationManager::VERSION_ALIASES_TABLE_NAME;
8051  if(activeVersions.find(versionAliasesTableName) == activeVersions.end())
8052  {
8053  __SUP_SS__ << "Active version of VersionAliases missing!"
8054  << "Make sure you have a valid active Backbone Group." << __E__;
8055  xmlOut.addTextElementToData("Error", ss.str());
8056  return;
8057  }
8058  __SUP_COUT__ << "activeVersions[\"" << versionAliasesTableName
8059  << "\"]=" << activeVersions[versionAliasesTableName] << __E__;
8060  xmlOut.addTextElementToData("VersionAliasesVersion",
8061  activeVersions[versionAliasesTableName].toString());
8062 
8063  std::vector<std::pair<std::string, ConfigurationTree>> aliasNodePairs =
8064  cfgMgr->getNode(versionAliasesTableName).getChildren();
8065 
8066  for(auto& aliasNodePair : aliasNodePairs)
8067  {
8068  // note : these are column names in the versionAliasesTableName table
8069  // VersionAlias, TableName, Version, CommentDescription
8070  xmlOut.addTextElementToData(
8071  "VersionAlias",
8072  aliasNodePair.second.getNode("VersionAlias").getValueAsString());
8073  xmlOut.addTextElementToData(
8074  "TableName", aliasNodePair.second.getNode("TableName").getValueAsString());
8075  xmlOut.addTextElementToData(
8076  "Version", aliasNodePair.second.getNode("Version").getValueAsString());
8077  xmlOut.addTextElementToData(
8078  "Comment",
8079  aliasNodePair.second.getNode(TableViewColumnInfo::COL_NAME_COMMENT)
8080  .getValueAsString());
8081  }
8082 } // end handleVersionAliasesXML()
8083 
8084 //==============================================================================
8090 void ConfigurationGUISupervisor::handleGetTableGroupTypeXML(
8091  HttpXmlDocument& xmlOut, ConfigurationManagerRW* cfgMgr, const std::string& tableList)
8092 {
8093  std::map<std::string /*name*/, TableVersion /*version*/> memberMap;
8094  std::string name, versionStr;
8095  auto c = tableList.find(',', 0);
8096  auto i = c;
8097  i = 0; // auto used to get proper index/length type
8098  while(c < tableList.length())
8099  {
8100  // add the table name and version pair to the map
8101  name = tableList.substr(i, c - i);
8102  i = c + 1;
8103  c = tableList.find(',', i);
8104  if(c == std::string::npos) // missing version list entry?!
8105  {
8106  __SUP_SS__ << "Incomplete Table Name-Version pair!" << __E__;
8107  __SUP_COUT_ERR__ << "\n" << ss.str();
8108  xmlOut.addTextElementToData("Error", ss.str());
8109  return;
8110  }
8111 
8112  versionStr = tableList.substr(i, c - i);
8113  i = c + 1;
8114  c = tableList.find(',', i);
8115 
8116  memberMap[name] = TableVersion(versionStr);
8117  }
8118 
8119  std::string groupTypeString = "";
8120  // try to determine type, dont report errors, just mark ots::GroupType::UNKNOWN_TYPE
8121  try
8122  {
8123  // determine the type of the table group
8124  groupTypeString = cfgMgr->getTypeNameOfGroup(memberMap);
8125  xmlOut.addTextElementToData("TableGroupType", groupTypeString);
8126  }
8127  catch(std::runtime_error& e)
8128  {
8129  __SUP_SS__ << "Table group has invalid type! " << e.what() << __E__;
8130  __SUP_COUT__ << "\n" << ss.str();
8131  groupTypeString = ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN;
8132  xmlOut.addTextElementToData("TableGroupType", groupTypeString);
8133  }
8134  catch(...)
8135  {
8136  __SUP_SS__ << "Table group has invalid type! " << __E__;
8137  try
8138  {
8139  throw;
8140  } //one more try to printout extra info
8141  catch(const std::exception& e)
8142  {
8143  ss << "Exception message: " << e.what();
8144  }
8145  catch(...)
8146  {
8147  }
8148  __SUP_COUT__ << "\n" << ss.str();
8149  groupTypeString = ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN;
8150  xmlOut.addTextElementToData("TableGroupType", groupTypeString);
8151  }
8152 } //end handleGetTableGroupTypeXML()
8153 
8154 //==============================================================================
8170 void ConfigurationGUISupervisor::handleTableGroupsXML(HttpXmlDocument& xmlOut,
8171  ConfigurationManagerRW* cfgMgr,
8172  bool returnMembers)
8173 {
8174  __SUP_COUTT__ << "cfgMgr runtime=" << cfgMgr->runTimeSeconds() << __E__;
8175  // 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
8176  // xercesc::DOMElement* parentEl;
8177 
8178  // get all group info from cache (if no cache, get from interface)
8179 
8180  if(!cfgMgr->getAllGroupInfo().size() ||
8181  cfgMgr->getAllGroupInfo().begin()->second.getLatestKeyGroupTypeString() == "" ||
8182  cfgMgr->getAllGroupInfo().begin()->second.getLatestKeyGroupTypeString() ==
8183  ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN ||
8184  ( //if active Context group type is not defined, then refresh
8185  cfgMgr->getActiveGroupKey(ConfigurationManager::GroupType::CONTEXT_TYPE) !=
8186  TableGroupKey::INVALID &&
8187  cfgMgr->getAllGroupInfo().find(cfgMgr->getActiveGroupName(
8188  ConfigurationManager::GroupType::CONTEXT_TYPE)) !=
8189  cfgMgr->getAllGroupInfo().end() &&
8190  (cfgMgr->getAllGroupInfo()
8191  .at(cfgMgr->getActiveGroupName(
8192  ConfigurationManager::GroupType::CONTEXT_TYPE))
8193  .getLatestKeyGroupTypeString() == "" ||
8194  cfgMgr->getAllGroupInfo()
8195  .at(cfgMgr->getActiveGroupName(
8196  ConfigurationManager::GroupType::CONTEXT_TYPE))
8197  .getLatestKeyGroupTypeString() ==
8198  ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN)) ||
8199  ( //if active Config group type is not defined, then refresh
8200  cfgMgr->getActiveGroupKey(
8201  ConfigurationManager::GroupType::CONFIGURATION_TYPE) !=
8202  TableGroupKey::INVALID &&
8203  cfgMgr->getAllGroupInfo().find(cfgMgr->getActiveGroupName(
8204  ConfigurationManager::GroupType::CONFIGURATION_TYPE)) !=
8205  cfgMgr->getAllGroupInfo().end() &&
8206  (cfgMgr->getAllGroupInfo()
8207  .at(cfgMgr->getActiveGroupName(
8208  ConfigurationManager::GroupType::CONFIGURATION_TYPE))
8209  .getLatestKeyGroupTypeString() == "" ||
8210  cfgMgr->getAllGroupInfo()
8211  .at(cfgMgr->getActiveGroupName(
8212  ConfigurationManager::GroupType::CONFIGURATION_TYPE))
8213  .getLatestKeyGroupTypeString() ==
8214  ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN)))
8215  {
8216  __SUP_COUT__
8217  << "Group Info cache appears empty or stale. Attempting to regenerate..."
8218  << __E__;
8219  cfgMgr->getAllTableInfo(true /*refresh*/,
8220  0 /* accumulatedWarnings */,
8221  "" /* errorFilterName */,
8222  true /* getGroupKeys */,
8223  true /* getGroupInfo */,
8224  true /* initializeActiveGroups */);
8225  }
8226 
8227  const std::map<std::string, GroupInfo>& allGroupInfo = cfgMgr->getAllGroupInfo();
8228 
8229  __SUP_COUTT__ << "cfgMgr runtime=" << cfgMgr->runTimeSeconds() << __E__;
8230 
8231  std::string groupName;
8232  std::string groupString, groupTypeString, groupComment, groupCreationTime,
8233  groupAuthor;
8234  for(auto& groupInfo : allGroupInfo)
8235  {
8236  groupName = groupInfo.first;
8237 
8238  //get group info and force update of groupKeys from DB Interface cache if possible
8239  cfgMgr->getGroupInfo(groupName, true /* attemptToReloadKeys */);
8240 
8241  if(groupInfo.second.getKeys().size() == 0)
8242  {
8243  __SUP_COUT__ << "Group name '" << groupName
8244  << "' found, but no keys so ignoring." << __E__;
8245  continue;
8246  }
8247 
8248  xmlOut.dataSs_ << "<TableGroupName value='" << groupName << "'/>" << __E__;
8249  xmlOut.dataSs_ << "<TableGroupKey value='" << groupInfo.second.getLatestKey()
8250  << "'/>" << __E__;
8251 
8252  // trusting the cache!
8253  xmlOut.dataSs_ << "<TableGroupType value='"
8254  << groupInfo.second.getLatestKeyGroupTypeString() << "'/>"
8255  << __E__;
8256  xmlOut.dataSs_ << "<TableGroupComment value='"
8258  groupInfo.second.getLatestKeyGroupComment(),
8259  true /* allowWhiteSpace */)
8260  << "'/>" << __E__;
8261  xmlOut.dataSs_ << "<TableGroupAuthor value='"
8262  << groupInfo.second.getLatestKeyGroupAuthor() << "'/>" << __E__;
8263  xmlOut.dataSs_ << "<TableGroupCreationTime value='"
8264  << groupInfo.second.getLatestKeyGroupCreationTime() << "'/>"
8265  << __E__;
8266 
8267  if(returnMembers)
8268  {
8269  // parentEl = xmlOut.addTextElementToData("TableGroupMembers", "");
8270  xmlOut.dataSs_ << "<TableGroupMembers value=''>" << __E__;
8271 
8272  for(auto& memberPair : groupInfo.second.getLatestKeyMemberMap())
8273  {
8274  xmlOut.dataSs_ << "\t<MemberName value='" << memberPair.first << "'/>"
8275  << __E__;
8276  xmlOut.dataSs_ << "\t<MemberVersion value='" << memberPair.second << "'/>"
8277  << __E__;
8278 
8279  // xmlOut.addTextElementToParent("MemberName", memberPair.first, parentEl);
8280  // xmlOut.addTextElementToParent(
8281  // "MemberVersion", memberPair.second.toString(), parentEl);
8282  }
8283  xmlOut.dataSs_ << "</TableGroupMembers>" << __E__;
8284  } // end if returnMembers
8285 
8286  // add other group keys to xml for this group name
8287  // but just empty members (not displayed anyway)
8288  for(auto& keyInSet : groupInfo.second.getKeys())
8289  {
8290  if(keyInSet == groupInfo.second.getLatestKey())
8291  continue; // skip the lastest
8292 
8293  xmlOut.dataSs_ << "<TableGroupName value='" << groupName << "'/>" << __E__;
8294  xmlOut.dataSs_ << "<TableGroupKey value='" << keyInSet << "'/>" << __E__;
8295  // xmlOut.addTextElementToData("TableGroupName", groupName);
8296  // xmlOut.addTextElementToData("TableGroupKey", keyInSet.toString());
8297 
8298  // TODO -- make loadingHistoricalInfo an input parameter
8299  bool loadingHistoricalInfo = false;
8300  if(loadingHistoricalInfo)
8301  {
8302  groupComment = ""; // clear just in case failure
8303  try
8304  {
8305  cfgMgr->loadTableGroup(groupName,
8306  keyInSet,
8307  0,
8308  0,
8309  0,
8310  0,
8311  &groupComment,
8312  0,
8313  0, // mostly defaults
8314  true /*doNotLoadMembers*/,
8315  &groupTypeString);
8316  }
8317  catch(...)
8318  {
8319  groupTypeString = ConfigurationManager::GROUP_TYPE_NAME_UNKNOWN;
8320  __SUP_COUT_WARN__ << "Failed to load group '" << groupName << "("
8321  << keyInSet
8322  << ")' to extract group comment and type." << __E__;
8323  }
8324 
8325  xmlOut.dataSs_ << "<TableGroupType value='" << groupTypeString << "'/>"
8326  << __E__;
8327  xmlOut.dataSs_ << "<TableGroupComment value='"
8328  << StringMacros::escapeString(groupComment,
8329  true /* allowWhiteSpace */)
8330  << "'/>" << __E__;
8331  xmlOut.dataSs_ << "<TableGroupAuthor value='" << groupAuthor << "'/>"
8332  << __E__;
8333  xmlOut.dataSs_ << "<TableGroupCreationTime value='" << groupCreationTime
8334  << "'/>" << __E__;
8335  // xmlOut.addTextElementToData("TableGroupType", groupTypeString);
8336  // xmlOut.addTextElementToData("TableGroupComment", groupComment);
8337  // xmlOut.addTextElementToData("TableGroupAuthor", groupAuthor);
8338  // xmlOut.addTextElementToData("TableGroupCreationTime", groupCreationTime);
8339  }
8340  else
8341  {
8342  // just use guess that historical groups are of same type
8343  xmlOut.dataSs_ << "<TableGroupType value='"
8344  << groupInfo.second.getLatestKeyGroupTypeString() << "'/>"
8345  << __E__;
8346  //leave place holder comment,author,time for javascript parsing
8347  xmlOut.dataSs_ << "<TableGroupComment value='"
8348  << ""
8349  << "'/>" << __E__;
8350  xmlOut.dataSs_ << "<TableGroupAuthor value='"
8351  << ""
8352  << "'/>" << __E__;
8353  xmlOut.dataSs_ << "<TableGroupCreationTime value='"
8354  << ""
8355  << "'/>" << __E__;
8356  }
8357 
8358  if(returnMembers)
8359  {
8360  //need to add empty group members, event for historical groups, for easier Javascript extraction
8361  xmlOut.dataSs_ << "<TableGroupMembers/>" << __E__;
8362  // xmlOut.addTextElementToData("TableGroupMembers", "");
8363  }
8364 
8365  } // end other key loop
8366  __SUP_COUTT__ << groupName << " runtime=" << cfgMgr->runTimeSeconds() << __E__;
8367  } // end primary group loop
8368  __SUP_COUTT__ << "cfgMgr runtime=" << cfgMgr->runTimeSeconds() << __E__;
8369 } // end handleTableGroupsXML()
8370 
8371 //==============================================================================
8390 void ConfigurationGUISupervisor::handleTablesXML(HttpXmlDocument& xmlOut,
8391  ConfigurationManagerRW* cfgMgr,
8392  const std::string& filterStartTimeStr,
8393  const std::string& filterEndTimeStr,
8394  const std::string& filterMode)
8395 {
8396  time_t filterStartTime = 0;
8397  time_t filterEndTime = 0;
8398  if(filterStartTimeStr != "")
8399  {
8400  try
8401  {
8402  filterStartTime = static_cast<time_t>(std::stoll(filterStartTimeStr));
8403  }
8404  catch(const std::exception& e)
8405  {
8406  __SUP_SS__ << "Error parsing startTime parameter: " << e.what() << __E__;
8407  __SUP_COUT_ERR__ << "\n" << ss.str();
8408  xmlOut.addTextElementToData("Error", ss.str());
8409  return;
8410  }
8411  }
8412  if(filterEndTimeStr != "")
8413  {
8414  try
8415  {
8416  filterEndTime = static_cast<time_t>(std::stoll(filterEndTimeStr));
8417  }
8418  catch(const std::exception& e)
8419  {
8420  __SUP_SS__ << "Error parsing endTime parameter: " << e.what() << __E__;
8421  __SUP_COUT_ERR__ << "\n" << ss.str();
8422  xmlOut.addTextElementToData("Error", ss.str());
8423  return;
8424  }
8425  }
8426  if(filterStartTime != 0 && filterEndTime != 0 && filterStartTime > filterEndTime)
8427  {
8428  __SUP_SS__ << "Invalid time range: startTime (" << filterStartTime
8429  << ") must be <= endTime (" << filterEndTime << ")." << __E__;
8430  __SUP_SS_THROW__;
8431  }
8432  if(filterMode != "created" && filterMode != "loaded")
8433  {
8434  __SUP_SS__ << "Invalid filterMode parameter '" << filterMode
8435  << ".' Expected 'created' or 'loaded.'" << __E__;
8436  __SUP_COUT_ERR__ << "\n" << ss.str();
8437  xmlOut.addTextElementToData("Error", ss.str());
8438  return;
8439  }
8440  // diagnostics: track where the time is going
8441  const auto diagStartTime = std::chrono::steady_clock::now();
8442  auto diagElapsedSec = [](const std::chrono::steady_clock::time_point& start) {
8443  return std::chrono::duration<double>(std::chrono::steady_clock::now() - start)
8444  .count();
8445  };
8446 
8447  if(cfgMgr->getAllGroupInfo().size() == 0 || cfgMgr->getActiveVersions().size() == 0)
8448  {
8449  __SUP_COUT__ << "Table Info cache appears empty. Attempting to regenerate."
8450  << __E__;
8451  cfgMgr->getAllTableInfo(true /*refresh*/,
8452  0 /* accumulatedWarnings */,
8453  "" /* errorFilterName */,
8454  false /* getGroupKeys */,
8455  false /* getGroupInfo */,
8456  true /* initializeActiveGroups */);
8457  __SUP_COUT__ << "getAllTableInfo() regenerate took "
8458  << diagElapsedSec(diagStartTime) << " s" << __E__;
8459  }
8460 
8461  xercesc::DOMElement* parentEl;
8462  const std::map<std::string, TableInfo>& allTableInfo = cfgMgr->getAllTableInfo();
8463 
8464  // construct specially ordered table name set
8465  std::set<std::string, StringMacros::IgnoreCaseCompareStruct> orderedTableSet;
8466  for(const auto& tablePair : allTableInfo)
8467  orderedTableSet.emplace(tablePair.first);
8468 
8469  // std::map<std::string, TableInfo>::const_iterator it = allTableInfo.begin();
8470 
8471  __SUP_COUT__ << "# of tables to consider: " << allTableInfo.size() << __E__;
8472 
8473  const auto diagAliasStartTime = std::chrono::steady_clock::now();
8474  std::map<std::string, std::map<std::string, TableVersion>> versionAliases =
8475  cfgMgr->getVersionAliases();
8476 
8477  __SUP_COUT__ << "# of tables w/aliases: " << versionAliases.size() << " (took "
8478  << diagElapsedSec(diagAliasStartTime) << " s)" << __E__;
8479 
8480  if(filterStartTime != 0 && filterEndTime != 0 && filterMode == "created")
8481  {
8482  // parallel pre-warm of the process-wide creation time cache, so the
8483  // filter loop below gets immediate cache hits (only versions not yet in
8484  // the disk-persisted cache are loaded, so this is cheap after first use)
8485  const auto diagPreloadStartTime = std::chrono::steady_clock::now();
8486  cfgMgr->preloadVersionCreationTimes();
8487  __SUP_COUT__ << "Version creation time cache pre-warm took "
8488  << diagElapsedSec(diagPreloadStartTime) << " s" << __E__;
8489  }
8490 
8491  // diagnostics accumulated over the table loop
8492  size_t diagNumTablesFound = 0; //tables with at least one version listed
8493  size_t diagNumVersionsConsidered = 0;
8494  size_t diagNumVersionsMatched = 0;
8495  size_t diagNumTimeLookups = 0;
8496  double diagTimeLookupSec = 0; //cumulative time in creation/load time lookups
8497  double diagSlowestTableSec = 0;
8498  std::string diagSlowestTableName = "";
8499  size_t diagTableCount = 0;
8500 
8501  for(const auto& orderedTableName : orderedTableSet) // while(it !=
8502  // allTableInfo.end())
8503  {
8504  std::map<std::string, TableInfo>::const_iterator it =
8505  allTableInfo.find(orderedTableName);
8506  if(it == allTableInfo.end())
8507  {
8508  __SS__ << "Impossible missing table in map '" << orderedTableName << "'"
8509  << __E__;
8510  __SS_THROW__;
8511  }
8512 
8513  // for each table name
8514  // get existing version keys
8515 
8516  // add system table name
8517  xmlOut.addTextElementToData("TableName", it->first);
8518  parentEl = xmlOut.addTextElementToData("TableVersions", "");
8519 
8520  // include aliases for this table (if the versions exist)
8521  if(versionAliases.find(it->first) != versionAliases.end())
8522  for(auto& aliasVersion : versionAliases[it->first])
8523  if(it->second.versions_.find(aliasVersion.second) !=
8524  it->second.versions_
8525  .end()) //Note : scratch version is always an alias ==> ConfigurationManager::SCRATCH_VERSION_ALIAS)
8526  xmlOut.addTextElementToParent(
8527  "Version",
8528  ConfigurationManager::ALIAS_VERSION_PREAMBLE + aliasVersion.first,
8529  parentEl);
8530 
8531  // get all table versions for the current table
8532  // except skip scratch version
8533  // for speed, group versions into spans:
8534  //======
8536  auto vSpanToXML = [&diagNumVersionsConsidered,
8537  &diagNumVersionsMatched,
8538  &diagNumTimeLookups,
8539  &diagTimeLookupSec](auto const& sortedKeys,
8540  auto& xmlOut,
8541  auto& configEl,
8542  const std::string& tableName,
8543  ConfigurationManagerRW* cfgMgr,
8544  const time_t filterStartTime,
8545  const time_t filterEndTime,
8546  const std::string& filterMode) {
8547  //add lo and hi spans, instead of each individual value
8548  size_t lo = -1, hi = -1;
8549  bool allVersionsFiltered = true;
8550  for(auto& keyInOrder : sortedKeys)
8551  {
8552  //skip scratch version
8553  if(keyInOrder.isScratchVersion())
8554  continue;
8555 
8556  ++diagNumVersionsConsidered;
8557 
8558  if(filterStartTime != 0 && filterEndTime != 0)
8559  {
8560  const auto diagLookupStartTime = std::chrono::steady_clock::now();
8561  try
8562  {
8563  // Note: neither helper stamps the version's lastAccessTime
8564  // ("Last Load"), so filtering does not corrupt Last Load times.
8565  // - "created" times are immutable and cached, so each version is
8566  // loaded from the database at most once per process lifetime.
8567  // - "loaded" times are in-memory only; 0 (never loaded by this
8568  // process, or evicted from cache) falls outside any range.
8569  time_t tableVersionTime =
8570  filterMode == "loaded"
8571  ? cfgMgr->getVersionLastAccessTime(tableName, keyInOrder)
8572  : cfgMgr->getVersionCreationTime(tableName, keyInOrder);
8573 
8574  ++diagNumTimeLookups;
8575  diagTimeLookupSec +=
8576  std::chrono::duration<double>(
8577  std::chrono::steady_clock::now() - diagLookupStartTime)
8578  .count();
8579 
8580  if(tableVersionTime < filterStartTime ||
8581  tableVersionTime > filterEndTime)
8582  {
8583  //Note: trace-level to avoid log flooding (one line per
8584  // filtered version can be thousands of lines)
8585  __COUTT__ << "Table '" << tableName << "' version v"
8586  << keyInOrder << " " << filterMode
8587  << " time is outside the filter range, so "
8588  "skipping."
8589  << __E__;
8590  continue;
8591  }
8592  }
8593  catch(const std::runtime_error&)
8594  {
8595  ++diagNumTimeLookups;
8596  diagTimeLookupSec +=
8597  std::chrono::duration<double>(
8598  std::chrono::steady_clock::now() - diagLookupStartTime)
8599  .count();
8600  __COUT__ << "Failed to get " << filterMode << " time for table '"
8601  << tableName << "' version v" << keyInOrder
8602  << ", so skipping." << __E__;
8603  continue;
8604  }
8605  }
8606 
8607  allVersionsFiltered = false;
8608  ++diagNumVersionsMatched;
8609 
8610  if(lo == size_t(-1)) //establish start of potential span
8611  {
8612  hi = lo = keyInOrder.version();
8613  continue;
8614  }
8615  else if(hi + 1 == keyInOrder.version()) //span is growing
8616  {
8617  hi = keyInOrder.version();
8618  continue;
8619  }
8620  //else jump by more than one, so close out span
8621 
8622  if(lo == hi) //single value
8623  xmlOut.addNumberElementToParent("Version", lo, configEl);
8624  else //span
8625  xmlOut.addTextElementToParent(
8626  "Version",
8627  "_" + std::to_string(lo) + "_" + std::to_string(hi),
8628  configEl);
8629  hi = lo = keyInOrder.version();
8630  }
8631 
8632  if(lo != size_t(-1)) //check if last one to do!
8633  {
8634  if(lo == hi) //single value
8635  xmlOut.addNumberElementToParent("Version", lo, configEl);
8636  else //span
8637  xmlOut.addTextElementToParent(
8638  "Version",
8639  "_" + std::to_string(lo) + "_" + std::to_string(hi),
8640  configEl);
8641  }
8642  return allVersionsFiltered;
8643  }; //end local lambda vSpanToXML()
8644 
8645  const auto diagTableStartTime = std::chrono::steady_clock::now();
8646  if(vSpanToXML(it->second.versions_,
8647  xmlOut,
8648  parentEl,
8649  it->first,
8650  cfgMgr,
8651  filterStartTime,
8652  filterEndTime,
8653  filterMode) &&
8654  filterStartTime != 0 && filterEndTime != 0)
8655  {
8656  // Only remove tables when a time filter is active; without a filter,
8657  // tables with no persistent versions (e.g. definition-only tables)
8658  // should still be listed, as in the unfiltered Table View.
8659  // Remove the pair we just added: TableVersions then TableName.
8660  unsigned int childCount = xmlOut.getChildrenCount();
8661  if(childCount >= 2)
8662  {
8663  xmlOut.removeDataElement(childCount - 1);
8664  xmlOut.removeDataElement(childCount - 2);
8665  }
8666  }
8667  else
8668  ++diagNumTablesFound;
8669 
8670  // diagnostics: report slow tables and periodic progress
8671  double diagTableSec = diagElapsedSec(diagTableStartTime);
8672  if(diagTableSec > diagSlowestTableSec)
8673  {
8674  diagSlowestTableSec = diagTableSec;
8675  diagSlowestTableName = it->first;
8676  }
8677  if(diagTableSec > 1.0)
8678  __SUP_COUT__ << "Slow table filter: '" << it->first << "' with "
8679  << it->second.versions_.size() << " versions took "
8680  << diagTableSec << " s" << __E__;
8681  ++diagTableCount;
8682  if(diagTableCount % 50 == 0)
8683  __SUP_COUT__ << "getTables filter progress: " << diagTableCount << " of "
8684  << orderedTableSet.size() << " tables in "
8685  << diagElapsedSec(diagStartTime) << " s (" << diagNumTimeLookups
8686  << " version time lookups taking " << diagTimeLookupSec
8687  << " s so far)" << __E__;
8688 
8689  } // end table loop
8690 
8691  // always return the table and version counts, even if everything was filtered out
8692  xmlOut.addTextElementToData("NumberOfTablesConsidered",
8693  std::to_string(allTableInfo.size()));
8694  xmlOut.addTextElementToData("NumberOfTablesFound",
8695  std::to_string(diagNumTablesFound));
8696  xmlOut.addTextElementToData("NumberOfVersionsConsidered",
8697  std::to_string(diagNumVersionsConsidered));
8698  xmlOut.addTextElementToData("NumberOfVersionsFound",
8699  std::to_string(diagNumVersionsMatched));
8700 
8701  __SUP_COUT__ << "getTables filter summary: mode=" << filterMode << " range=["
8702  << filterStartTime << "," << filterEndTime << "]"
8703  << " tables considered=" << allTableInfo.size()
8704  << " found=" << diagNumTablesFound
8705  << "; versions considered=" << diagNumVersionsConsidered
8706  << " matched=" << diagNumVersionsMatched << "; " << diagNumTimeLookups
8707  << " version time lookups took " << diagTimeLookupSec
8708  << " s; slowest table '" << diagSlowestTableName << "' took "
8709  << diagSlowestTableSec << " s; total " << diagElapsedSec(diagStartTime)
8710  << " s" << __E__;
8711 
8712 } // end handleTablesXML()
8713 
8714 //==============================================================================
8723 void ConfigurationGUISupervisor::handleGetArtdaqNodeRecordsXML(
8724  HttpXmlDocument& xmlOut,
8725  ConfigurationManagerRW* cfgMgr,
8726  const std::string& modifiedTables,
8727  const std::string& tableGroup,
8728  TableGroupKey tableGroupKey,
8729  const std::string& contextGroup,
8730  TableGroupKey contextGroupKey,
8731  bool suppressMultiNode)
8732 {
8733  __COUT__ << "Retrieving artdaq nodes..." << __E__;
8734 
8735  // setup active tables based on specified or active groups and modified tables
8736  setupActiveTablesXML(xmlOut,
8737  cfgMgr,
8738  tableGroup,
8739  tableGroupKey,
8740  modifiedTables,
8741  false /* refreshAll */);
8742 
8743  if(contextGroup != "" && !contextGroupKey.isInvalid())
8744  {
8745  cfgMgr->loadTableGroup(contextGroup, contextGroupKey, false /*doActivate*/);
8746  }
8747 
8748  {
8749  TableBase* ctxTableBase =
8750  cfgMgr->getTableByName(ConfigurationManager::XDAQ_CONTEXT_TABLE_NAME);
8751  XDAQContextTable* ctxTable = dynamic_cast<XDAQContextTable*>(ctxTableBase);
8752  if(!ctxTable)
8753  {
8754  __SUP_SS__ << "Failed to cast "
8755  << ConfigurationManager::XDAQ_CONTEXT_TABLE_NAME
8756  << " to XDAQContextTable." << __E__;
8757  __SS_THROW__;
8758  }
8759  ctxTable->extractContexts(cfgMgr);
8760  }
8761 
8762  std::map<std::string /*type*/,
8763  std::map<std::string /*record*/, std::vector<std::string /*property*/>>>
8764  nodeTypeToObjectMap;
8765  std::map<std::string /*subsystemName*/, std::string /*destinationSubsystemName*/>
8766  subsystemObjectMap;
8767 
8768  std::vector<std::string /*property*/> artdaqSupervisorInfo;
8769 
8770  std::string artdaqSupervisorName;
8771  const ARTDAQTableBase::ARTDAQInfo& info =
8773  nodeTypeToObjectMap,
8774  subsystemObjectMap,
8775  artdaqSupervisorInfo,
8776  suppressMultiNode);
8777 
8778  if(artdaqSupervisorInfo.size() != 4 /*expecting 4 artdaq Supervisor parameters*/)
8779  {
8780  __SUP_COUT__ << "No artdaq supervisor found." << __E__;
8781  return;
8782  }
8783 
8784  __SUP_COUT__ << "========== "
8785  << "Found " << info.subsystems.size() << " subsystems." << __E__;
8786 
8787  unsigned int paramIndex = 0; // start at first artdaq Supervisor parameter
8788 
8789  auto parentEl = xmlOut.addTextElementToData("artdaqSupervisor",
8790  artdaqSupervisorInfo[paramIndex++]);
8791 
8792  std::string typeString = "artdaqSupervisor";
8793 
8794  xmlOut.addTextElementToParent(
8795  typeString + "-status", artdaqSupervisorInfo[paramIndex++], parentEl);
8796  xmlOut.addTextElementToParent(
8797  typeString + "-contextAddress", artdaqSupervisorInfo[paramIndex++], parentEl);
8798  xmlOut.addTextElementToParent(
8799  typeString + "-contextPort", artdaqSupervisorInfo[paramIndex++], parentEl);
8800 
8801  for(auto& subsystem : info.subsystems)
8802  {
8803  typeString = "subsystem";
8804 
8805  __SUP_COUT__ << "\t\t"
8806  << "Found " << typeString << " " << subsystem.first << " \t := '"
8807  << subsystem.second.label << "'" << __E__;
8808 
8809  xmlOut.addTextElementToParent(typeString, subsystem.second.label, parentEl);
8810  xmlOut.addTextElementToParent(
8811  typeString + "-id", std::to_string(subsystem.first), parentEl);
8812 
8813  xmlOut.addTextElementToParent(typeString + "-sourcesCount",
8814  std::to_string(subsystem.second.sources.size()),
8815  parentEl);
8816 
8817  // destination
8818  xmlOut.addTextElementToParent(typeString + "-destination",
8819  std::to_string(subsystem.second.destination),
8820  parentEl);
8821 
8822  } // end subsystem handling
8823 
8824  __SUP_COUT__ << "========== "
8825  << "Found " << nodeTypeToObjectMap.size() << " process types." << __E__;
8826 
8827  for(auto& nameTypePair : nodeTypeToObjectMap)
8828  {
8829  typeString = nameTypePair.first;
8830 
8831  __SUP_COUT__ << "\t"
8832  << "Found " << nameTypePair.second.size() << " " << typeString
8833  << "(s)" << __E__;
8834 
8835  for(auto& artdaqNode : nameTypePair.second)
8836  {
8837  __SUP_COUT__ << "\t\t"
8838  << "Found '" << artdaqNode.first << "' " << typeString << __E__;
8839  __SUP_COUTV__(StringMacros::vectorToString(artdaqNode.second));
8840 
8841  if(artdaqNode.second.size() < 2)
8842  {
8843  __SUP_SS__ << "Impossible parameter size for node '" << artdaqNode.first
8844  << "' " << typeString << " - please notify admins!" << __E__;
8845  __SUP_SS_THROW__;
8846  }
8847 
8848  auto nodeEl =
8849  xmlOut.addTextElementToParent(typeString, artdaqNode.first, parentEl);
8850 
8851  paramIndex = 3; // start at 3 after subsystem parameter
8852  if(artdaqNode.second.size() > paramIndex)
8853  {
8854  __SUP_COUTT__ << "\t\t\t"
8855  << "-multinode: " << artdaqNode.second[paramIndex] << __E__;
8856  xmlOut.addTextElementToParent(
8857  typeString + "-multinode", artdaqNode.second[paramIndex++], nodeEl);
8858  }
8859  if(artdaqNode.second.size() > paramIndex)
8860  {
8861  __SUP_COUTT__ << "\t\t\t"
8862  << "-nodefixedwidth: " << artdaqNode.second[paramIndex]
8863  << __E__;
8864  xmlOut.addTextElementToParent(typeString + "-nodefixedwidth",
8865  artdaqNode.second[paramIndex++],
8866  nodeEl);
8867  }
8868  if(artdaqNode.second.size() > paramIndex)
8869  {
8870  __SUP_COUTT__ << "\t\t\t"
8871  << "-hostarray: " << artdaqNode.second[paramIndex] << __E__;
8872  xmlOut.addTextElementToParent(
8873  typeString + "-hostarray", artdaqNode.second[paramIndex++], nodeEl);
8874  }
8875  if(artdaqNode.second.size() > paramIndex)
8876  {
8877  __SUP_COUTT__ << "\t\t\t"
8878  << "-hostfixedwidth: " << artdaqNode.second[paramIndex]
8879  << __E__;
8880  xmlOut.addTextElementToParent(typeString + "-hostfixedwidth",
8881  artdaqNode.second[paramIndex++],
8882  nodeEl);
8883  }
8884 
8885  paramIndex = 0; // return to starting parameter
8886  __SUP_COUTT__ << "\t\t\t"
8887  << "-status: " << artdaqNode.second[paramIndex] << __E__;
8888  xmlOut.addTextElementToParent(
8889  typeString + "-status", artdaqNode.second[paramIndex++], parentEl);
8890  __SUP_COUTT__ << "\t\t\t"
8891  << "-hostname: " << artdaqNode.second[paramIndex] << __E__;
8892  xmlOut.addTextElementToParent(
8893  typeString + "-hostname", artdaqNode.second[paramIndex++], parentEl);
8894  __SUP_COUTT__ << "\t\t\t"
8895  << "-subsystem: " << artdaqNode.second[paramIndex] << __E__;
8896  xmlOut.addTextElementToParent(
8897  typeString + "-subsystem", artdaqNode.second[paramIndex], parentEl);
8898  }
8899  } // end processor type handling
8900 
8901  __SUP_COUT__ << "Done retrieving artdaq nodes." << __E__;
8902 
8903 } // end handleGetArtdaqNodeRecordsXML()
8904 
8905 //==============================================================================
8912 void ConfigurationGUISupervisor::handleSaveArtdaqNodeRecordsXML(
8913  const std::string& nodeString,
8914  const std::string& subsystemString,
8915  HttpXmlDocument& xmlOut,
8916  ConfigurationManagerRW* cfgMgr,
8917  const std::string& modifiedTables)
8918 {
8919  __SUP_COUT__ << "Saving artdaq nodes..." << __E__;
8920 
8921  // setup active tables based on active groups and modified tables
8922  setupActiveTablesXML(
8923  xmlOut, cfgMgr, "", TableGroupKey(-1), modifiedTables, false /* refreshAll */);
8924 
8925  // start node object extraction from nodeString
8926  std::map<std::string /*type*/,
8927  std::map<std::string /*record*/, std::vector<std::string /*property*/>>>
8928  nodeTypeToObjectMap;
8929  {
8930  // nodeString format:
8931  // <type>:<nodeName>=<originalName>,<hostname>,<subsystemName>;<nodeName>=<originalName>,<hostname>,<subsystemName>;
8932  // ... |<type>:...|
8933  // repeat | separated types
8934  std::map<std::string /*type*/, std::string /*typeRecordSetString*/>
8935  nodeTypeToStringMap;
8936  StringMacros::getMapFromString(nodeString, nodeTypeToStringMap, {'|'}, {':'});
8937 
8938  __SUP_COUTV__(StringMacros::mapToString(nodeTypeToStringMap));
8939 
8940  for(auto& typePair : nodeTypeToStringMap)
8941  {
8942  if(typePair.first == "")
8943  continue; // skip empty names
8944 
8945  __SUP_COUTV__(StringMacros::decodeURIComponent(typePair.first));
8946 
8947  nodeTypeToObjectMap.emplace(
8948  std::make_pair(StringMacros::decodeURIComponent(typePair.first),
8949  std::map<std::string /*record*/,
8950  std::vector<std::string /*property*/>>()));
8951 
8952  std::map<std::string /*node*/, std::string /*nodeRecordSetString*/>
8953  nodeRecordToStringMap;
8954 
8956  typePair.second, nodeRecordToStringMap, {';'}, {'='});
8957 
8958  __SUP_COUTV__(StringMacros::mapToString(nodeRecordToStringMap));
8959 
8960  for(auto& nodePair : nodeRecordToStringMap)
8961  {
8962  if(nodePair.first == "")
8963  continue; // skip empty names
8964 
8965  __SUP_COUTV__(StringMacros::decodeURIComponent(nodePair.first));
8966 
8967  std::vector<std::string /*property*/> nodePropertyVector;
8968 
8970  nodePair.second, nodePropertyVector, {','});
8971 
8972  __SUP_COUTV__(StringMacros::vectorToString(nodePropertyVector));
8973 
8974  // decode all properties
8975  for(unsigned int i = 0; i < nodePropertyVector.size(); ++i)
8976  {
8977  __SUP_COUTV__(
8978  StringMacros::decodeURIComponent(nodePropertyVector[i]));
8979 
8980  nodePropertyVector[i] =
8981  StringMacros::decodeURIComponent(nodePropertyVector[i]);
8982  }
8983 
8984  nodeTypeToObjectMap[typePair.first].emplace(
8985  std::make_pair(StringMacros::decodeURIComponent(nodePair.first),
8986  nodePropertyVector));
8987  }
8988  }
8989  } // end node object extraction from nodeString
8990 
8991  // start subsystem object extraction from subsystemString
8992  std::map<std::string /*subsystemName*/, std::string /*destinationSubsystemName*/>
8993  subsystemObjectMap;
8994  {
8995  // subsystemString format:
8996  // <name>:<destination>;<name>:<destination>; ...;
8997  // repeat ; separated subsystems
8998 
8999  std::map<std::string /*subsystemName*/, std::string /*destinationSubsystemName*/>
9000  tmpSubsystemObjectMap;
9002  subsystemString, tmpSubsystemObjectMap, {';'}, {':'});
9003 
9004  __SUP_COUTV__(StringMacros::mapToString(tmpSubsystemObjectMap));
9005 
9006  // decode all values (probably unnecessary, but more future proof)
9007  for(auto& subsystemPair : tmpSubsystemObjectMap)
9008  {
9009  __SUP_COUTV__(StringMacros::decodeURIComponent(subsystemPair.first));
9010  __SUP_COUTV__(StringMacros::decodeURIComponent(subsystemPair.second));
9011 
9012  subsystemObjectMap.emplace(
9013  std::make_pair(StringMacros::decodeURIComponent(subsystemPair.first),
9014  StringMacros::decodeURIComponent(subsystemPair.second)));
9015  }
9016  } // end subsystem object extraction from subsystemString
9017 
9019  cfgMgr, nodeTypeToObjectMap, subsystemObjectMap);
9020 
9021  __SUP_COUT__ << "Done saving artdaq nodes." << __E__;
9022 } // end handleSaveArtdaqNodeRecordsXML()
9023 
9024 //==============================================================================
9031 void ConfigurationGUISupervisor::handleLoadArtdaqNodeLayoutXML(
9032  HttpXmlDocument& xmlOut,
9034  cfgMgr, //force read-only config manager to avoid requiring user-lock (i.e., not ConfigurationManagerRW)
9035  const std::string& contextGroupName /* = "" */,
9036  const TableGroupKey& contextGroupKey /* = INVALID */) const
9037 {
9038  bool usingActiveGroups = (contextGroupName == "" || contextGroupKey.isInvalid());
9039 
9040  //NOTE: must be same/similar code as otsdaq/otsdaq/TablePlugins/ARTDAQTableBase/ARTDAQTableBase.cc:2332
9041  const std::string& finalContextGroupName =
9042  usingActiveGroups
9043  ? cfgMgr->getActiveGroupName(ConfigurationManager::GroupType::CONTEXT_TYPE)
9044  : contextGroupName;
9045  const TableGroupKey& finalContextGroupKey =
9046  usingActiveGroups
9047  ? cfgMgr->getActiveGroupKey(ConfigurationManager::GroupType::CONTEXT_TYPE)
9048  : contextGroupKey;
9049  const std::string& finalConfigGroupName =
9050  cfgMgr->getActiveGroupName(ConfigurationManager::GroupType::CONFIGURATION_TYPE);
9051  const TableGroupKey& finalConfigGroupKey =
9052  cfgMgr->getActiveGroupKey(ConfigurationManager::GroupType::CONFIGURATION_TYPE);
9053 
9054  FILE* fp = nullptr;
9055  //first try context+config name only
9056  {
9057  std::stringstream layoutPath;
9058  layoutPath << ARTDAQTableBase::ARTDAQ_CONFIG_LAYOUTS_PATH << finalContextGroupName
9059  << "_" << finalContextGroupKey << "." << finalConfigGroupName << "_"
9060  << finalConfigGroupKey << ".dat";
9061 
9062  fp = fopen(layoutPath.str().c_str(), "r");
9063  if(!fp)
9064  {
9065  __SUP_COUT__ << "Layout file not found for '" << finalContextGroupName << "("
9066  << finalContextGroupKey << ") + " << finalConfigGroupName << "("
9067  << finalConfigGroupKey << ")': " << layoutPath.str() << __E__;
9068  // return; //try context only!
9069  }
9070  else
9071  __SUP_COUTV__(layoutPath.str());
9072  }
9073  //last try context name only
9074  if(!fp)
9075  {
9076  std::stringstream layoutPath;
9077  layoutPath << ARTDAQTableBase::ARTDAQ_CONFIG_LAYOUTS_PATH << finalContextGroupName
9078  << "_" << finalContextGroupKey << ".dat";
9079  __SUP_COUTV__(layoutPath.str());
9080 
9081  fp = fopen(layoutPath.str().c_str(), "r");
9082  if(!fp)
9083  {
9084  __SUP_COUT__ << "Layout file not found for '" << finalContextGroupName << "("
9085  << finalContextGroupKey << ")': " << layoutPath.str() << __E__;
9086  return;
9087  }
9088  else
9089  __SUP_COUTV__(layoutPath.str());
9090  }
9091 
9092  // file format is line by line
9093  // line 0 -- grid: <rows> <cols>
9094  // line 1-N -- node: <type> <name> <x-grid> <y-grid>
9095 
9096  const size_t maxLineSz = 1000;
9097  char line[maxLineSz];
9098  if(!fgets(line, maxLineSz, fp))
9099  {
9100  fclose(fp);
9101  return;
9102  }
9103  else
9104  {
9105  // extract grid
9106 
9107  unsigned int rows, cols;
9108 
9109  sscanf(line, "%u %u", &rows, &cols);
9110 
9111  __COUT__ << "Grid rows,cols = " << rows << "," << cols << __E__;
9112 
9113  xmlOut.addTextElementToData("grid-rows", std::to_string(rows));
9114  xmlOut.addTextElementToData("grid-cols", std::to_string(cols));
9115  }
9116 
9117  char name[maxLineSz];
9118  char type[maxLineSz];
9119  unsigned int x, y;
9120  while(fgets(line, maxLineSz, fp))
9121  {
9122  // extract node
9123  sscanf(line, "%s %s %u %u", type, name, &x, &y);
9124 
9125  xmlOut.addTextElementToData("node-type", type);
9126  xmlOut.addTextElementToData("node-name", name);
9127  xmlOut.addTextElementToData("node-x", std::to_string(x));
9128  xmlOut.addTextElementToData("node-y", std::to_string(y));
9129  } // end node extraction loop
9130 
9131  fclose(fp);
9132 
9133 } // end handleLoadArtdaqNodeLayoutXML()
9134 
9135 //==============================================================================
9142 void ConfigurationGUISupervisor::handleSaveArtdaqNodeLayoutXML(
9143  HttpXmlDocument& /*xmlOut*/,
9144  ConfigurationManagerRW* cfgMgr,
9145  const std::string& layoutString,
9146  const std::string& contextGroupName,
9147  const TableGroupKey& contextGroupKey)
9148 {
9149  bool usingActiveGroups = (contextGroupName == "" || contextGroupKey.isInvalid());
9150 
9151  const std::string& finalContextGroupName =
9152  usingActiveGroups
9153  ? cfgMgr->getActiveGroupName(ConfigurationManager::GroupType::CONTEXT_TYPE)
9154  : contextGroupName;
9155  const TableGroupKey& finalContextGroupKey =
9156  usingActiveGroups
9157  ? cfgMgr->getActiveGroupKey(ConfigurationManager::GroupType::CONTEXT_TYPE)
9158  : contextGroupKey;
9159  const std::string& finalConfigGroupName =
9160  cfgMgr->getActiveGroupName(ConfigurationManager::GroupType::CONFIGURATION_TYPE);
9161  const TableGroupKey& finalConfigGroupKey =
9162  cfgMgr->getActiveGroupKey(ConfigurationManager::GroupType::CONFIGURATION_TYPE);
9163 
9164  __SUP_COUTV__(layoutString);
9165 
9166  std::stringstream layoutPath;
9167  layoutPath << ARTDAQTableBase::ARTDAQ_CONFIG_LAYOUTS_PATH << finalContextGroupName
9168  << "_" << finalContextGroupKey << "." << finalConfigGroupName << "_"
9169  << finalConfigGroupKey << ".dat";
9170  __SUP_COUTV__(layoutPath.str());
9171 
9172  std::vector<std::string> fields = StringMacros::getVectorFromString(layoutString);
9173  __SUP_COUTV__(StringMacros::vectorToString(fields));
9174 
9175  if(fields.size() < 2 || (fields.size() - 2) % 4 != 0)
9176  {
9177  __SUP_SS__ << "Invalid layout string fields size of " << fields.size() << __E__;
9178  __SUP_SS_THROW__;
9179  }
9180 
9181  FILE* fp = fopen(layoutPath.str().c_str(), "w");
9182  if(!fp)
9183  {
9184  __SUP_SS__ << "Could not open layout file for writing for '"
9185  << finalContextGroupName << "(" << finalContextGroupKey << ") + "
9186  << finalConfigGroupName << "(" << finalConfigGroupKey
9187  << ")': " << layoutPath.str() << __E__;
9188  __SUP_SS_THROW__;
9189  }
9190 
9191  // match load code at ::handleLoadArtdaqNodeLayoutXML()
9192 
9193  // write grid
9194  fprintf(fp, "%s %s\n", fields[0].c_str(), fields[1].c_str());
9195 
9196  // write nodes
9197  for(unsigned int i = 2; i < fields.size(); i += 4)
9198  fprintf(fp,
9199  "%s %s %s %s\n",
9200  fields[i + 0].c_str(),
9201  fields[i + 1].c_str(),
9202  fields[i + 2].c_str(),
9203  fields[i + 3].c_str());
9204 
9205  fclose(fp);
9206 
9207 } // end handleSaveArtdaqNodeLayoutXML()
9208 
9209 //==============================================================================
9211 void ConfigurationGUISupervisor::handleOtherSubsystemActiveGroups(
9212  HttpXmlDocument& xmlOut,
9213  ConfigurationManagerRW* cfgMgr,
9214  bool getFullList,
9215  std::string targetSubsystem /* = "" */)
9216 try
9217 {
9218  try
9219  {
9220  ConfigurationTree node =
9221  cfgMgr->getNode(ConfigurationManager::CONTEXT_SUBSYSTEM_OPTIONAL_TABLE);
9222  auto children = node.getChildren();
9223 
9224  for(auto subsystem : children)
9225  {
9226  __SUP_COUTV__(subsystem.first);
9227  __SUP_COUTV__(
9228  StringMacros::vectorToString(subsystem.second.getChildrenNames()));
9229 
9230  std::string userPath =
9231  subsystem.second.getNode("SubsystemUserDataPath").getValue();
9232  __SUP_COUTV__(userPath);
9233  }
9234  }
9235  catch(const std::runtime_error& e)
9236  {
9237  __SUP_COUT__ << "Ignoring errors in handling other subsystem active groups "
9238  "(assuming the subsystem information map is not setup in "
9239  << ConfigurationManager::CONTEXT_SUBSYSTEM_OPTIONAL_TABLE
9240  << ") -- here is the error: \n"
9241  << e.what() << __E__;
9242  return; //ignore errors if subsystems not defined
9243  }
9244 
9245  //else subsystems are defined, so do not ignore errors!
9246 
9247  ConfigurationTree node =
9248  cfgMgr->getNode(ConfigurationManager::CONTEXT_SUBSYSTEM_OPTIONAL_TABLE);
9249  auto children = node.getChildren();
9250  for(auto subsystem : children)
9251  {
9252  if(targetSubsystem != "" && targetSubsystem != subsystem.first)
9253  continue; //skip non-target subsystem
9254 
9255  xercesc::DOMElement* parent =
9256  xmlOut.addTextElementToData("SubsystemName", subsystem.first);
9257 
9258  if(!getFullList)
9259  continue;
9260 
9261  std::string filename, userDataPath;
9262  std::string username, hostname;
9263 
9264  std::map<std::string /*groupType*/,
9265  std::pair<std::string /*groupName*/, TableGroupKey>>
9266  retMap = cfgMgr->getOtherSubsystemActiveTableGroups(
9267  subsystem.first, &userDataPath, &hostname, &username);
9268 
9269  for(const auto& retPair : retMap)
9270  {
9271  xmlOut.addTextElementToParent("CurrentlyActive" + retPair.first + "GroupName",
9272  retPair.second.first,
9273  parent);
9274  xmlOut.addTextElementToParent("CurrentlyActive" + retPair.first + "GroupKey",
9275  retPair.second.second.toString(),
9276  parent);
9277  }
9278 
9279  std::vector<std::string> filenameTypes = {"Configured",
9280  "Started",
9281  "ActivatedConfig",
9282  "ActivatedContext",
9283  "ActivatedBackbone",
9284  "ActivatedIterator"};
9285 
9286  std::vector<std::string> filenames = {
9287  ConfigurationManager::LAST_CONFIGURED_CONFIG_GROUP_FILE,
9288  ConfigurationManager::LAST_STARTED_CONFIG_GROUP_FILE,
9289  ConfigurationManager::LAST_ACTIVATED_CONFIG_GROUP_FILE,
9290  ConfigurationManager::LAST_ACTIVATED_CONTEXT_GROUP_FILE,
9291  ConfigurationManager::LAST_ACTIVATED_BACKBONE_GROUP_FILE,
9292  ConfigurationManager::LAST_ACTIVATED_ITERATE_GROUP_FILE};
9293 
9294  std::string userPath =
9295  subsystem.second.getNode("SubsystemUserDataPath").getValue();
9296  auto splitPath = StringMacros::getVectorFromString(userPath, {':'});
9297  std::string cmdResult;
9298  for(unsigned int i = 0; i < filenames.size(); ++i)
9299  {
9300  filename = userDataPath + "/ServiceData/RunControlData/" + filenames[i];
9301  __SUP_COUTV__(filename);
9302 
9303  std::string tmpSubsystemFilename =
9304  ConfigurationManager::LAST_TABLE_GROUP_SAVE_PATH + "/" + filenames[i] +
9305  "." + subsystem.first;
9306  __SUP_COUTV__(tmpSubsystemFilename);
9307 
9308  if(splitPath.size() == 2) //must scp
9309  {
9310  if(username.size()) //has username
9311  cmdResult = StringMacros::exec(
9312  ("rm " + tmpSubsystemFilename + " 2>/dev/null; scp " + username +
9313  "@" + hostname + ":" + filename + " " + tmpSubsystemFilename +
9314  " 2>&1; cat " + tmpSubsystemFilename + " 2>&1")
9315  .c_str());
9316  else
9317  cmdResult = StringMacros::exec(
9318  ("rm " + tmpSubsystemFilename + " 2>/dev/null; scp " + hostname +
9319  ":" + filename + " " + tmpSubsystemFilename + " 2>&1; cat " +
9320  tmpSubsystemFilename + " 2>&1")
9321  .c_str());
9322  }
9323  else if(splitPath.size() == 1) //then can just directly access the file
9324  {
9325  cmdResult = StringMacros::exec(("rm " + tmpSubsystemFilename +
9326  " 2>/dev/null; cp " + filename + " " +
9327  tmpSubsystemFilename + " 2>&1; cat " +
9328  tmpSubsystemFilename + " 2>&1")
9329  .c_str());
9330  }
9331 
9332  __SUP_COUTV__(cmdResult);
9333  std::string timeString;
9334  std::pair<std::string /*group name*/, TableGroupKey> theGroup =
9336  filenames[i] + "." + subsystem.first, timeString);
9337 
9338  // fill return parameters
9339  xmlOut.addTextElementToParent(
9340  "Last" + filenameTypes[i] + "GroupName", theGroup.first, parent);
9341  xmlOut.addTextElementToParent("Last" + filenameTypes[i] + "GroupKey",
9342  theGroup.second.toString(),
9343  parent);
9344  xmlOut.addTextElementToParent(
9345  "Last" + filenameTypes[i] + "GroupTime", timeString, parent);
9346  } // end active/recent filename handling
9347 
9348  } //end subsystem loop
9349 } // end getSubsytemTableGroups()
9350 catch(const std::runtime_error& e)
9351 {
9352  __SUP_SS__
9353  << "An error occurred handling subsystem active groups (Please check the "
9354  "subsystem user data path information map setup in the Context group table "
9355  << ConfigurationManager::CONTEXT_SUBSYSTEM_OPTIONAL_TABLE
9356  << ") -- here is the error: \n"
9357  << e.what() << __E__;
9358  __SUP_SS_THROW__;
9359 } // end getSubsytemTableGroups() catch
9360 
9361 //==============================================================================
9363 void ConfigurationGUISupervisor::handleSearchFieldInGroupXML(
9364  HttpXmlDocument& xmlOut,
9365  ConfigurationManagerRW* cfgMgr,
9366  const std::string& searchText,
9367  const bool activeGroupsOnly,
9368  const std::string& groupType,
9369  const std::string& optionGroups,
9370  const std::string& versionsToCheck)
9371 {
9372  // Validate searchText: empty string matches every cell and would be extremely expensive
9373  if(searchText.empty() || searchText.find_first_not_of(" \t\r\n") == std::string::npos)
9374  {
9375  __COUT_WARN__ << "searchText is empty or whitespace; aborting search." << __E__;
9376  xmlOut.addTextElementToData("Error", "Search text must not be empty.");
9377  return;
9378  }
9379 
9380  bool searchAllGroups = false;
9381  if(optionGroups.size() == 0)
9382  {
9383  __COUT__ << "No optionGroups specified, searching all groups of type "
9384  << groupType << __E__;
9385  searchAllGroups = true;
9386  }
9387  std::vector<std::string> optionGroupsList =
9388  StringMacros::getVectorFromString(optionGroups, {','});
9389 
9390  const std::map<std::string, GroupInfo>& allGroupInfo = cfgMgr->getAllGroupInfo();
9391 
9392  int searchMinThreshold = 0;
9393  int searchMaxThreshold = 0;
9394  size_t dashPos = -1;
9395 
9396  // Parse versionsToCheck for version range
9397  if(versionsToCheck.empty())
9398  {
9399  searchMaxThreshold = INT_MAX;
9400  }
9401  else if(versionsToCheck.find('-') != std::string::npos)
9402  {
9403  __COUT__ << "Parsing versionsToCheck for range: " << versionsToCheck << __E__;
9404  dashPos = versionsToCheck.find('-');
9405 
9406  if(dashPos != 0)
9407  {
9408  // Format: "N-M" means version N to version M
9409  std::string minStr = versionsToCheck.substr(0, dashPos);
9410  std::string maxStr = versionsToCheck.substr(dashPos + 1);
9411 
9412  searchMinThreshold = atoi(minStr.c_str());
9413  searchMaxThreshold = atoi(maxStr.c_str());
9414  }
9415  }
9416  else if(versionsToCheck != "")
9417  {
9418  // Single version specified
9419  searchMinThreshold = atoi(versionsToCheck.c_str());
9420  searchMaxThreshold = INT_MAX; // effectively no upper limit
9421  }
9422  else
9423  {
9424  __COUT__ << "Unable to set min and max thresholds for versionsToCheck: "
9425  << versionsToCheck << __E__;
9426  xmlOut.addTextElementToData(
9427  "Error",
9428  "Unable to set min and max thresholds for versionsToCheck: " +
9429  versionsToCheck);
9430  }
9431  __SUP_COUT__ << "versionsToCheck range: " << searchMinThreshold << " to "
9432  << searchMaxThreshold << __E__;
9433 
9434  auto parentEl = xmlOut.addTextElementToData("SearchResults", "");
9435 
9436  for(auto& groupInfo : allGroupInfo)
9437  {
9438  __COUT__ << "Checking group: " << groupInfo.first
9439  << " group type : " << groupInfo.second.getLatestKeyGroupTypeString()
9440  << __E__;
9441 
9442  if(groupInfo.second.getLatestKeyGroupTypeString() != groupType)
9443  continue;
9444 
9445  // Check if groupInfo.first is in optionGroups
9446  if(!searchAllGroups &&
9447  std::find(optionGroupsList.begin(), optionGroupsList.end(), groupInfo.first) ==
9448  optionGroupsList.end())
9449  {
9450  __COUT__ << "\tSkipping group not in optionGroups list." << __E__;
9451  continue; // skip this group
9452  }
9453 
9454  std::set<TableGroupKey /*key*/> allGroupKeys = groupInfo.second.getKeys();
9455  // convert set to vector for easier string handling
9456  std::vector<TableGroupKey> groupKeysVec(allGroupKeys.begin(), allGroupKeys.end());
9457  // convert vector to string for output
9458  std::string groupKeysStr = StringMacros::vectorToString(groupKeysVec, {','});
9459  __COUT__ << "Group keys:" << groupKeysStr << __E__;
9460 
9461  // Dash is first position: "-N" means (maxVersion - N) to maxVersion
9462  if(dashPos == 0)
9463  {
9464  std::string offsetStr = versionsToCheck.substr(1);
9465  int offset =
9466  atoi(offsetStr.c_str()) - 1; // -1 to include the max version itself
9467 
9468  __SUP_COUT__ << "versionsToCheck offset from end: " << offset << __E__;
9469 
9470  // Find the max version from this group
9471  if(groupKeysVec.size() > 0)
9472  {
9473  TableGroupKey maxVersion = groupKeysVec.at(
9474  groupKeysVec.size() - 1); // get the last (max) version
9475  int maxVersionNum = std::stoi(maxVersion.toString());
9476  int minVersionNum = std::max(0, maxVersionNum - offset);
9477 
9478  searchMinThreshold = minVersionNum;
9479  searchMaxThreshold = maxVersionNum;
9480 
9481  __SUP_COUT__ << "Version range: " << searchMinThreshold << " to "
9482  << searchMaxThreshold << __E__;
9483  }
9484  }
9485 
9486  std::set<TableGroupKey /*key*/> groupKeys;
9487  // make set of single active key for easier handling below
9488  if(activeGroupsOnly)
9489  {
9490  std::set<TableGroupKey> singleKeySet;
9491  singleKeySet.insert(groupInfo.second.getLatestKey());
9492  groupKeys = std::move(singleKeySet);
9493  }
9494  else
9495  groupKeys = std::move(allGroupKeys);
9496 
9497  for(const auto& groupKey : groupKeys)
9498  {
9499  __COUT__ << "\tProcessing group: " << groupInfo.first << " v" << groupKey
9500  << __E__;
9501 
9502  if(!activeGroupsOnly &&
9503  (std::stoi(groupKey.toString()) < searchMinThreshold ||
9504  std::stoi(groupKey.toString()) > searchMaxThreshold))
9505  {
9506  __COUT__ << "\tSkipping group version outside of versionsToCheck range."
9507  << __E__;
9508  continue; // skip this group version
9509  }
9510 
9511  // Get the member map for this group
9512  std::map<std::string /*name*/, TableVersion /*version*/> memberMap;
9513  try
9514  {
9515  cfgMgr->loadTableGroup(groupInfo.first,
9516  groupKey,
9517  false /*doActivate*/,
9518  &memberMap,
9519  0 /*progressBar*/,
9520  0 /*accumulateErrors*/,
9521  0 /*groupComment*/,
9522  0 /*groupAuthor*/,
9523  0 /*groupCreationTime*/,
9524  true /*doNotLoadMembers*/);
9525 
9526  // Process each table in the group
9527  for(auto& tablePair : memberMap)
9528  {
9529  __COUT__ << "\t\tTable: " << tablePair.first << " v"
9530  << tablePair.second << __E__;
9531 
9532  // Load the table for this specific version and search that exact view
9533  try
9534  {
9535  TableBase* memberTable = cfgMgr->getVersionedTableByName(
9536  tablePair.first, tablePair.second);
9537 
9538  // Get the table view for the requested version
9539  const TableView& tableView = memberTable->getView();
9540 
9541  // Loop through all rows and columns to search for searchText
9542  for(unsigned int row = 0; row < tableView.getNumberOfRows();
9543  ++row)
9544  {
9545  for(unsigned int col = 0;
9546  col < tableView.getNumberOfColumns();
9547  ++col)
9548  {
9549  const std::string& cellValue =
9550  tableView.getDataView()[row][col];
9551 
9552  // Check if searchText is found in cell value
9553  if(cellValue.find(searchText) != std::string::npos)
9554  {
9555  __COUT__ << "\t\t\tMatch found in " << tablePair.first
9556  << " v" << tablePair.second << " row " << row
9557  << " column " << col
9558  << " value: " << cellValue << __E__;
9559  // Separate match element by group/table for better organization
9560  auto groupTableEl = xmlOut.addTextElementToParent(
9561  "GroupTableMatch",
9562  groupInfo.first + "/" + tablePair.first,
9563  parentEl);
9564  xmlOut.addTextElementToParent(
9565  "MatchRow", std::to_string(row), groupTableEl);
9566  xmlOut.addTextElementToParent(
9567  "MatchColumn",
9568  tableView.getColumnInfo(col).getName(),
9569  groupTableEl);
9570  xmlOut.addTextElementToParent("MatchGroupVersion",
9571  groupKey.toString(),
9572  groupTableEl);
9573  xmlOut.addTextElementToParent(
9574  "MatchTableVersion",
9575  tablePair.second.toString(),
9576  groupTableEl);
9577  }
9578  } // end columns
9579  } // end rows
9580  } // end try load versioned table
9581  catch(const std::runtime_error& e)
9582  {
9583  __COUT_WARN__ << "Failed to search table " << tablePair.first
9584  << " v" << tablePair.second << ": " << e.what()
9585  << __E__;
9586  }
9587  } // end try load table group members
9588  }
9589  catch(const std::runtime_error& e)
9590  {
9591  __COUT_WARN__ << "Failed to load group members for " << groupInfo.first
9592  << ": " << e.what() << __E__;
9593  }
9594  }
9595  } // end groupInfo loop
9596 } // end handleSearchFieldInGroupXML()
9597 
9598 //==============================================================================
9600 void ConfigurationGUISupervisor::handleSearchFieldInTableXML(
9601  HttpXmlDocument& xmlOut,
9602  ConfigurationManagerRW* cfgMgr,
9603  const std::string& searchText,
9604  const std::string& tableName,
9605  const std::string& searchVersionToCheck,
9606  bool activeTablesOnly)
9607 {
9608  // Validate searchText: empty string matches every cell and would be extremely expensive
9609  if(searchText.empty() || searchText.find_first_not_of(" \t\r\n") == std::string::npos)
9610  {
9611  __COUT_WARN__ << "searchText is empty or whitespace; aborting search." << __E__;
9612  xmlOut.addTextElementToData("Error", "Search text must not be empty.");
9613  return;
9614  }
9615 
9616  const std::map<std::string, TableInfo>& allTableInfo = cfgMgr->getAllTableInfo();
9617  std::map<std::string, TableVersion> allActivePairs = cfgMgr->getActiveVersions();
9618 
9619  int searchMinThreshold = 0;
9620  int searchMaxThreshold = 0;
9621 
9622  // Parse searchVersionToCheck for version range
9623  if(searchVersionToCheck.empty())
9624  {
9625  searchMaxThreshold = INT_MAX;
9626  }
9627  else if(searchVersionToCheck.find('-') != std::string::npos)
9628  {
9629  size_t dashPos = searchVersionToCheck.find('-');
9630 
9631  if(dashPos == 0)
9632  {
9633  // Dash is first position: "-N" means (maxVersion - N) to maxVersion
9634  std::string offsetStr = searchVersionToCheck.substr(1);
9635  int offset =
9636  atoi(offsetStr.c_str()) - 1; // -1 to include the max version itself
9637 
9638  __SUP_COUT__ << "searchVersionToCheck offset from end: " << offset << __E__;
9639 
9640  // Find the max version from allTableInfo
9641  if(allTableInfo.find(tableName) != allTableInfo.end())
9642  {
9643  const TableInfo& tableInfo = allTableInfo.at(tableName);
9644  if(tableInfo.versions_.size() > 0)
9645  {
9646  auto maxVersionIter = tableInfo.versions_.rbegin();
9647  int maxVersionNum = maxVersionIter->version();
9648  int minVersionNum = std::max(0, maxVersionNum - offset);
9649 
9650  searchMinThreshold = minVersionNum;
9651  searchMaxThreshold = maxVersionNum;
9652 
9653  __SUP_COUT__ << "Version range: " << searchMinThreshold << " to "
9654  << searchMaxThreshold << __E__;
9655  }
9656  }
9657  }
9658  else
9659  {
9660  // Format: "N-M" means version N to version M
9661  std::string minStr = searchVersionToCheck.substr(0, dashPos);
9662  std::string maxStr = searchVersionToCheck.substr(dashPos + 1);
9663 
9664  searchMinThreshold = atoi(minStr.c_str());
9665  searchMaxThreshold = atoi(maxStr.c_str());
9666  }
9667  }
9668  else if(searchVersionToCheck != "")
9669  {
9670  // Single version specified
9671  searchMinThreshold = atoi(searchVersionToCheck.c_str());
9672  searchMaxThreshold = INT_MAX; // effectively no upper limit
9673  }
9674  else
9675  {
9676  __COUT__ << "Unable to set min and max thresholds for searchVersionToCheck: "
9677  << searchVersionToCheck << __E__;
9678  xmlOut.addTextElementToData(
9679  "Error",
9680  "Unable to set min and max thresholds for searchVersionToCheck: " +
9681  searchVersionToCheck);
9682  }
9683  __SUP_COUT__ << "searchVersionToCheck range: " << searchMinThreshold << " to "
9684  << searchMaxThreshold << __E__;
9685 
9686  try
9687  {
9688  if(allTableInfo.find(tableName) == allTableInfo.end())
9689  {
9690  __SUP_SS__ << "Table '" << tableName << "' not found." << __E__;
9691  xmlOut.addTextElementToData("Error", ss.str());
9692  return;
9693  }
9694 
9695  const TableInfo& tableInfo = allTableInfo.at(tableName);
9696  __COUT__ << "tableInfo versions length " << tableInfo.versions_.size() << __E__;
9697 
9698  unsigned int matchCount = 0;
9699  bool allowIllegalColumns = true;
9700  // Create a parent element for search results
9701  auto parentEl = xmlOut.addTextElementToData("SearchResults", "");
9702 
9703  // Loop through all versions of the table
9704  for(const auto& version : tableInfo.versions_)
9705  {
9706  try
9707  {
9708  auto activeIt = allActivePairs.find(tableName);
9709  bool isActiveVersion =
9710  (activeIt != allActivePairs.end() && activeIt->second == version);
9711  __COUT__ << "Active check for " << tableName << " v" << version << " => "
9712  << (isActiveVersion ? "active" : "inactive") << __E__;
9713  if(!isActiveVersion && activeTablesOnly)
9714  continue; // Skip versions that are not active
9715 
9716  if((std::stoi(version.toString()) < searchMinThreshold ||
9717  std::stoi(version.toString()) > searchMaxThreshold) &&
9718  activeTablesOnly == false /*ignore version threshold if active only*/)
9719  {
9720  __COUT__ << "Skipping version " << version << " out of range "
9721  << searchMinThreshold << " to " << searchMaxThreshold
9722  << __E__;
9723  continue;
9724  }
9725  TableBase* table = cfgMgr->getTableByName(tableName);
9726 
9727  // get view pointer
9728  TableView* tableViewPtr;
9729  if(version.isInvalid()) // use mock-up
9730  {
9731  __COUT__ << "Using mock-up for table " << tableName << " version "
9732  << version << __E__;
9733  tableViewPtr = table->getMockupViewP();
9734  }
9735  else // use view version
9736  {
9737  try
9738  {
9739  // locally accumulate 'manageable' errors getting the version to avoid
9740  // reverting to mockup
9741  std::string localAccumulatedErrors = "";
9742  tableViewPtr =
9743  cfgMgr
9745  tableName,
9746  version,
9747  allowIllegalColumns /*looseColumnMatching*/,
9748  &localAccumulatedErrors,
9749  false /*getRawData*/)
9750  ->getViewP();
9751 
9752  if(localAccumulatedErrors != "")
9753  xmlOut.addTextElementToData("Error", localAccumulatedErrors);
9754  }
9755  catch(const std::runtime_error& e)
9756  {
9757  __COUT__ << "Error loading version " << version << " for table "
9758  << tableName << ": " << e.what() << __E__;
9759  __SUP_COUT_WARN__ << "Could not load version " << version
9760  << " for table " << tableName << ": "
9761  << e.what() << __E__;
9762  // fallback to mockup
9763  tableViewPtr = table->getMockupViewP();
9764  }
9765  }
9766 
9767  __COUT__ << "Getting table " << tableName << " version " << version
9768  << __E__;
9769 
9770  // Search through all rows and columns
9771  for(unsigned int row = 0; row < tableViewPtr->getNumberOfRows(); ++row)
9772  {
9773  for(unsigned int col = 0; col < tableViewPtr->getNumberOfColumns();
9774  ++col)
9775  {
9776  const std::string& cellValue =
9777  tableViewPtr->getDataView()[row][col];
9778  // Check if searchText is found in cell value
9779  if(cellValue.find(searchText) != std::string::npos)
9780  {
9781  __COUT__ << "Match found in row " << row << " column " << col
9782  << " value: " << cellValue << __E__;
9783  auto matchEl = xmlOut.addTextElementToParent(
9784  "Match", cellValue, parentEl);
9785  xmlOut.addTextElementToParent(
9786  "MatchVersion", version.toString(), matchEl);
9787  xmlOut.addTextElementToParent(
9788  "MatchRow", std::to_string(row), matchEl);
9789  xmlOut.addTextElementToParent(
9790  "MatchColumn",
9791  tableViewPtr->getColumnInfo(col).getName(),
9792  matchEl);
9793 
9794  ++matchCount;
9795  }
9796  }
9797  } // end for rows and columns
9798  }
9799  catch(const std::runtime_error& e)
9800  {
9801  __COUT__ << "Error searching in version " << version << " for table "
9802  << tableName << ": " << e.what() << __E__;
9803  __SUP_COUT_WARN__ << "Could not load version " << version << " for table "
9804  << tableName << ": " << e.what() << __E__;
9805  }
9806  } //emd for version loop
9807 
9808  xmlOut.addTextElementToData("MatchCount", std::to_string(matchCount));
9809  }
9810  catch(std::runtime_error& e)
9811  {
9812  __SUP_SS__ << "Error searching in table '" << tableName << "'!\n\n " << e.what()
9813  << __E__;
9814  __SUP_COUT_ERR__ << ss.str();
9815  xmlOut.addTextElementToData("Error", ss.str());
9816  }
9817  catch(...)
9818  {
9819  __SUP_SS__ << "Error searching in table '" << tableName << "'!\n\n " << __E__;
9820  try
9821  {
9822  throw;
9823  }
9824  catch(const std::exception& e)
9825  {
9826  ss << "Exception message: " << e.what();
9827  }
9828  catch(...)
9829  {
9830  }
9831  __SUP_COUT_ERR__ << ss.str();
9832  xmlOut.addTextElementToData("Error", ss.str());
9833  }
9834 } // end handleSearchFieldInTableXML()
9835 
9836 //==============================================================================
9838 void ConfigurationGUISupervisor::handleGroupDiff(
9839  HttpXmlDocument& xmlOut,
9840  ConfigurationManagerRW* cfgMgr,
9841  const std::string& groupName,
9842  const TableGroupKey& groupKey,
9843  const TableGroupKey& diffKey /* = TableGroupKey() */,
9844  const std::string& diffGroupNameInput /* = "" */)
9845 {
9846  //Steps:
9847  // - Get group type and load table map
9848  // - Get match type active group table map
9849  // - For each table, compare
9850  std::string diffGroupName;
9851 
9852  if(diffKey.isInvalid())
9853  __SUP_COUT__ << "Differencing group " << groupName << "(" << groupKey
9854  << ") with the active group." << __E__;
9855  else
9856  {
9857  if(diffGroupNameInput == "")
9858  diffGroupName = groupName;
9859  else
9860  diffGroupName = diffGroupNameInput;
9861 
9862  __SUP_COUT__ << "Differencing group " << groupName << "(" << groupKey
9863  << ") with group " << diffGroupName << "(" << diffKey << ")"
9864  << __E__;
9865  }
9866 
9867  try
9868  {
9869  std::map<std::string /*name*/, TableVersion /*version*/> memberMap, diffMemberMap;
9870  std::string groupType, accumulateErrors;
9871  std::stringstream diffReport;
9872  bool noDifference = true;
9873 
9874  cfgMgr->loadTableGroup(
9875  groupName,
9876  groupKey,
9877  false /*doActivate*/,
9878  &memberMap /*groupMembers*/,
9879  0 /*progressBar*/,
9880  &accumulateErrors /*accumulateErrors*/,
9881  0 /*groupComment*/,
9882  0 /*groupAuthor*/,
9883  0 /*groupCreationTime*/,
9884  false /*doNotLoadMember*/,
9885  (diffKey.isInvalid()
9886  ? &groupType
9887  : 0)); //for specified diff group (not active), do not need groupType
9888 
9889  __SUP_COUTV__(StringMacros::mapToString(memberMap));
9890 
9891  std::map<std::string /* groupType */, std::pair<std::string, TableGroupKey>>
9892  activeGroups;
9893  if(diffKey.isInvalid())
9894  {
9895  activeGroups = cfgMgr->getActiveTableGroups();
9896 
9897  __SUP_COUTV__(StringMacros::mapToString(activeGroups));
9898  __SUP_COUTV__(groupType);
9899 
9900  if(activeGroups.find(groupType) == activeGroups.end() ||
9901  activeGroups.at(groupType).first == "" ||
9902  activeGroups.at(groupType).second.isInvalid())
9903  {
9904  __SUP_SS__ << "Could not find an active group of type '" << groupType
9905  << ".' Please check the expected active configuration groups "
9906  "for errors (going to 'System View' of the Config App may "
9907  "reveal errors)."
9908  << __E__;
9909  __SUP_SS_THROW__;
9910  }
9911 
9912  __SUP_COUT__ << "active " << groupType << " group is "
9913  << activeGroups.at(groupType).first << "("
9914  << activeGroups.at(groupType).second << ")" << __E__;
9915 
9916  diffReport << "This difference report is between " << groupType
9917  << " group <b>'" << groupName << "(" << groupKey << ")'</b>"
9918  << " and active group <b>'" << activeGroups.at(groupType).first
9919  << "(" << activeGroups.at(groupType).second << ")'</b>." << __E__;
9920 
9921  cfgMgr->loadTableGroup(activeGroups.at(groupType).first,
9922  activeGroups.at(groupType).second,
9923  false /*doActivate*/,
9924  &diffMemberMap /*groupMembers*/,
9925  0 /*progressBar*/,
9926  &accumulateErrors /*accumulateErrors*/,
9927  0 /*groupComment*/,
9928  0 /*groupAuthor*/,
9929  0 /*groupCreationTime*/,
9930  false /*doNotLoadMember*/);
9931 
9932  diffReport << "\n\n"
9933  << "'" << groupName << "(" << groupKey << ")' has <b>"
9934  << memberMap.size() << " member tables</b>, and "
9935  << "'" << activeGroups.at(groupType).first << "("
9936  << activeGroups.at(groupType).second << ")' has <b>"
9937  << diffMemberMap.size() << " member tables</b>." << __E__;
9938  }
9939  else //specified diff group (not active), so do not need groupType
9940  {
9941  diffReport << "This difference report is between group <b>'" << groupName
9942  << "(" << groupKey << ")'</b>"
9943  << " and group <b>'" << diffGroupName << "(" << diffKey
9944  << ")'</b>." << __E__;
9945 
9946  cfgMgr->loadTableGroup(diffGroupName,
9947  diffKey,
9948  false /*doActivate*/,
9949  &diffMemberMap /*groupMembers*/,
9950  0 /*progressBar*/,
9951  &accumulateErrors /*accumulateErrors*/,
9952  0 /*groupComment*/,
9953  0 /*groupAuthor*/,
9954  0 /*groupCreationTime*/,
9955  false /*doNotLoadMember*/);
9956 
9957  diffReport << "\n\n"
9958  << "'" << groupName << "(" << groupKey << ")' has <b>"
9959  << memberMap.size() << " member tables</b>, and "
9960  << "'" << diffGroupName << "(" << diffKey << ")' has <b>"
9961  << diffMemberMap.size() << " member tables</b>." << __E__;
9962  }
9963 
9964  __SUP_COUTV__(StringMacros::mapToString(diffMemberMap));
9965 
9966  diffReport << "<INDENT><ol>";
9967 
9968  unsigned int tableDifferences = 0;
9969 
9970  for(auto& member : memberMap)
9971  {
9972  if(diffMemberMap.find(member.first) == diffMemberMap.end())
9973  {
9974  diffReport << "\n\n<li>"
9975  << "Table <b>" << member.first << "-v" << member.second
9976  << "</b> not found in active group."
9977  << "</li>" << __E__;
9978  noDifference = false;
9979  ++tableDifferences;
9980  continue;
9981  }
9982 
9983  __SUP_COUTT__ << "Comparing " << member.first << "-v" << member.second
9984  << " ... " << member.first << "-v"
9985  << diffMemberMap.at(member.first) << __E__;
9986 
9987  if(member.second == diffMemberMap.at(member.first))
9988  continue;
9989 
9990  diffReport << "\n\n<li>"
9991  << "Table <b>" << member.first << " v" << member.second
9992  << "</b> in " << groupName << "(" << groupKey << ")' ...vs... "
9993  << " <b>v" << diffMemberMap.at(member.first) << "</b> in "
9994  << diffGroupName << "(" << diffKey << ")':" << __E__;
9995 
9996  TableBase* table = cfgMgr->getTableByName(member.first);
9997 
9998  diffReport << "<ul>";
9999  std::map<std::string /* uid */, std::vector<std::string /* colName */>>
10000  modifiedRecords; //useful for tree diff view display
10001  if(!table->diffTwoVersions(member.second,
10002  diffMemberMap.at(member.first),
10003  &diffReport,
10004  &modifiedRecords))
10005  {
10006  //difference found!
10007  noDifference = false;
10008  ++tableDifferences;
10009  auto parentEl =
10010  xmlOut.addTextElementToData("TableWithDiff", member.first);
10011  for(auto& modifiedRecord : modifiedRecords)
10012  {
10013  auto recordParentEl = xmlOut.addTextElementToParent(
10014  "RecordWithDiff", modifiedRecord.first, parentEl);
10015  for(auto& modifiedColumn : modifiedRecord.second)
10016  xmlOut.addTextElementToParent(
10017  "ColNameWithDiff", modifiedColumn, recordParentEl);
10018  }
10019  }
10020  diffReport << "</ul></li>";
10021 
10022  } //end member table comparison loop
10023 
10024  for(auto& diffMember : diffMemberMap)
10025  {
10026  if(memberMap.find(diffMember.first) == memberMap.end())
10027  {
10028  if(diffKey.isInvalid())
10029  diffReport << "\n\n<li>"
10030  << "Active Group Table <b>" << diffMember.first << "-v"
10031  << diffMember.second << "</b> not found in '" << groupName
10032  << "(" << groupKey << ")'."
10033  << "</li>" << __E__;
10034  else
10035  diffReport << "\n\n<li>" << diffGroupName << "(" << diffKey
10036  << ") Table <b>" << diffMember.first << "-v"
10037  << diffMember.second << "</b> not found in '" << groupName
10038  << "(" << groupKey << ")'."
10039  << "</li>" << __E__;
10040 
10041  noDifference = false;
10042  ++tableDifferences;
10043  continue;
10044  }
10045  }
10046  diffReport << "\n</ol></INDENT>";
10047 
10048  if(diffKey.isInvalid())
10049  {
10050  if(noDifference)
10051  diffReport << "\n\nNo difference found between "
10052  << "<b>'" << groupName << "(" << groupKey
10053  << ")'</b> and active group "
10054  << "<b>'" << activeGroups.at(groupType).first << "("
10055  << activeGroups.at(groupType).second << ")'</b>." << __E__;
10056  else
10057  diffReport << "\n\n<b>" << tableDifferences
10058  << "</b> member table differences identified between "
10059  << "<b>'" << groupName << "(" << groupKey
10060  << ")'</b> and active group "
10061  << "<b>'" << activeGroups.at(groupType).first << "("
10062  << activeGroups.at(groupType).second << ")'</b>." << __E__;
10063  }
10064  else
10065  {
10066  if(noDifference)
10067  diffReport << "\n\nNo difference found between "
10068  << "<b>'" << groupName << "(" << groupKey
10069  << ")'</b> and group "
10070  << "<b>'" << diffGroupName << "(" << diffKey << ")'</b>."
10071  << __E__;
10072  else
10073  diffReport << "\n\n<b>" << tableDifferences
10074  << "</b> member table differences identified between "
10075  << "<b>'" << groupName << "(" << groupKey
10076  << ")'</b> and group "
10077  << "<b>'" << diffGroupName << "(" << diffKey << ")'</b>."
10078  << __E__;
10079  }
10080 
10081  xmlOut.addTextElementToData("NoDifference", noDifference ? "1" : "0");
10082  xmlOut.addTextElementToData("DiffReport", diffReport.str());
10083  }
10084  catch(const std::runtime_error& e)
10085  {
10086  __SUP_COUT_ERR__ << "Caught error while differencing group " << groupName << "("
10087  << groupKey << ") with group " << diffGroupName << "(" << diffKey
10088  << ")" << __E__ << e.what() << __E__;
10089  throw; //rethrow
10090  }
10091 } // end handleGroupDiff()
10092 
10093 //==============================================================================
10095 void ConfigurationGUISupervisor::handleTableDiff(HttpXmlDocument& xmlOut,
10096  ConfigurationManagerRW* cfgMgr,
10097  const std::string& tableName,
10098  const TableVersion& vA,
10099  const TableVersion& vB)
10100 {
10101  __SUP_COUT__ << "Differencing tableName " << tableName << " v" << vA << " with v"
10102  << vB << __E__;
10103 
10104  //first make sure tables are loaded
10105  TableBase* table = cfgMgr->getTableByName(tableName);
10106 
10107  try
10108  {
10109  // locally accumulate 'manageable' errors getting the version to avoid
10110  // reverting to mockup
10111  std::string localAccumulatedErrors = "";
10112  cfgMgr->getVersionedTableByName(tableName,
10113  vA,
10114  false /*looseColumnMatching*/,
10115  &localAccumulatedErrors,
10116  false /*getRawData*/);
10117 
10118  if(localAccumulatedErrors != "")
10119  xmlOut.addTextElementToData("Error", localAccumulatedErrors);
10120  }
10121  catch(std::runtime_error& e) // default to mock-up for fail-safe in GUI editor
10122  {
10123  __SUP_SS__ << "Failed to get table " << tableName << " version " << vA;
10124  ss << "\n\n...Here is why it failed:\n\n" << e.what() << __E__;
10125  __SUP_COUT_ERR__ << "\n" << ss.str();
10126 
10127  xmlOut.addTextElementToData("Error", "Error getting view! " + ss.str());
10128  }
10129  catch(...) // default to mock-up for fail-safe in GUI editor
10130  {
10131  __SUP_SS__ << "Failed to get table " << tableName << " version: " << vA << __E__;
10132  try
10133  {
10134  throw;
10135  } //one more try to printout extra info
10136  catch(const std::exception& e)
10137  {
10138  ss << "Exception message: " << e.what();
10139  }
10140  catch(...)
10141  {
10142  }
10143 
10144  __SUP_COUT_ERR__ << "\n" << ss.str();
10145  xmlOut.addTextElementToData("Error", "Error getting view! " + ss.str());
10146  }
10147  try
10148  {
10149  // locally accumulate 'manageable' errors getting the version to avoid
10150  // reverting to mockup
10151  std::string localAccumulatedErrors = "";
10152  cfgMgr->getVersionedTableByName(tableName,
10153  vB,
10154  false /*looseColumnMatching*/,
10155  &localAccumulatedErrors,
10156  false /*getRawData*/);
10157 
10158  if(localAccumulatedErrors != "")
10159  xmlOut.addTextElementToData("Error", localAccumulatedErrors);
10160  }
10161  catch(std::runtime_error& e) // default to mock-up for fail-safe in GUI editor
10162  {
10163  __SUP_SS__ << "Failed to get table " << tableName << " version " << vB;
10164  ss << "\n\n...Here is why it failed:\n\n" << e.what() << __E__;
10165  __SUP_COUT_ERR__ << "\n" << ss.str();
10166 
10167  xmlOut.addTextElementToData("Error", "Error getting view! " + ss.str());
10168  }
10169  catch(...) // default to mock-up for fail-safe in GUI editor
10170  {
10171  __SUP_SS__ << "Failed to get table " << tableName << " version: " << vB << __E__;
10172  try
10173  {
10174  throw;
10175  } //one more try to printout extra info
10176  catch(const std::exception& e)
10177  {
10178  ss << "Exception message: " << e.what();
10179  }
10180  catch(...)
10181  {
10182  }
10183 
10184  __SUP_COUT_ERR__ << "\n" << ss.str();
10185  xmlOut.addTextElementToData("Error", "Error getting view! " + ss.str());
10186  }
10187 
10188  bool noDifference = true;
10189  std::stringstream diffReport;
10190 
10191  diffReport << "This difference report is between table " << tableName << " v" << vA
10192  << " and v" << vB << "</b>." << __E__;
10193 
10194  diffReport << "<INDENT>";
10195  diffReport << "<ul>";
10196  std::map<std::string /* uid */, std::vector<std::string /* colName */>>
10197  modifiedRecords; //useful for tree diff view display
10198  if(!table->diffTwoVersions(vA, vB, &diffReport))
10199  noDifference = false; //difference found!
10200  diffReport << "</ul></INDENT>";
10201 
10202  xmlOut.addTextElementToData("NoDifference", noDifference ? "1" : "0");
10203  xmlOut.addTextElementToData("DiffReport", diffReport.str());
10204 } // end handleTableDiff()
10205 
10206 //==============================================================================
10209 void ConfigurationGUISupervisor::testXDAQContext()
10210 {
10211  if(0) //keep for debugging
10212  {
10213  __COUT_INFO__ << "Hello0!";
10214  ConfigurationManagerRW cfgMgrInst("ExampleUser");
10215  __COUT_INFO__ << "Hello1!";
10216  ConfigurationManagerRW* cfgMgr = &cfgMgrInst;
10217  __COUT_INFO__ << "Hello2!";
10218  cfgMgr->testXDAQContext();
10219  __COUT_INFO__ << "Hello3!";
10220  return;
10221  }
10222 
10223  try
10224  {
10225  __SUP_COUT_INFO__ << "Attempting test activation of the context group." << __E__;
10227  cfgMgr; // create instance to activate saved context and backbone groups (not config group)
10228  }
10229  catch(const std::runtime_error& e)
10230  {
10231  __SUP_COUT_WARN__
10232  << "The test activation of the context group failed. Ignoring error: \n"
10233  << e.what() << __E__;
10234  return;
10235  }
10236  catch(...)
10237  {
10238  __SUP_COUT_WARN__ << "The test activation of the context group failed. Ignoring."
10239  << __E__;
10240  return;
10241  }
10242 
10243  __SUP_COUT_INFO__ << "Completed test activation of the context group." << __E__;
10244  return;
10245 
10247  // below has been used for debugging.
10248 
10249  // behave like a user
10250  // start with top level xdaq context
10251  // then add and delete rows proof-of-concept
10252  // export xml xdaq table file
10253 
10256  // behave like a new user
10257  //
10258  // ConfigurationManagerRW cfgMgrInst("ExampleUser");
10259 
10260  // ConfigurationManagerRW* cfgMgr =& cfgMgrInst;
10261 
10262  // // std::map<std::string, TableVersion> groupMembers;
10263  // // groupMembers["DesktopIcon"] = TableVersion(2);
10264  // // cfgMgr->saveNewTableGroup("test",
10265  // // groupMembers, "test comment");
10266 
10267  // //
10268  // const std::map<std::string, TableInfo>& allTableInfo =
10269  // cfgMgr->getAllTableInfo(true /* refresh*/);
10270  // __SUP_COUT__ << "allTableInfo.size() = " << allTableInfo.size() << __E__;
10271  // for(auto& mapIt : allTableInfo)
10272  // {
10273  // __SUP_COUT__ << "Table Name: " << mapIt.first << __E__;
10274  // __SUP_COUT__ << "\t\tExisting Versions: " << mapIt.second.versions_.size()
10275  // <<
10276  // __E__;
10277 
10278  // //get version key for the current system table key
10279  // for (auto& v:mapIt.second.versions_)
10280  // {
10281  // __SUP_COUT__ << "\t\t" << v << __E__;
10282  // }
10283  // }
10284  // __SUP_COUTT__ << "Group Info end runtime=" << cfgMgr->runTimeSeconds() << __E__;
10285  // testXDAQContext just a test bed for navigating the new config tree
10286  // cfgMgr->testXDAQContext();
10287 
10290 } // 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
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)