otsdaq  v2_05_02_indev
ConfigurationManagerRW.cc
1 #include "otsdaq/ConfigurationInterface/ConfigurationManagerRW.h"
2 
3 #include <dirent.h>
4 
5 using namespace ots;
6 
7 #undef __MF_SUBJECT__
8 #define __MF_SUBJECT__ "ConfigurationManagerRW"
9 
10 #define TABLE_INFO_PATH std::string(__ENV__("TABLE_INFO_PATH")) + "/"
11 #define TABLE_INFO_EXT "Info.xml"
12 
13 #define CORE_TABLE_INFO_FILENAME \
14  ((getenv("SERVICE_DATA_PATH") == NULL) ? (std::string(__ENV__("USER_DATA")) + "/ServiceData") : (std::string(__ENV__("SERVICE_DATA_PATH")))) + \
15  "/CoreTableInfoNames.dat"
16 
17 //==============================================================================
18 // ConfigurationManagerRW
19 ConfigurationManagerRW::ConfigurationManagerRW(const std::string& username) : ConfigurationManager(username) // for use as author of new views
20 {
21  __COUT__ << "Instantiating Config Manager with Write Access! (for " << username << ")" << __E__;
22 
23  theInterface_ = ConfigurationInterface::getInstance(false); // false to use artdaq DB
24 
25  //make table group history directory here and at Gateway Supervisor (just in case)
26  mkdir((ConfigurationManager::LAST_TABLE_GROUP_SAVE_PATH).c_str(), 0755);
27 
28  //=========================
29  // dump names of core tables (so UpdateOTS.sh can copy core tables for user)
30  // only if table does not exist
31  {
32  const std::set<std::string>& contextMemberNames = getContextMemberNames();
33  const std::set<std::string>& backboneMemberNames = getBackboneMemberNames();
34  const std::set<std::string>& iterateMemberNames = getIterateMemberNames();
35 
36  FILE* fp = fopen((CORE_TABLE_INFO_FILENAME).c_str(), "r");
37 
38  //__COUT__ << "Updating core tables file..." << __E__;
39 
40  if(fp) // check for all core table names in file, and force their presence
41  {
42  std::vector<unsigned int> foundVector;
43  char line[100];
44  for(const auto& name : contextMemberNames)
45  {
46  foundVector.push_back(false);
47  rewind(fp);
48  while(fgets(line, 100, fp))
49  {
50  if(strlen(line) < 1)
51  continue;
52  line[strlen(line) - 1] = '\0'; // remove endline
53  if(strcmp(line, ("ContextGroup/" + name).c_str()) == 0) // is match?
54  {
55  foundVector.back() = true;
56  //__COUTV__(name);
57  break;
58  }
59  }
60  }
61 
62  for(const auto& name : backboneMemberNames)
63  {
64  foundVector.push_back(false);
65  rewind(fp);
66  while(fgets(line, 100, fp))
67  {
68  if(strlen(line) < 1)
69  continue;
70  line[strlen(line) - 1] = '\0'; // remove endline
71  if(strcmp(line, ("BackboneGroup/" + name).c_str()) == 0) // is match?
72  {
73  foundVector.back() = true;
74  //__COUTV__(name);
75  break;
76  }
77  }
78  }
79 
80  for(const auto& name : iterateMemberNames)
81  {
82  foundVector.push_back(false);
83  rewind(fp);
84  while(fgets(line, 100, fp))
85  {
86  if(strlen(line) < 1)
87  continue;
88  line[strlen(line) - 1] = '\0'; // remove endline
89  if(strcmp(line, ("IterateGroup/" + name).c_str()) == 0) // is match?
90  {
91  foundVector.back() = true;
92  //__COUTV__(name);
93  break;
94  }
95  }
96  }
97 
98  fclose(fp);
99 
100  // for(const auto &found:foundVector)
101  // __COUTV__(found);
102 
103  // open file for appending the missing names
104  fp = fopen((CORE_TABLE_INFO_FILENAME).c_str(), "a");
105  if(fp)
106  {
107  unsigned int i = 0;
108  for(const auto& name : contextMemberNames)
109  {
110  if(!foundVector[i])
111  fprintf(fp, "\nContextGroup/%s", name.c_str());
112 
113  ++i;
114  }
115  for(const auto& name : backboneMemberNames)
116  {
117  if(!foundVector[i])
118  fprintf(fp, "\nBackboneGroup/%s", name.c_str());
119 
120  ++i;
121  }
122  for(const auto& name : iterateMemberNames)
123  {
124  if(!foundVector[i])
125  fprintf(fp, "\nIterateGroup/%s", name.c_str());
126 
127  ++i;
128  }
129  fclose(fp);
130  }
131  else
132  {
133  __SS__ << "Failed to open core table info file for appending: " << CORE_TABLE_INFO_FILENAME << __E__;
134  __SS_THROW__;
135  }
136  }
137  else
138  {
139  fp = fopen((CORE_TABLE_INFO_FILENAME).c_str(), "w");
140  if(fp)
141  {
142  for(const auto& name : contextMemberNames)
143  fprintf(fp, "\nContextGroup/%s", name.c_str());
144  for(const auto& name : backboneMemberNames)
145  fprintf(fp, "\nBackboneGroup/%s", name.c_str());
146  for(const auto& name : iterateMemberNames)
147  fprintf(fp, "\nIterateGroup/%s", name.c_str());
148  fclose(fp);
149  }
150  else
151  {
152  __SS__ << "Failed to open core table info file: " << CORE_TABLE_INFO_FILENAME << __E__;
153  __SS_THROW__;
154  }
155  }
156  } // end dump names of core tables
157 } // end constructor
158 
159 //==============================================================================
160 // getAllTableInfo()
161 // Used by ConfigurationGUISupervisor to get all the info for the existing configurations
162 //
163 // if(accumulatedWarnings)
164 // this implies allowing column errors and accumulating such errors in given string
165 const std::map<std::string, TableInfo>& ConfigurationManagerRW::getAllTableInfo(bool refresh,
166  std::string* accumulatedWarnings /*=0*/,
167  const std::string& errorFilterName /*=""*/)
168 {
169  // allTableInfo_ is container to be returned
170 
171  if(!refresh)
172  return allTableInfo_;
173 
174  // else refresh!
175  allTableInfo_.clear();
176  allGroupInfo_.clear();
177 
178  TableBase* table;
179 
180  // existing configurations are defined by which infos are in TABLE_INFO_PATH
181  // can test that the class exists based on this
182  // and then which versions
183  __COUT__ << "======================================================== "
184  "getAllTableInfo start"
185  << __E__;
186  __COUT__ << "Refreshing all! Extracting list of tables..." << __E__;
187  DIR* pDIR;
188  struct dirent* entry;
189  std::string path = TABLE_INFO_PATH;
190  char fileExt[] = TABLE_INFO_EXT;
191  const unsigned char MIN_TABLE_NAME_SZ = 3;
192  if((pDIR = opendir(path.c_str())) != 0)
193  {
194  while((entry = readdir(pDIR)) != 0)
195  {
196  // enforce table name length
197  if(strlen(entry->d_name) < strlen(fileExt) + MIN_TABLE_NAME_SZ)
198  continue;
199 
200  // find file names with correct file extenstion
201  if(strcmp(&(entry->d_name[strlen(entry->d_name) - strlen(fileExt)]), fileExt) != 0)
202  continue; // skip different extentions
203 
204  entry->d_name[strlen(entry->d_name) - strlen(fileExt)] = '\0'; // remove file extension to get table name
205 
206  //__COUT__ << entry->d_name << __E__;
207 
208  // 0 will force the creation of new instance (and reload from Info)
209  table = 0;
210 
211  try // only add valid table instances to maps
212  {
213  theInterface_->get(table, entry->d_name, 0, 0,
214  true); // dont fill
215  }
216  catch(cet::exception const&)
217  {
218  if(table)
219  delete table;
220  table = 0;
221 
222  __COUT__ << "Skipping! No valid class found for... " << entry->d_name << "\n";
223  continue;
224  }
225  catch(std::runtime_error& e)
226  {
227  if(table)
228  delete table;
229  table = 0;
230 
231  __COUT__ << "Skipping! No valid class found for... " << entry->d_name << "\n";
232  __COUT__ << "Error: " << e.what() << __E__;
233 
234  // for a runtime_error, it is likely that columns are the problem
235  // the Table Editor needs to still fix these.. so attempt to
236  // proceed.
237  if(accumulatedWarnings)
238  {
239  if(errorFilterName == "" || errorFilterName == entry->d_name)
240  {
241  *accumulatedWarnings += std::string("\nIn table '") + entry->d_name + "'..." + e.what(); // global accumulate
242 
243  __SS__ << "Attempting to allow illegal columns!" << __E__;
244  *accumulatedWarnings += ss.str();
245  }
246 
247  // attempt to recover and build a mock-up
248  __COUT__ << "Attempting to allow illegal columns!" << __E__;
249 
250  std::string returnedAccumulatedErrors;
251  try
252  {
253  // table = new TableBase(entry->d_name,
254  // &returnedAccumulatedErrors);
255  table = new TableBase(entry->d_name, &returnedAccumulatedErrors);
256  }
257  catch(...)
258  {
259  __COUT__ << "Skipping! Allowing illegal columns didn't work either... " << entry->d_name << "\n";
260  continue;
261  }
262  __COUT__ << "Error (but allowed): " << returnedAccumulatedErrors << __E__;
263 
264  if(errorFilterName == "" || errorFilterName == entry->d_name)
265  *accumulatedWarnings += std::string("\nIn table '") + entry->d_name + "'..." + returnedAccumulatedErrors; // global accumulate
266  }
267  else
268  continue;
269  }
270 
271  //__COUT__ << "Instance created: " << entry->d_name << "\n"; //found!
272 
273  if(nameToTableMap_[entry->d_name]) // handle if instance existed
274  {
275  // copy the temporary versions! (or else all is lost)
276  std::set<TableVersion> versions = nameToTableMap_[entry->d_name]->getStoredVersions();
277  for(auto& version : versions)
278  if(version.isTemporaryVersion())
279  {
280  //__COUT__ << "copying tmp = " << version << __E__;
281 
282  try // do NOT let TableView::init() throw here
283  {
284  nameToTableMap_[entry->d_name]->setActiveView(version);
285  table->copyView( // this calls TableView::init()
286  nameToTableMap_[entry->d_name]->getView(),
287  version,
288  username_);
289  }
290  catch(...) // do NOT let invalid temporary version throw at this
291  // point
292  {
293  } // just trust configurationBase throws out the failed version
294  }
295  //__COUT__ << "deleting: " << entry->d_name << "\n"; //found!
296  delete nameToTableMap_[entry->d_name];
297  nameToTableMap_[entry->d_name] = 0;
298  }
299 
300  nameToTableMap_[entry->d_name] = table;
301 
302  allTableInfo_[entry->d_name].tablePtr_ = table;
303  allTableInfo_[entry->d_name].versions_ = theInterface_->getVersions(table);
304 
305  // also add any existing temporary versions to all table info
306  // because the interface wont find those versions
307  std::set<TableVersion> versions = nameToTableMap_[entry->d_name]->getStoredVersions();
308  for(auto& version : versions)
309  if(version.isTemporaryVersion())
310  {
311  //__COUT__ << "surviving tmp = " << version << __E__;
312  allTableInfo_[entry->d_name].versions_.emplace(version);
313  }
314  }
315  closedir(pDIR);
316  }
317  __COUT__ << "Extracting list of tables complete. Now initializing..." << __E__;
318 
319  // call init to load active versions by default, activate with warnings allowed (assuming development going on)
320  {
321  // if there is a filter name, do not include init warnings (it just scares people in the table editor)
322  std::string tmpAccumulateWarnings;
323  init(0 /*accumulatedErrors*/, false /*initForWriteAccess*/, accumulatedWarnings ? &tmpAccumulateWarnings : nullptr);
324 
325  if(accumulatedWarnings && errorFilterName == "")
326  *accumulatedWarnings += tmpAccumulateWarnings;
327  }
328  __COUT__ << "======================================================== getAllTableInfo end" << __E__;
329 
330  // get Group Info too!
331  try
332  {
333  // build allGroupInfo_ for the ConfigurationManagerRW
334 
335  std::set<std::string /*name*/> tableGroups = theInterface_->getAllTableGroupNames();
336  __COUT__ << "Number of Groups: " << tableGroups.size() << __E__;
337 
338  TableGroupKey key;
339  std::string name;
340  for(const auto& fullName : tableGroups)
341  {
342  TableGroupKey::getGroupNameAndKey(fullName, name, key);
343  cacheGroupKey(name, key);
344  }
345 
346  // for each group get member map & comment, author, time, and type for latest key
347  for(auto& groupInfo : allGroupInfo_)
348  {
349  try
350  {
351  loadTableGroup(groupInfo.first /*groupName*/,
352  groupInfo.second.getLatestKey(),
353  false /*doActivate*/,
354  &groupInfo.second.latestKeyMemberMap_ /*groupMembers*/,
355  0 /*progressBar*/,
356  0 /*accumulateErrors*/,
357  &groupInfo.second.latestKeyGroupComment_,
358  &groupInfo.second.latestKeyGroupAuthor_,
359  &groupInfo.second.latestKeyGroupCreationTime_,
360  true /*doNotLoadMember*/,
361  &groupInfo.second.latestKeyGroupTypeString_);
362  }
363  catch(...)
364  {
365  __COUT_WARN__ << "Error occurred loading latest group info into cache for '" << groupInfo.first << "'..." << __E__;
366  groupInfo.second.latestKeyGroupComment_ = "UNKNOWN";
367  groupInfo.second.latestKeyGroupAuthor_ = "UNKNOWN";
368  groupInfo.second.latestKeyGroupCreationTime_ = "0";
369  groupInfo.second.latestKeyGroupTypeString_ = "UNKNOWN";
370  }
371  } // end group info loop
372  } // end get group info
373  catch(const std::runtime_error& e)
374  {
375  __SS__ << "A fatal error occurred reading the info for all table groups. Error: " << e.what() << __E__;
376  __COUT_ERR__ << "\n" << ss.str();
377  if(accumulatedWarnings)
378  *accumulatedWarnings += ss.str();
379  else
380  throw;
381  }
382  catch(...)
383  {
384  __SS__ << "An unknown fatal error occurred reading the info for all table groups." << __E__;
385  __COUT_ERR__ << "\n" << ss.str();
386  if(accumulatedWarnings)
387  *accumulatedWarnings += ss.str();
388  else
389  throw;
390  }
391 
392  return allTableInfo_;
393 } // end getAllTableInfo
394 
395 //==============================================================================
396 // getVersionAliases()
397 // get version aliases organized by table, for currently active backbone tables
398 // add scratch versions to the alias map returned by ConfigurationManager
399 std::map<std::string /*table name*/, std::map<std::string /*version alias*/, TableVersion /*aliased version*/>> ConfigurationManagerRW::getVersionAliases(
400  void) const
401 {
402  //__COUT__ << "getVersionAliases()" << __E__;
403  std::map<std::string /*table name*/, std::map<std::string /*version alias*/, TableVersion /*aliased version*/>> retMap =
404  ConfigurationManager::getVersionAliases();
405 
406  // always have scratch alias for each table that has a scratch version
407  // overwrite map entry if necessary
408  if(!ConfigurationInterface::isVersionTrackingEnabled())
409  for(const auto& tableInfo : allTableInfo_)
410  for(const auto& version : tableInfo.second.versions_)
411  if(version.isScratchVersion())
412  retMap[tableInfo.first][ConfigurationManager::SCRATCH_VERSION_ALIAS] = TableVersion(TableVersion::SCRATCH);
413 
414  return retMap;
415 } // end getVersionAliases()
416 
417 //==============================================================================
418 // setActiveGlobalConfiguration
419 // load table group and activate
420 // deactivates previous table group of same type if necessary
421 void ConfigurationManagerRW::activateTableGroup(const std::string& tableGroupName, TableGroupKey tableGroupKey, std::string* accumulatedTreeErrors)
422 {
423  try
424  {
425  //__COUTV__(accumulatedTreeErrors);
426  loadTableGroup(tableGroupName,
427  tableGroupKey,
428  true, // loads and activates
429  0, // no members needed
430  0, // no progress bar
431  accumulatedTreeErrors); // accumulate warnings or not
432  }
433  catch(...)
434  {
435  __COUT_ERR__ << "There were errors, so de-activating group: " << tableGroupName << " (" << tableGroupKey << ")" << __E__;
436  try // just in case any lingering pieces, let's deactivate
437  {
438  destroyTableGroup(tableGroupName, true);
439  }
440  catch(...)
441  {
442  }
443  throw; // re-throw original exception
444  }
445 
446  __COUT_INFO__ << "Updating persistent active groups to " << ConfigurationManager::ACTIVE_GROUPS_FILENAME << " ..." << __E__;
447  __MOUT_INFO__ << "Updating persistent active groups to " << ConfigurationManager::ACTIVE_GROUPS_FILENAME << " ..." << __E__;
448 
449  std::string fn = ConfigurationManager::ACTIVE_GROUPS_FILENAME;
450  FILE* fp = fopen(fn.c_str(), "w");
451  if(!fp)
452  {
453  __SS__ << "Fatal Error! Unable to open the file " << ConfigurationManager::ACTIVE_GROUPS_FILENAME << " for editing! Is there a permissions problem?"
454  << __E__;
455  __COUT_ERR__ << ss.str();
456  __SS_THROW__;
457  return;
458  }
459 
460  __MCOUT_INFO__("Active Context table group: " << theContextTableGroup_ << "("
461  << (theContextTableGroupKey_ ? theContextTableGroupKey_->toString().c_str() : "-1") << ")" << __E__);
462  __MCOUT_INFO__("Active Backbone table group: " << theBackboneTableGroup_ << "("
463  << (theBackboneTableGroupKey_ ? theBackboneTableGroupKey_->toString().c_str() : "-1") << ")" << __E__);
464  __MCOUT_INFO__("Active Iterate table group: " << theIterateTableGroup_ << "("
465  << (theIterateTableGroupKey_ ? theIterateTableGroupKey_->toString().c_str() : "-1") << ")" << __E__);
466  __MCOUT_INFO__("Active Configuration table group: " << theConfigurationTableGroup_ << "("
467  << (theConfigurationTableGroupKey_ ? theConfigurationTableGroupKey_->toString().c_str() : "-1") << ")"
468  << __E__);
469 
470  fprintf(fp, "%s\n", theContextTableGroup_.c_str());
471  fprintf(fp, "%s\n", theContextTableGroupKey_ ? theContextTableGroupKey_->toString().c_str() : "-1");
472  fprintf(fp, "%s\n", theBackboneTableGroup_.c_str());
473  fprintf(fp, "%s\n", theBackboneTableGroupKey_ ? theBackboneTableGroupKey_->toString().c_str() : "-1");
474  fprintf(fp, "%s\n", theIterateTableGroup_.c_str());
475  fprintf(fp, "%s\n", theIterateTableGroupKey_ ? theIterateTableGroupKey_->toString().c_str() : "-1");
476  fprintf(fp, "%s\n", theConfigurationTableGroup_.c_str());
477  fprintf(fp, "%s\n", theConfigurationTableGroupKey_ ? theConfigurationTableGroupKey_->toString().c_str() : "-1");
478  fclose(fp);
479 
480  //save last activated group
481  {
482  std::pair<std::string /*group name*/, TableGroupKey> activatedGroup(std::string(tableGroupName),tableGroupKey);
483  if(theConfigurationTableGroupKey_ && theConfigurationTableGroup_ == tableGroupName &&
484  *theConfigurationTableGroupKey_ == tableGroupKey)
485  ConfigurationManager::saveGroupNameAndKey(activatedGroup, LAST_ACTIVATED_CONFIG_GROUP_FILE);
486  else if(theContextTableGroupKey_ && theContextTableGroup_ == tableGroupName &&
487  *theContextTableGroupKey_ == tableGroupKey)
488  ConfigurationManager::saveGroupNameAndKey(activatedGroup, LAST_ACTIVATED_CONTEXT_GROUP_FILE);
489  else if(theBackboneTableGroupKey_ && theBackboneTableGroup_ == tableGroupName &&
490  *theBackboneTableGroupKey_ == tableGroupKey)
491  ConfigurationManager::saveGroupNameAndKey(activatedGroup, LAST_ACTIVATED_BACKBONE_GROUP_FILE);
492  else if(theIterateTableGroupKey_ && theIterateTableGroup_ == tableGroupName &&
493  *theIterateTableGroupKey_ == tableGroupKey)
494  ConfigurationManager::saveGroupNameAndKey(activatedGroup, LAST_ACTIVATED_ITERATOR_GROUP_FILE);
495  } //end save last activated group
496 
497 } // end activateTableGroup()
498 
499 //==============================================================================
500 // createTemporaryBackboneView
501 // sourceViewVersion of INVALID is from MockUp, else from valid view version
502 // returns temporary version number (which is always negative)
503 TableVersion ConfigurationManagerRW::createTemporaryBackboneView(TableVersion sourceViewVersion)
504 {
505  __COUT_INFO__ << "Creating temporary backbone view from version " << sourceViewVersion << __E__;
506 
507  // find common available temporary version among backbone members
508  TableVersion tmpVersion = TableVersion::getNextTemporaryVersion(); // get the default temporary version
509  TableVersion retTmpVersion;
510  auto backboneMemberNames = ConfigurationManager::getBackboneMemberNames();
511  for(auto& name : backboneMemberNames)
512  {
513  retTmpVersion = ConfigurationManager::getTableByName(name)->getNextTemporaryVersion();
514  if(retTmpVersion < tmpVersion)
515  tmpVersion = retTmpVersion;
516  }
517 
518  __COUT__ << "Common temporary backbone version found as " << tmpVersion << __E__;
519 
520  // create temporary views from source version to destination temporary version
521  for(auto& name : backboneMemberNames)
522  {
523  retTmpVersion = getTableByName(name)->createTemporaryView(sourceViewVersion, tmpVersion);
524  if(retTmpVersion != tmpVersion)
525  {
526  __SS__ << "Failure! Temporary view requested was " << tmpVersion << ". Mismatched temporary view created: " << retTmpVersion << __E__;
527  __COUT_ERR__ << ss.str();
528  __SS_THROW__;
529  }
530  }
531 
532  return tmpVersion;
533 } // end createTemporaryBackboneView()
534 
535 //==============================================================================
536 TableBase* ConfigurationManagerRW::getTableByName(const std::string& tableName)
537 {
538  if(nameToTableMap_.find(tableName) == nameToTableMap_.end())
539  {
540  if(tableName == ConfigurationManager::ARTDAQ_TOP_TABLE_NAME)
541  {
542  __COUT_WARN__ << "Since target table was the artdaq top configuration level, "
543  "attempting to help user by appending to core tables file: "
544  << CORE_TABLE_INFO_FILENAME << __E__;
545  FILE* fp = fopen((CORE_TABLE_INFO_FILENAME).c_str(), "a");
546  if(fp)
547  {
548  fprintf(fp, "\nARTDAQ/*");
549  fclose(fp);
550  }
551  }
552 
553  __SS__ << "Table not found with name: " << tableName << __E__;
554  size_t f;
555  if((f = tableName.find(' ')) != std::string::npos)
556  ss << "There was a space character found in the table name needle at "
557  "position "
558  << f << " in the string (was this intended?). " << __E__;
559 
560  ss << "\nIf you think this table should exist in the core set of tables, try running 'UpdateOTS.sh --tables' to update your tables, then relaunch ots."
561  << __E__;
562  ss << "\nTables must be defined in $USER_DATA/TableInfo to exist in ots. Please verify your table definitions, and then restart ots." << __E__;
563  __COUT_ERR__ << "\n" << ss.str();
564  __SS_THROW__;
565  }
566  return nameToTableMap_[tableName];
567 } // end getTableByName()
568 
569 //==============================================================================
570 // getVersionedTableByName
571 // Used by table GUI to load a particular table-version pair as the active version.
572 // This table instance must already exist and be owned by ConfigurationManager.
573 // return null pointer on failure, on success return table pointer.
574 TableBase* ConfigurationManagerRW::getVersionedTableByName(const std::string& tableName,
575  TableVersion version,
576  bool looseColumnMatching /* =false */,
577  std::string* accumulatedErrors /* =0 */)
578 {
579  auto it = nameToTableMap_.find(tableName);
580  if(it == nameToTableMap_.end())
581  {
582  __SS__ << "\nCan not find table named '" << tableName << "'\n\n\n\nYou need to load the table before it can be used."
583  << "It probably is missing from the member list of the Table "
584  "Group that was loaded?\n\n\n\n\n"
585  << __E__;
586  __SS_THROW__;
587  }
588  TableBase* table = it->second;
589 
590  if(version.isTemporaryVersion())
591  table->setActiveView(version);
592  else
593  theInterface_->get(table,
594  tableName,
595  0,
596  0,
597  false, // fill w/version
598  version,
599  false, // do not reset
600  looseColumnMatching,
601  accumulatedErrors);
602  return table;
603 } // end getVersionedTableByName()
604 
605 //==============================================================================
606 // saveNewTable
607 // saves version, makes the new version the active version, and returns new version
608 TableVersion ConfigurationManagerRW::saveNewTable(const std::string& tableName, TableVersion temporaryVersion,
609  bool makeTemporary) //,
610 // bool saveToScratchVersion)
611 {
612  TableVersion newVersion(temporaryVersion);
613 
614  // set author of version
615  TableBase* table = getTableByName(tableName);
616  table->getTemporaryView(temporaryVersion)->setAuthor(username_);
617  // NOTE: author is assigned to permanent versions when saved to DBI
618 
619  if(!makeTemporary) // saveNewVersion makes the new version the active version
620  newVersion = theInterface_->saveNewVersion(table, temporaryVersion);
621  else // make the temporary version active
622  table->setActiveView(newVersion);
623 
624  // if there is a problem, try to recover
625  while(!makeTemporary && !newVersion.isScratchVersion() && allTableInfo_[tableName].versions_.find(newVersion) != allTableInfo_[tableName].versions_.end())
626  {
627  __COUT_ERR__ << "What happenened!?? ERROR::: new persistent version v" << newVersion
628  << " already exists!? How is it possible? Retrace your steps and "
629  "tell an admin."
630  << __E__;
631 
632  // create a new temporary version of the target view
633  temporaryVersion = table->createTemporaryView(newVersion);
634 
635  if(newVersion.isTemporaryVersion())
636  newVersion = temporaryVersion;
637  else
638  newVersion = TableVersion::getNextVersion(newVersion);
639 
640  __COUT_WARN__ << "Attempting to recover and use v" << newVersion << __E__;
641 
642  if(!makeTemporary) // saveNewVersion makes the new version the active version
643  newVersion = theInterface_->saveNewVersion(table, temporaryVersion, newVersion);
644  else // make the temporary version active
645  table->setActiveView(newVersion);
646  }
647 
648  if(newVersion.isInvalid())
649  {
650  __SS__ << "Something went wrong saving the new version v" << newVersion << ". What happened?! (duplicates? database error?)" << __E__;
651  __COUT_ERR__ << "\n" << ss.str();
652  __SS_THROW__;
653  }
654 
655  // update allTableInfo_ with the new version
656  allTableInfo_[tableName].versions_.insert(newVersion);
657 
658  //__COUT__ << "New '" << tableName << "' version added to info " << newVersion << __E__;
659 
660  // table->getView().print();
661  return newVersion;
662 } // end saveNewTable()
663 
664 //==============================================================================
665 // eraseTemporaryVersion
666 // if version is invalid then erases ALL temporary versions
667 //
668 // maintains allTableInfo_ also while erasing
669 void ConfigurationManagerRW::eraseTemporaryVersion(const std::string& tableName, TableVersion targetVersion)
670 {
671  TableBase* table = getTableByName(tableName);
672 
673  table->trimTemporary(targetVersion);
674 
675  // if allTableInfo_ is not setup, then done
676  if(allTableInfo_.find(tableName) == allTableInfo_.end())
677  return;
678  // else cleanup table info
679 
680  if(targetVersion.isInvalid())
681  {
682  // erase all temporary versions!
683  for(auto it = allTableInfo_[tableName].versions_.begin(); it != allTableInfo_[tableName].versions_.end();
684  /*no increment*/)
685  {
686  if(it->isTemporaryVersion())
687  {
688  __COUT__ << "Removing '" << tableName << "' version info: " << *it << __E__;
689  allTableInfo_[tableName].versions_.erase(it++);
690  }
691  else
692  ++it;
693  }
694  }
695  else // erase target version only
696  {
697  //__COUT__ << "Removing '" << tableName << "' version info: " << targetVersion << __E__;
698  auto it = allTableInfo_[tableName].versions_.find(targetVersion);
699  if(it == allTableInfo_[tableName].versions_.end())
700  {
701  __COUT__ << "Target '" << tableName << "' version v" << targetVersion << " was not found in info versions..." << __E__;
702  return;
703  }
704  allTableInfo_[tableName].versions_.erase(allTableInfo_[tableName].versions_.find(targetVersion));
705  //__COUT__ << "Target '" << tableName << "' version v" <<
706  // targetVersion << " was erased from info." << __E__;
707  }
708 } // end eraseTemporaryVersion()
709 
710 //==============================================================================
711 // clearCachedVersions
712 // clear ALL cached persistent versions (does not erase temporary versions)
713 //
714 // maintains allTableInfo_ also while erasing (trivial, do nothing)
715 void ConfigurationManagerRW::clearCachedVersions(const std::string& tableName)
716 {
717  TableBase* table = getTableByName(tableName);
718 
719  table->trimCache(0);
720 } // end clearCachedVersions()
721 
722 //==============================================================================
723 // clearAllCachedVersions
724 // clear ALL cached persistent versions (does not erase temporary versions)
725 //
726 // maintains allTableInfo_ also while erasing (trivial, do nothing)
727 void ConfigurationManagerRW::clearAllCachedVersions()
728 {
729  for(auto configInfo : allTableInfo_)
730  configInfo.second.tablePtr_->trimCache(0);
731 } // end clearAllCachedVersions()
732 
733 //==============================================================================
734 // copyViewToCurrentColumns
735 TableVersion ConfigurationManagerRW::copyViewToCurrentColumns(const std::string& tableName, TableVersion sourceVersion)
736 {
737  getTableByName(tableName)->reset();
738 
739  // make sure source version is loaded
740  // need to load with loose column rules!
741  TableBase* table = getVersionedTableByName(tableName, TableVersion(sourceVersion), true);
742 
743  // copy from source version to a new temporary version
744  TableVersion newTemporaryVersion = table->copyView(table->getView(), TableVersion(), username_);
745 
746  // update allTableInfo_ with the new version
747  allTableInfo_[tableName].versions_.insert(newTemporaryVersion);
748 
749  return newTemporaryVersion;
750 } // end copyViewToCurrentColumns()
751 
752 //==============================================================================
753 // cacheGroupKey
754 void ConfigurationManagerRW::cacheGroupKey(const std::string& groupName, TableGroupKey key)
755 {
756  allGroupInfo_[groupName].keys_.emplace(key);
757 
758  // __SS__ << "Now keys are: " << __E__;
759  // for(auto& key:allGroupInfo_[groupName].keys_)
760  // ss << "\t" << key << __E__;
761  // __COUT__ << ss.str() << __E__;
762 } // end cacheGroupKey()
763 
764 //==============================================================================
765 // getGroupInfo
766 // the interface is slow when there are a lot of groups..
767 // so plan is to maintain local cache of recent group info
768 const GroupInfo& ConfigurationManagerRW::getGroupInfo(const std::string& groupName)
769 {
770  // //NOTE: seems like this filter is taking the long amount of time
771  // std::set<std::string /*name*/> fullGroupNames =
772  // theInterface_->getAllTableGroupNames(groupName); //db filter by
773  // group name
774 
775  // so instead caching ourselves...
776  auto it = allGroupInfo_.find(groupName);
777  if(it == allGroupInfo_.end())
778  {
779  __SS__ << "Group name '" << groupName << "' not found in group info! (creating empty info)" << __E__;
780  __COUT_WARN__ << ss.str();
781  //__SS_THROW__;
782  return allGroupInfo_[groupName];
783  }
784  return it->second;
785 } // end getGroupInfo()
786 
787 //==============================================================================
788 // findTableGroup
789 // return group with same name and same members and same aliases
790 // else return invalid key
791 //
792 // Note: if aliases, then member alias is matched (not member
793 //
794 // Note: this is taking too long when there are a ton of groups.
795 // Change to going back only a limited number.. (but the order also comes in alpha order
796 // from theInterface_->getAllTableGroupNames which is a problem for choosing
797 // the most recent to check. )
798 TableGroupKey ConfigurationManagerRW::findTableGroup(const std::string& groupName,
799  const std::map<std::string, TableVersion>& groupMemberMap,
800  const std::map<std::string /*name*/, std::string /*alias*/>& groupAliases)
801 {
802  // //NOTE: seems like this filter is taking the long amount of time
803  // std::set<std::string /*name*/> fullGroupNames =
804  // theInterface_->getAllTableGroupNames(groupName); //db filter by
805  // group name
806  const GroupInfo& groupInfo = getGroupInfo(groupName);
807 
808  // std::string name;
809  // TableGroupKey key;
810  std::map<std::string /*name*/, TableVersion /*version*/> compareToMemberMap;
811  std::map<std::string /*name*/, std::string /*alias*/> compareToGroupAliases;
812  bool isDifferent;
813 
814  const unsigned int MAX_DEPTH_TO_CHECK = 20;
815  unsigned int keyMinToCheck = 0;
816 
817  if(groupInfo.keys_.size())
818  keyMinToCheck = groupInfo.keys_.rbegin()->key();
819  if(keyMinToCheck > MAX_DEPTH_TO_CHECK)
820  {
821  keyMinToCheck -= MAX_DEPTH_TO_CHECK;
822  __COUT__ << "Checking groups back to key... " << keyMinToCheck << __E__;
823  }
824  else
825  {
826  keyMinToCheck = 0;
827  __COUT__ << "Checking all groups." << __E__;
828  }
829 
830  // have min key to check, now loop through and check groups
831  // std::string fullName;
832  for(const auto& key : groupInfo.keys_)
833  {
834  // TableGroupKey::getGroupNameAndKey(fullName,name,key);
835 
836  if(key.key() < keyMinToCheck)
837  continue; // skip keys that are too old
838 
839  // fullName = TableGroupKey::getFullGroupString(groupName,key);
840  //
841  // __COUT__ << "checking group... " << fullName << __E__;
842  //
843  // compareToMemberMap =
844  // theInterface_->getTableGroupMembers(fullName);
845 
846  loadTableGroup(groupName,
847  key,
848  false /*doActivate*/,
849  &compareToMemberMap /*memberMap*/,
850  0,
851  0,
852  0,
853  0,
854  0, /*null pointers*/
855  true /*doNotLoadMember*/,
856  0 /*groupTypeString*/,
857  &compareToGroupAliases);
858 
859  isDifferent = false;
860  for(auto& memberPair : groupMemberMap)
861  {
862  //__COUT__ << memberPair.first << " - " << memberPair.second << __E__;
863 
864  if(groupAliases.find(memberPair.first) != groupAliases.end())
865  {
866  // handle this table as alias, not version
867  if(compareToGroupAliases.find(memberPair.first) == compareToGroupAliases.end() || // alias is missing
868  groupAliases.at(memberPair.first) != compareToGroupAliases.at(memberPair.first))
869  { // then different
870  //__COUT__ << "alias mismatch found!" << __E__;
871  isDifferent = true;
872  break;
873  }
874  else
875  continue;
876  } // else check if compareTo group is using an alias for table
877  else if(compareToGroupAliases.find(memberPair.first) != compareToGroupAliases.end())
878  {
879  // then different
880  //__COUT__ << "alias mismatch found!" << __E__;
881  isDifferent = true;
882  break;
883 
884  } // else handle as table version comparison
885  else if(compareToMemberMap.find(memberPair.first) == compareToMemberMap.end() || // name is missing
886  memberPair.second != compareToMemberMap.at(memberPair.first)) // or version mismatch
887  { // then different
888  //__COUT__ << "mismatch found!" << __E__;
889  isDifferent = true;
890  break;
891  }
892  }
893  if(isDifferent)
894  continue;
895 
896  // check member size for exact match
897  if(groupMemberMap.size() != compareToMemberMap.size())
898  continue; // different size, so not same (groupMemberMap is a subset of
899  // memberPairs)
900 
901  __COUT__ << "Found exact match with key: " << key << __E__;
902  // else found an exact match!
903  return key;
904  }
905  __COUT__ << "No match found - this group is new!" << __E__;
906  // if here, then no match found
907  return TableGroupKey(); // return invalid key
908 }
909 
910 //==============================================================================
911 // saveNewTableGroup
912 // saves new group and returns the new group key
913 // if previousVersion is provided, attempts to just bump that version
914 // else, bumps latest version found in db
915 //
916 // Note: groupMembers map will get modified with group metadata table version
917 TableGroupKey ConfigurationManagerRW::saveNewTableGroup(const std::string& groupName,
918  std::map<std::string, TableVersion>& groupMembers,
919  const std::string& groupComment,
920  std::map<std::string /*table*/, std::string /*alias*/>* groupAliases)
921 {
922  // steps:
923  // determine new group key
924  // verify group members
925  // verify groupNameWithKey
926  // verify store
927 
928  if(groupMembers.size() == 0) // do not allow empty groups
929  {
930  __SS__ << "Empty group member list. Can not create a group without members!" << __E__;
931  __SS_THROW__;
932  }
933 
934  // determine new group key
935  TableGroupKey newKey = TableGroupKey::getNextKey(theInterface_->findLatestGroupKey(groupName));
936 
937  __COUT__ << "New Key for group: " << groupName << " found as " << newKey << __E__;
938 
939  // verify group members
940  // - use all table info
941  std::map<std::string, TableInfo> allCfgInfo = getAllTableInfo();
942  for(auto& memberPair : groupMembers)
943  {
944  // check member name
945  if(allCfgInfo.find(memberPair.first) == allCfgInfo.end())
946  {
947  __COUT_ERR__ << "Group member \"" << memberPair.first << "\" not found in database!";
948 
949  if(groupMetadataTable_.getTableName() == memberPair.first)
950  {
951  __COUT_WARN__ << "Looks like this is the groupMetadataTable_ '" << ConfigurationInterface::GROUP_METADATA_TABLE_NAME
952  << ".' Note that this table is added to the member map when groups "
953  "are saved."
954  << "It should not be part of member map when calling this function." << __E__;
955  __COUT__ << "Attempting to recover." << __E__;
956  groupMembers.erase(groupMembers.find(memberPair.first));
957  }
958  else
959  {
960  __SS__ << ("Group member not found!") << __E__;
961  __SS_THROW__;
962  }
963  }
964  // check member version
965  if(allCfgInfo[memberPair.first].versions_.find(memberPair.second) == allCfgInfo[memberPair.first].versions_.end())
966  {
967  __SS__ << "Group member \"" << memberPair.first << "\" version \"" << memberPair.second << "\" not found in database!";
968  __SS_THROW__;
969  }
970  } // end verify members
971 
972  // verify group aliases
973  if(groupAliases)
974  {
975  for(auto& aliasPair : *groupAliases)
976  {
977  // check for alias table in member names
978  if(groupMembers.find(aliasPair.first) == groupMembers.end())
979  {
980  __COUT_ERR__ << "Group member \"" << aliasPair.first << "\" not found in group member map!";
981 
982  __SS__ << ("Alias table not found in member list!") << __E__;
983  __SS_THROW__;
984  }
985  }
986  } // end verify group aliases
987 
988  // verify groupNameWithKey and attempt to store
989  try
990  {
991  // save meta data for group; reuse groupMetadataTable_
992  std::string groupAliasesString = "";
993  if(groupAliases)
994  groupAliasesString = StringMacros::mapToString(*groupAliases, "," /*primary delimeter*/, ":" /*secondary delimeter*/);
995  __COUT__ << "Metadata: " << username_ << " " << time(0) << " " << groupComment << " " << groupAliasesString << __E__;
996 
997  // to compensate for unusual errors upstream, make sure the metadata table has one
998  // row
999  while(groupMetadataTable_.getViewP()->getNumberOfRows() > 1)
1000  groupMetadataTable_.getViewP()->deleteRow(0);
1001  if(groupMetadataTable_.getViewP()->getNumberOfRows() == 0)
1002  groupMetadataTable_.getViewP()->addRow();
1003 
1004  // columns are uid,comment,author,time
1005  groupMetadataTable_.getViewP()->setValue(groupAliasesString, 0, ConfigurationManager::METADATA_COL_ALIASES);
1006  groupMetadataTable_.getViewP()->setValue(groupComment, 0, ConfigurationManager::METADATA_COL_COMMENT);
1007  groupMetadataTable_.getViewP()->setValue(username_, 0, ConfigurationManager::METADATA_COL_AUTHOR);
1008  groupMetadataTable_.getViewP()->setValue(time(0), 0, ConfigurationManager::METADATA_COL_TIMESTAMP);
1009 
1010  // set version to first available persistent version
1011  groupMetadataTable_.getViewP()->setVersion(TableVersion::getNextVersion(theInterface_->findLatestVersion(&groupMetadataTable_)));
1012 
1013  // groupMetadataTable_.print();
1014 
1015  theInterface_->saveActiveVersion(&groupMetadataTable_);
1016 
1017  // force groupMetadataTable_ to be a member for the group
1018  groupMembers[groupMetadataTable_.getTableName()] = groupMetadataTable_.getViewVersion();
1019 
1020  theInterface_->saveTableGroup(groupMembers, TableGroupKey::getFullGroupString(groupName, newKey));
1021  __COUT__ << "Created table group: " << groupName << ":" << newKey << __E__;
1022  }
1023  catch(std::runtime_error& e)
1024  {
1025  __COUT_ERR__ << "Failed to create table group: " << groupName << ":" << newKey << __E__;
1026  __COUT_ERR__ << "\n\n" << e.what() << __E__;
1027  throw;
1028  }
1029  catch(...)
1030  {
1031  __COUT_ERR__ << "Failed to create table group: " << groupName << ":" << newKey << __E__;
1032  throw;
1033  }
1034 
1035  // store cache of recent groups
1036  cacheGroupKey(groupName, newKey);
1037 
1038  // at this point succeeded!
1039  return newKey;
1040 } // end saveNewTableGroup()
1041 
1042 //==============================================================================
1043 // saveNewBackbone
1044 // makes the new version the active version and returns new version number
1045 // INVALID will give a new backbone from mockup
1046 TableVersion ConfigurationManagerRW::saveNewBackbone(TableVersion temporaryVersion)
1047 {
1048  __COUT_INFO__ << "Creating new backbone from temporary version " << temporaryVersion << __E__;
1049 
1050  // find common available temporary version among backbone members
1051  TableVersion newVersion(TableVersion::DEFAULT);
1052  TableVersion retNewVersion;
1053  auto backboneMemberNames = ConfigurationManager::getBackboneMemberNames();
1054  for(auto& name : backboneMemberNames)
1055  {
1056  retNewVersion = ConfigurationManager::getTableByName(name)->getNextVersion();
1057  __COUT__ << "New version for backbone member (" << name << "): " << retNewVersion << __E__;
1058  if(retNewVersion > newVersion)
1059  newVersion = retNewVersion;
1060  }
1061 
1062  __COUT__ << "Common new backbone version found as " << newVersion << __E__;
1063 
1064  // create new views from source temporary version
1065  for(auto& name : backboneMemberNames)
1066  {
1067  // saveNewVersion makes the new version the active version
1068  retNewVersion = getConfigurationInterface()->saveNewVersion(getTableByName(name), temporaryVersion, newVersion);
1069  if(retNewVersion != newVersion)
1070  {
1071  __SS__ << "Failure! New view requested was " << newVersion << ". Mismatched new view created: " << retNewVersion << __E__;
1072  __COUT_ERR__ << ss.str();
1073  __SS_THROW__;
1074  }
1075  }
1076 
1077  return newVersion;
1078 } // end saveNewBackbone()
1079 
1080 //==============================================================================
1081 // saveModifiedVersionXML
1082 //
1083 // once source version has been modified in temporary version
1084 // this function finishes it off.
1085 TableVersion ConfigurationManagerRW::saveModifiedVersion(const std::string& tableName,
1086  TableVersion originalVersion,
1087  bool makeTemporary,
1088  TableBase* table,
1089  TableVersion temporaryModifiedVersion,
1090  bool ignoreDuplicates /*= false*/,
1091  bool lookForEquivalent /*= false*/,
1092  bool* foundEquivalent /*= nullptr*/)
1093 {
1094  bool needToEraseTemporarySource = (originalVersion.isTemporaryVersion() && !makeTemporary);
1095 
1096  if(foundEquivalent)
1097  *foundEquivalent = false; // initialize
1098 
1099  // check for duplicate tables already in cache
1100  if(!ignoreDuplicates)
1101  {
1102  __COUT__ << "Checking for duplicate '" << tableName << "' tables..." << __E__;
1103 
1104  TableVersion duplicateVersion;
1105 
1106  {
1107  //"DEEP" checking
1108  // load into cache 'recent' versions for this table
1109  // 'recent' := those already in cache, plus highest version numbers not
1110  // in cache
1111  const std::map<std::string, TableInfo>& allTableInfo = getAllTableInfo(); // do not refresh
1112 
1113  auto versionReverseIterator = allTableInfo.at(tableName).versions_.rbegin(); // get reverse iterator
1114  __COUT__ << "Filling up '" << tableName << "' cache from " << table->getNumberOfStoredViews() << " to max count of " << table->MAX_VIEWS_IN_CACHE
1115  << __E__;
1116  for(; table->getNumberOfStoredViews() < table->MAX_VIEWS_IN_CACHE && versionReverseIterator != allTableInfo.at(tableName).versions_.rend();
1117  ++versionReverseIterator)
1118  {
1119  __COUT__ << "'" << tableName << "' versions in reverse order " << *versionReverseIterator << __E__;
1120  try
1121  {
1122  getVersionedTableByName(tableName, *versionReverseIterator); // load to cache
1123  }
1124  catch(const std::runtime_error& e)
1125  {
1126  // ignore error
1127 
1128  //__COUT__ << "Error loading historical '" << tableName <<
1129  // "' version, but ignoring: "
1130  // << e.what() << __E__;
1131  }
1132  }
1133  }
1134 
1135  __COUT__ << "Checking '" << tableName << "' duplicate..." << __E__;
1136 
1137  duplicateVersion = table->checkForDuplicate(
1138  temporaryModifiedVersion,
1139  (!originalVersion.isTemporaryVersion() && !makeTemporary) ? TableVersion() : // if from persistent to persistent, then include original version in
1140  // search
1141  originalVersion);
1142 
1143  if(lookForEquivalent && !duplicateVersion.isInvalid())
1144  {
1145  // found an equivalent!
1146  __COUT__ << "Equivalent '" << tableName << "' table found in version v" << duplicateVersion << __E__;
1147 
1148  // if duplicate version was temporary, do not use
1149  if(duplicateVersion.isTemporaryVersion() && !makeTemporary)
1150  {
1151  __COUT__ << "Need persistent. Duplicate '" << tableName
1152  << "' version was temporary. "
1153  "Abandoning duplicate."
1154  << __E__;
1155  duplicateVersion = TableVersion(); // set invalid
1156  }
1157  else
1158  {
1159  // erase and return equivalent version
1160 
1161  // erase modified equivalent version
1162  eraseTemporaryVersion(tableName, temporaryModifiedVersion);
1163 
1164  // erase original if needed
1165  if(needToEraseTemporarySource)
1166  eraseTemporaryVersion(tableName, originalVersion);
1167 
1168  if(foundEquivalent)
1169  *foundEquivalent = true;
1170 
1171  // xmlOut.addTextElementToData("savedName", tableName);
1172  // xmlOut.addTextElementToData("savedVersion", duplicateVersion.toString());
1173  // xmlOut.addTextElementToData("foundEquivalentVersion", "1");
1174  // xmlOut.addTextElementToData(tableName + "_foundEquivalentVersion", "1");
1175 
1176  __COUT__ << "\t\t Equivalent '" << tableName << "' assigned version: " << duplicateVersion << __E__;
1177 
1178  return duplicateVersion;
1179  }
1180  }
1181 
1182  if(!duplicateVersion.isInvalid())
1183  {
1184  __SS__ << "This version of table '" << tableName << "' is identical to another version currently cached v" << duplicateVersion
1185  << ". No reason to save a duplicate." << __E__;
1186  __COUT_ERR__ << "\n" << ss.str();
1187 
1188  // delete temporaryModifiedVersion
1189  table->eraseView(temporaryModifiedVersion);
1190  __SS_THROW__;
1191  }
1192 
1193  __COUT__ << "Check for duplicate '" << tableName << "' tables complete." << __E__;
1194  }
1195 
1196  if(makeTemporary)
1197  __COUT__ << "\t\t**************************** Save as temporary '" << tableName << "' table version" << __E__;
1198  else
1199  __COUT__ << "\t\t**************************** Save as new '" << tableName << "' table version" << __E__;
1200 
1201  TableVersion newAssignedVersion = saveNewTable(tableName, temporaryModifiedVersion, makeTemporary);
1202 
1203  if(needToEraseTemporarySource)
1204  eraseTemporaryVersion(tableName, originalVersion);
1205 
1206  // xmlOut.addTextElementToData("savedName", tableName);
1207  // xmlOut.addTextElementToData("savedVersion", newAssignedVersion.toString());
1208 
1209  __COUT__ << "\t\t '" << tableName << "' new assigned version: " << newAssignedVersion << __E__;
1210  return newAssignedVersion;
1211 } // end saveModifiedVersion()
1212 
1213 //==============================================================================
1214 GroupEditStruct::GroupEditStruct(const ConfigurationManager::GroupType& groupType, ConfigurationManagerRW* cfgMgr)
1215  : groupType_(groupType), originalGroupName_(cfgMgr->getActiveGroupName(groupType)), originalGroupKey_(cfgMgr->getActiveGroupKey(groupType)), cfgMgr_(cfgMgr)
1216 {
1217  if(originalGroupName_ == "" || originalGroupKey_.isInvalid())
1218  {
1219  __SS__ << "Error! No active group found for type '" << ConfigurationManager::convertGroupTypeToName(groupType)
1220  << ".' There must be an active group to edit the group." << __E__;
1221  __SS_THROW__;
1222  }
1223 
1224  __COUT__ << "Extracting Group-Edit Struct for type " << ConfigurationManager::convertGroupTypeToName(groupType) << __E__;
1225 
1226  std::map<std::string, TableVersion> activeTables = cfgMgr->getActiveVersions();
1227 
1228  const std::set<std::string>& memberNames =
1229  groupType == ConfigurationManager::GroupType::CONTEXT_TYPE
1230  ? ConfigurationManager::getContextMemberNames()
1231  : (groupType == ConfigurationManager::GroupType::BACKBONE_TYPE
1232  ? ConfigurationManager::getBackboneMemberNames()
1233  : (groupType == ConfigurationManager::GroupType::ITERATE_TYPE ? ConfigurationManager::getIterateMemberNames()
1234  : cfgMgr->getConfigurationMemberNames()));
1235 
1236  for(auto& memberName : memberNames)
1237  try
1238  {
1239  //__COUT__ << memberName << " v" << activeTables.at(memberName) << __E__;
1240  groupMembers_.emplace(std::make_pair(memberName, activeTables.at(memberName)));
1241 
1242  groupTables_.emplace(std::make_pair(memberName, TableEditStruct(memberName, cfgMgr))); // Table ready for editing!
1243  }
1244  catch(...)
1245  {
1246  __SS__ << "Error! Could not find group member table '" << memberName << "' for group type '"
1247  << ConfigurationManager::convertGroupTypeToName(groupType) << ".' All group members must be present to create the group editing structure."
1248  << __E__;
1249  __SS_THROW__;
1250  }
1251 
1252 } // end GroupEditStruct constructor()
1253 
1254 //==============================================================================
1255 GroupEditStruct::~GroupEditStruct()
1256 {
1257  __COUT__ << "GroupEditStruct from editing '" << originalGroupName_ << "(" << originalGroupKey_ << ")' Destructing..." << __E__;
1258  dropChanges();
1259  __COUT__ << "GroupEditStruct from editing '" << originalGroupName_ << "(" << originalGroupKey_ << ")' Desctructed." << __E__;
1260 } // end GroupEditStruct destructor()
1261 
1262 //==============================================================================
1263 // Note: if markModified, and table not found in group, this function will try to add it to group
1264 TableEditStruct& GroupEditStruct::getTableEditStruct(const std::string& tableName, bool markModified /*= false*/)
1265 {
1266  auto it = groupTables_.find(tableName);
1267  if(it == groupTables_.end())
1268  {
1269  if(groupType_ == ConfigurationManager::GroupType::CONFIGURATION_TYPE && markModified)
1270  {
1271  __COUT__ << "Table '" << tableName << "' not found in configuration table members from editing '" << originalGroupName_ << "(" << originalGroupKey_
1272  << ")..."
1273  << " Attempting to add it!" << __E__;
1274 
1275  // emplace returns pair<object,bool wasAdded>
1276  auto newIt = groupTables_.emplace(std::make_pair(tableName, TableEditStruct(tableName, cfgMgr_))); // Table ready for editing!
1277  if(newIt.second)
1278  {
1279  newIt.first->second.modified_ = markModified; // could indicate 'dirty' immediately in user code, which will cause a save of table
1280  groupMembers_.emplace(std::make_pair(tableName, newIt.first->second.temporaryVersion_));
1281  return newIt.first->second;
1282  }
1283  __COUT_ERR__ << "Failed to emplace new table..." << __E__;
1284  }
1285 
1286  __SS__ << "Table '" << tableName << "' not found in table members from editing '" << originalGroupName_ << "(" << originalGroupKey_ << ")!'" << __E__;
1287  __SS_THROW__;
1288  }
1289  it->second.modified_ = markModified; // could indicate 'dirty' immediately in user code, which will cause a save of table
1290  return it->second;
1291 } // end getTableEditStruct()
1292 
1293 //==============================================================================
1294 void GroupEditStruct::dropChanges()
1295 {
1296  __COUT__ << "Dropping unsaved changes from editing '" << originalGroupName_ << "(" << originalGroupKey_ << ")'..." << __E__;
1297 
1298  ConfigurationManagerRW* cfgMgr = cfgMgr_;
1299 
1300  // drop all temporary versions
1301  for(auto& groupTable : groupTables_)
1302  if(groupTable.second.createdTemporaryVersion_) // if temporary version created here
1303  {
1304  //__COUT__ << "Erasing temporary version " << groupTable.second.tableName_ << "-v"
1305  // << groupTable.second.temporaryVersion_ << __E__;
1306  // erase with proper version management
1307  cfgMgr->eraseTemporaryVersion(groupTable.second.tableName_, groupTable.second.temporaryVersion_);
1308  groupTable.second.createdTemporaryVersion_ = false;
1309  groupTable.second.modified_ = false;
1310  }
1311 
1312  __COUT__ << "Unsaved changes dropped from editing '" << originalGroupName_ << "(" << originalGroupKey_ << ").'" << __E__;
1313 } // end GroupEditStruct::dropChanges()
1314 
1315 //==============================================================================
1316 void GroupEditStruct::saveChanges(const std::string& groupNameToSave,
1317  TableGroupKey& newGroupKey,
1318  bool* foundEquivalentGroupKey /*= nullptr*/,
1319  bool activateNewGroup /*= false*/,
1320  bool updateGroupAliases /*= false*/,
1321  bool updateTableAliases /*= false*/,
1322  TableGroupKey* newBackboneKey /*= nullptr*/,
1323  bool* foundEquivalentBackboneKey /*= nullptr*/,
1324  std::string* accumulatedWarnings /*= nullptr*/)
1325 {
1326  __COUT__ << "Saving changes..." << __E__;
1327 
1328  newGroupKey = TableGroupKey(); // invalidate reference parameter
1329  if(newBackboneKey)
1330  *newBackboneKey = TableGroupKey(); // invalidate reference parameter
1331  if(foundEquivalentBackboneKey)
1332  *foundEquivalentBackboneKey = false; // clear to start
1333  ConfigurationManagerRW* cfgMgr = cfgMgr_;
1334 
1335  // save all temporary modified versions
1336  for(auto& groupTable : groupTables_)
1337  {
1338  if(!groupTable.second.modified_)
1339  continue; // skip if not modified
1340 
1341  __COUT__ << "Original version is " << groupTable.second.tableName_ << "-v" << groupTable.second.originalVersion_ << __E__;
1342 
1343  groupMembers_.at(groupTable.first) =
1344  cfgMgr->saveModifiedVersion(groupTable.second.tableName_,
1345  groupTable.second.originalVersion_,
1346  true /*make temporary*/,
1347  groupTable.second.table_,
1348  groupTable.second.temporaryVersion_,
1349  true /*ignoreDuplicates*/); // make temporary version to save persistent version properly
1350 
1351  __COUT__ << "Temporary target version is " << groupTable.second.tableName_ << "-v" << groupMembers_.at(groupTable.first) << "-v"
1352  << groupTable.second.temporaryVersion_ << __E__;
1353 
1354  groupMembers_.at(groupTable.first) = cfgMgr->saveModifiedVersion(groupTable.second.tableName_,
1355  groupTable.second.originalVersion_,
1356  false /*make temporary*/,
1357  groupTable.second.table_,
1358  groupTable.second.temporaryVersion_,
1359  false /*ignoreDuplicates*/,
1360  true /*lookForEquivalent*/); // save persistent version properly
1361 
1362  __COUT__ << "Final target version is " << groupTable.second.tableName_ << "-v" << groupMembers_.at(groupTable.first) << __E__;
1363 
1364  groupTable.second.modified_ = false; // clear modified flag
1365  groupTable.second.createdTemporaryVersion_ = false; // modified version is gone
1366  } // loop through table edit structs
1367 
1368  for(auto& table : groupMembers_)
1369  {
1370  __COUT__ << table.first << " v" << table.second << __E__;
1371  }
1372 
1373  __COUT__ << "Checking for duplicate groups..." << __E__;
1374  newGroupKey = cfgMgr->findTableGroup(groupNameToSave, groupMembers_);
1375 
1376  if(!newGroupKey.isInvalid())
1377  {
1378  __COUT__ << "Found equivalent group key (" << newGroupKey << ") for " << groupNameToSave << "." << __E__;
1379  if(foundEquivalentGroupKey)
1380  *foundEquivalentGroupKey = true;
1381  }
1382  else
1383  {
1384  newGroupKey = cfgMgr->saveNewTableGroup(groupNameToSave, groupMembers_);
1385  __COUT__ << "Saved new Context group key (" << newGroupKey << ") for " << groupNameToSave << "." << __E__;
1386  }
1387 
1388  bool groupAliasChange = false;
1389  bool tableAliasChange = false;
1390 
1391  GroupEditStruct backboneGroupEdit(ConfigurationManager::GroupType::BACKBONE_TYPE, cfgMgr);
1392 
1393  if(groupType_ != ConfigurationManager::GroupType::BACKBONE_TYPE && updateGroupAliases)
1394  {
1395  // check group aliases ... a la
1396  // ConfigurationGUISupervisor::handleSetGroupAliasInBackboneXML
1397 
1398  TableEditStruct& groupAliasTable = backboneGroupEdit.getTableEditStruct(ConfigurationManager::GROUP_ALIASES_TABLE_NAME, true /*markModified*/);
1399  TableView* tableView = groupAliasTable.tableView_;
1400 
1401  //unsigned int col;
1402  unsigned int row = 0;
1403 
1404  std::vector<std::pair<std::string, ConfigurationTree>> aliasNodePairs = cfgMgr->getNode(ConfigurationManager::GROUP_ALIASES_TABLE_NAME).getChildren();
1405  std::string groupName, groupKey;
1406  for(auto& aliasNodePair : aliasNodePairs)
1407  {
1408  groupName = aliasNodePair.second.getNode("GroupName").getValueAsString();
1409  groupKey = aliasNodePair.second.getNode("GroupKey").getValueAsString();
1410 
1411  __COUT__ << "Group Alias: " << aliasNodePair.first << " => " << groupName << "(" << groupKey << "); row=" << row << __E__;
1412 
1413  if(groupName == originalGroupName_ && TableGroupKey(groupKey) == originalGroupKey_)
1414  {
1415  __COUT__ << "Found alias! Changing group key from (" << originalGroupKey_ << ") to (" << newGroupKey << ")" << __E__;
1416 
1417  groupAliasChange = true;
1418 
1419  tableView->setValueAsString(newGroupKey.toString(), row, tableView->findCol("GroupKey"));
1420  }
1421 
1422  ++row;
1423  }
1424 
1425  if(groupAliasChange)
1426  {
1427  std::stringstream ss;
1428  tableView->print(ss);
1429  __COUT__ << ss.str();
1430  //
1441  //
1442  // backboneGroupEdit.groupMembers_.at(ConfigurationManager::GROUP_ALIASES_TABLE_NAME) =
1443  // cfgMgr->saveModifiedVersion(
1444  // groupAliasTable.tableName_,
1445  // groupAliasTable.originalVersion_,
1446  // true /*make temporary*/,
1447  // groupAliasTable.table_,
1448  // groupAliasTable.temporaryVersion_,
1449  // true /*ignoreDuplicates*/); // make temporary version to save persistent version properly
1450  //
1451  // __COUT__ << "Temporary target version is " <<
1452  // groupAliasTable.table_->getTableName() << "-v"
1453  // << backboneGroupEdit.groupMembers_.at(ConfigurationManager::GROUP_ALIASES_TABLE_NAME) << "-v"
1454  // << groupAliasTable.temporaryVersion_ << __E__;
1455  //
1456  // backboneGroupEdit.groupMembers_.at(ConfigurationManager::GROUP_ALIASES_TABLE_NAME) =
1457  // cfgMgr->saveModifiedVersion(
1458  // groupAliasTable.tableName_,
1459  // groupAliasTable.originalVersion_,
1460  // false /*make temporary*/,
1461  // groupAliasTable.table_,
1462  // groupAliasTable.temporaryVersion_,
1463  // false /*ignoreDuplicates*/,
1464  // true /*lookForEquivalent*/); // save persistent version properly
1465  //
1466  // __COUT__
1467  // << "Original version is "
1468  // << groupAliasTable.table_->getTableName() << "-v"
1469  // << groupAliasTable.originalVersion_ << " and new version is v"
1470  // << backboneGroupEdit.groupMembers_.at(ConfigurationManager::GROUP_ALIASES_TABLE_NAME)
1471  // << __E__;
1472  }
1473  } // end updateGroupAliases handling
1474 
1475  if(groupType_ != ConfigurationManager::GroupType::BACKBONE_TYPE && updateTableAliases)
1476  {
1477  // update all table version aliases
1478  TableView* tableView = backboneGroupEdit.getTableEditStruct(ConfigurationManager::VERSION_ALIASES_TABLE_NAME, true /*markModified*/).tableView_;
1479 
1480  for(auto& groupTable : groupTables_)
1481  {
1482  if(groupTable.second.originalVersion_ == groupMembers_.at(groupTable.second.tableName_))
1483  continue; // skip if no change
1484 
1485  __COUT__ << "Checking alias... original version is " << groupTable.second.tableName_ << "-v" << groupTable.second.originalVersion_
1486  << " and new version is v" << groupMembers_.at(groupTable.second.tableName_) << __E__;
1487 
1488  //unsigned int col;
1489  unsigned int row = 0;
1490 
1491  std::vector<std::pair<std::string, ConfigurationTree>> aliasNodePairs =
1492  cfgMgr->getNode(ConfigurationManager::VERSION_ALIASES_TABLE_NAME).getChildren();
1493  std::string tableName, tableVersion;
1494  for(auto& aliasNodePair : aliasNodePairs)
1495  {
1496  tableName = aliasNodePair.second.getNode("TableName").getValueAsString();
1497  tableVersion = aliasNodePair.second.getNode("Version").getValueAsString();
1498 
1499  __COUT__ << "Table Alias: " << aliasNodePair.first << " => " << tableName << "-v" << tableVersion << "" << __E__;
1500 
1501  if(tableName == groupTable.second.tableName_ && TableVersion(tableVersion) == groupTable.second.originalVersion_)
1502  {
1503  __COUT__ << "Found alias! Changing icon table version alias." << __E__;
1504 
1505  tableAliasChange = true;
1506 
1507  tableView->setValueAsString(groupMembers_.at(groupTable.second.tableName_).toString(), row, tableView->findCol("Version"));
1508  }
1509 
1510  ++row;
1511  }
1512  }
1513 
1514  if(tableAliasChange)
1515  {
1516  std::stringstream ss;
1517  tableView->print(ss);
1518  __COUT__ << ss.str();
1519  }
1520  } // end updateTableAliases handling
1521 
1522  // if backbone modified, save group and activate it
1523 
1524  TableGroupKey localNewBackboneKey;
1525  if(groupAliasChange || tableAliasChange)
1526  {
1527  for(auto& table : backboneGroupEdit.groupMembers_)
1528  {
1529  __COUT__ << table.first << " v" << table.second << __E__;
1530  }
1531  backboneGroupEdit.saveChanges(
1532  backboneGroupEdit.originalGroupName_, localNewBackboneKey, foundEquivalentBackboneKey ? foundEquivalentBackboneKey : nullptr);
1533 
1534  if(newBackboneKey)
1535  *newBackboneKey = localNewBackboneKey;
1536  }
1537 
1538  // acquire all active groups and ignore errors, so that activateTableGroup does not
1539  // erase other active groups
1540  {
1541  __COUT__ << "Restoring active table groups, before activating new groups..." << __E__;
1542 
1543  std::string localAccumulatedWarnings;
1544  cfgMgr->restoreActiveTableGroups(
1545  false /*throwErrors*/, "" /*pathToActiveGroupsFile*/, false /*onlyLoadIfBackboneOrContext*/, &localAccumulatedWarnings);
1546  }
1547 
1548  // activate new groups
1549  if(!localNewBackboneKey.isInvalid())
1550  cfgMgr->activateTableGroup(backboneGroupEdit.originalGroupName_, localNewBackboneKey, accumulatedWarnings ? accumulatedWarnings : nullptr);
1551 
1552  if(activateNewGroup)
1553  cfgMgr->activateTableGroup(groupNameToSave, newGroupKey, accumulatedWarnings ? accumulatedWarnings : nullptr);
1554 
1555  __COUT__ << "Changes saved." << __E__;
1556 } // end GroupEditStruct::saveChanges()
1557 
1558 //==============================================================================
1559 void ConfigurationManagerRW::testXDAQContext()
1560 {
1561  try
1562  {
1563  __COUT__ << "Loading table..." << __E__;
1564  loadTableGroup("FETest", TableGroupKey(2)); // Context_1
1565  ConfigurationTree t = getNode("/FETable/DEFAULT/FrontEndType");
1566 
1567  std::string v;
1568 
1569  __COUT__ << __E__;
1570  t.getValue(v);
1571  __COUT__ << "Value: " << v << __E__;
1572  __COUT__ << "Value index: " << t.getValue<int>() << __E__;
1573 
1574  return;
1575  }
1576  catch(...)
1577  {
1578  __COUT__ << "Failed to load table..." << __E__;
1579  }
1580 }