CMS 3D CMS Logo

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
EventPrincipal.cc
Go to the documentation of this file.
2 
23 
24 #include <algorithm>
25 #include <cassert>
26 #include <limits>
27 #include <memory>
28 
29 namespace edm {
31  std::shared_ptr<ProductRegistry const> reg,
32  std::shared_ptr<BranchIDListHelper const> branchIDListHelper,
33  std::shared_ptr<ThinnedAssociationsHelper const> thinnedAssociationsHelper,
34  ProcessConfiguration const& pc,
35  HistoryAppender* historyAppender,
36  unsigned int streamIndex) :
37  Base(reg, reg->productLookup(InEvent), pc, InEvent, historyAppender),
38  aux_(),
39  luminosityBlockPrincipal_(),
40  provRetrieverPtr_(new ProductProvenanceRetriever(streamIndex)),
41  unscheduledHandler_(),
42  moduleLabelsRunning_(),
43  eventSelectionIDs_(),
44  branchIDListHelper_(branchIDListHelper),
45  thinnedAssociationsHelper_(thinnedAssociationsHelper),
46  branchListIndexes_(),
47  branchListIndexToProcessIndex_(),
48  streamID_(streamIndex) {
50  }
51 
52  void
55  aux_ = EventAuxiliary();
57  provRetrieverPtr_->reset();
58  unscheduledHandler_.reset();
59  moduleLabelsRunning_.clear();
61  }
62 
63  void
65  ProcessHistoryRegistry const& processHistoryRegistry,
66  EventSelectionIDVector&& eventSelectionIDs,
67  BranchListIndexes&& branchListIndexes,
68  ProductProvenanceRetriever& provRetriever,
71  provRetrieverPtr_->deepSwap(provRetriever);
73  if(branchIDListHelper_->hasProducedProducts()) {
74  // Add index into BranchIDListRegistry for products produced this process
75  branchListIndexes_.push_back(branchIDListHelper_->producedBranchListIndex());
76  }
77  fillEventPrincipal(aux,processHistoryRegistry,reader);
78  }
79 
80  void
82  ProcessHistoryRegistry const& processHistoryRegistry,
83  EventSelectionIDVector&& eventSelectionIDs,
84  BranchListIndexes&& branchListIndexes) {
87  if(branchIDListHelper_->hasProducedProducts()) {
88  // Add index into BranchIDListRegistry for products produced this process
89  branchListIndexes_.push_back(branchIDListHelper_->producedBranchListIndex());
90  }
91  fillEventPrincipal(aux,processHistoryRegistry,nullptr);
92  }
93 
94  void
96  ProcessHistoryRegistry const& processHistoryRegistry,
98  if(aux.event() == invalidEventNumber) {
100  << "EventPrincipal::fillEventPrincipal, Invalid event number provided in EventAuxiliary, It is illegal for the event number to be 0\n";
101  }
102 
103  fillPrincipal(aux.processHistoryID(), processHistoryRegistry, reader);
104  aux_ = aux;
106 
107  if(branchListIndexes_.empty() and branchIDListHelper_->hasProducedProducts()) {
108  // Add index into BranchIDListRegistry for products produced this process
109  // if it hasn't already been filled in by the other fillEventPrincipal or by an earlier call to this function
110  branchListIndexes_.push_back(branchIDListHelper_->producedBranchListIndex());
111  }
112 
113  // Fill in helper map for Branch to ProductID mapping
114  ProcessIndex pix = 0;
115  for(auto const& blindex : branchListIndexes_) {
116  branchListIndexToProcessIndex_.insert(std::make_pair(blindex, pix));
117  ++pix;
118  }
119 
120  // Fill in the product ID's in the product holders.
121  for(auto const& prod : *this) {
122  if (prod->singleProduct()) {
123  // If an alias is in the same process as the original then isAlias will be true.
124  // Under that condition, we want the ProductID to be the same as the original.
125  // If not, then we've internally changed the original BranchID to the alias BranchID
126  // in the ProductID lookup so we need the alias BranchID.
127  auto const & bd =prod->branchDescription();
128  prod->setProvenance(productProvenanceRetrieverPtr(),
129  processHistory(),
130  branchIDToProductID(bd.isAlias()?bd.originalBranchID(): bd.branchID()));
131  }
132  }
133  }
134 
135  void
136  EventPrincipal::setLuminosityBlockPrincipal(std::shared_ptr<LuminosityBlockPrincipal> const& lbp) {
138  }
139 
140  void
142  assert(run == luminosityBlockPrincipal_->run());
143  assert(lumi == luminosityBlockPrincipal_->luminosityBlock());
144  EventNumber_t event = aux_.id().event();
145  aux_.id() = EventID(run, lumi, event);
146  }
147 
148  RunPrincipal const&
151  }
152 
153  void
155  BranchDescription const& bd,
156  std::unique_ptr<WrapperBase> edp,
157  ProductProvenance const& productProvenance) {
158 
159  // assert commented out for DaqSource. When DaqSource no longer uses put(), the assert can be restored.
160  //assert(produced());
161  if(edp.get() == nullptr) {
162  throw Exception(errors::InsertFailure, "Null Pointer")
163  << "put: Cannot put because ptr to product is null."
164  << "\n";
165  }
166  productProvenanceRetrieverPtr()->insertIntoSet(productProvenance);
168  assert(phb);
169  checkUniquenessAndType(edp.get(), phb);
170  // ProductHolder assumes ownership
171  phb->putProduct(std::move(edp), productProvenance);
172  }
173 
174  void
176  BranchDescription const& bd,
177  std::unique_ptr<WrapperBase> edp,
178  ProductProvenance const& productProvenance) {
179 
180  assert(!bd.produced());
181  productProvenanceRetrieverPtr()->insertIntoSet(productProvenance);
183  assert(phb);
184  checkUniquenessAndType(edp.get(), phb);
185  // ProductHolder assumes ownership
186  phb->putProduct(std::move(edp), productProvenance);
187  }
188 
189  void
191  if(phb.branchDescription().produced()) return; // nothing to do.
192  if(phb.product()) return; // nothing to do.
193  if(phb.productUnavailable()) return; // nothing to do.
194  if(!reader()) return; // nothing to do.
195 
196  // must attempt to load from persistent store
197  BranchKey const bk = BranchKey(phb.branchDescription());
198  {
199  if(mcc) {
201  }
202  std::shared_ptr<void> guard(nullptr,[this,mcc](const void*){
203  if(mcc) {
205  }
206  });
207 
208  std::unique_ptr<WrapperBase> edp(reader()->getProduct(bk, this));
209 
210  // Now fix up the ProductHolder
211  checkUniquenessAndType(edp.get(), &phb);
212  phb.putProduct(std::move(edp));
213  }
214  }
215 
216  BranchID
218  if(!pid.isValid()) {
219  throw Exception(errors::ProductNotFound, "InvalidID")
220  << "get by product ID: invalid ProductID supplied\n";
221  }
222  return productIDToBranchID(pid, branchIDListHelper_->branchIDLists(), branchListIndexes_);
223  }
224 
225  ProductID
227  if(!bid.isValid()) {
228  throw Exception(errors::NotFound, "InvalidID")
229  << "branchIDToProductID: invalid BranchID supplied\n";
230  }
231  typedef BranchIDListHelper::BranchIDToIndexMap BIDToIndexMap;
232  typedef BIDToIndexMap::const_iterator Iter;
233  typedef std::pair<Iter, Iter> IndexRange;
234 
235  IndexRange range = branchIDListHelper_->branchIDToIndexMap().equal_range(bid);
236  for(Iter it = range.first; it != range.second; ++it) {
237  BranchListIndex blix = it->second.first;
238  std::map<BranchListIndex, ProcessIndex>::const_iterator i = branchListIndexToProcessIndex_.find(blix);
239  if(i != branchListIndexToProcessIndex_.end()) {
240  ProductIndex productIndex = it->second.second;
241  ProcessIndex processIndex = i->second;
242  return ProductID(processIndex+1, productIndex+1);
243  }
244  }
245  // cannot throw, because some products may legitimately not have product ID's (e.g. pile-up).
246  return ProductID();
247  }
248 
249  unsigned int
251  return streamID_.value();
252  }
253 
256  exception<<"get by product ID: The product with given id: "<<pid
257  <<"\ntype: "<<phb->productType()
258  <<"\nproduct instance name: "<<phb->productInstanceName()
259  <<"\nprocess name: "<<phb->processName()
260  <<"\nwas already deleted. This is a configuration error. Please change the configuration of the module which caused this exception to state it reads this data.";
261  throw exception;
262  }
263 
264  BasicHandle
266  BranchID bid = pidToBid(pid);
267  ConstProductHolderPtr const phb = getProductHolder(bid);
268  if(phb == nullptr) {
269  return BasicHandle(makeHandleExceptionFactory([pid]()->std::shared_ptr<cms::Exception> {
270  std::shared_ptr<cms::Exception> whyFailed(std::make_shared<Exception>(errors::ProductNotFound, "InvalidID"));
271  *whyFailed
272  << "get by product ID: no product with given id: " << pid << "\n";
273  return whyFailed;
274  }));
275  }
276 
277  // Was this already deleted?
278  if(phb->productWasDeleted()) {
280  }
281  // Check for case where we tried on demand production and
282  // it failed to produce the object
283  if(phb->onDemand()) {
284  return BasicHandle(makeHandleExceptionFactory([pid]()->std::shared_ptr<cms::Exception> {
285  std::shared_ptr<cms::Exception> whyFailed(std::make_shared<Exception>(errors::ProductNotFound, "InvalidID"));
286  *whyFailed
287  << "get by ProductID: could not get product with id: " << pid << "\n"
288  << "Unscheduled execution not allowed to get via ProductID.\n";
289  return whyFailed;
290  }));
291  }
293  phb->resolveProduct(status,false,nullptr);
294 
295  return BasicHandle(phb->productData());
296  }
297 
298  WrapperBase const*
300  return getByProductID(pid).wrapper();
301  }
302 
303  WrapperBase const*
304  EventPrincipal::getThinnedProduct(ProductID const& pid, unsigned int& key) const {
305 
306  BranchID parent = pidToBid(pid);
307 
308  // Loop over thinned containers which were made by selecting elements from the parent container
309  for(auto associatedBranches = thinnedAssociationsHelper_->parentBegin(parent),
310  iEnd = thinnedAssociationsHelper_->parentEnd(parent);
311  associatedBranches != iEnd; ++associatedBranches) {
312 
313  ThinnedAssociation const* thinnedAssociation =
314  getThinnedAssociation(associatedBranches->association());
315  if(thinnedAssociation == nullptr) continue;
316 
317  if(associatedBranches->parent() != pidToBid(thinnedAssociation->parentCollectionID())) {
318  continue;
319  }
320 
321  unsigned int thinnedIndex = 0;
322  // Does this thinned container have the element referenced by key?
323  // If yes, thinnedIndex is set to point to it in the thinned container
324  if(!thinnedAssociation->hasParentIndex(key, thinnedIndex)) {
325  continue;
326  }
327  // Get the thinned container and return a pointer if we can find it
328  ProductID const& thinnedCollectionPID = thinnedAssociation->thinnedCollectionID();
329  BasicHandle bhThinned = getByProductID(thinnedCollectionPID);
330  if(!bhThinned.isValid()) {
331  // Thinned container is not found, try looking recursively in thinned containers
332  // which were made by selecting elements from this thinned container.
333  WrapperBase const* wrapperBase = getThinnedProduct(thinnedCollectionPID, thinnedIndex);
334  if(wrapperBase != nullptr) {
335  key = thinnedIndex;
336  return wrapperBase;
337  } else {
338  continue;
339  }
340  }
341  key = thinnedIndex;
342  return bhThinned.wrapper();
343  }
344  return nullptr;
345  }
346 
347  void
349  std::vector<WrapperBase const*>& foundContainers,
350  std::vector<unsigned int>& keys) const {
351 
352  BranchID parent = pidToBid(pid);
353 
354  // Loop over thinned containers which were made by selecting elements from the parent container
355  for(auto associatedBranches = thinnedAssociationsHelper_->parentBegin(parent),
356  iEnd = thinnedAssociationsHelper_->parentEnd(parent);
357  associatedBranches != iEnd; ++associatedBranches) {
358 
359  ThinnedAssociation const* thinnedAssociation =
360  getThinnedAssociation(associatedBranches->association());
361  if(thinnedAssociation == nullptr) continue;
362 
363  if(associatedBranches->parent() != pidToBid(thinnedAssociation->parentCollectionID())) {
364  continue;
365  }
366 
367  unsigned nKeys = keys.size();
368  unsigned int doNotLookForThisIndex = std::numeric_limits<unsigned int>::max();
369  std::vector<unsigned int> thinnedIndexes(nKeys, doNotLookForThisIndex);
370  bool hasAny = false;
371  for(unsigned k = 0; k < nKeys; ++k) {
372  // Already found this one
373  if(foundContainers[k] != nullptr) continue;
374  // Already know this one is not in this thinned container
375  if(keys[k] == doNotLookForThisIndex) continue;
376  // Does the thinned container hold the entry of interest?
377  // Modifies thinnedIndexes[k] only if it returns true and
378  // sets it to the index in the thinned collection.
379  if(thinnedAssociation->hasParentIndex(keys[k], thinnedIndexes[k])) {
380  hasAny = true;
381  }
382  }
383  if(!hasAny) {
384  continue;
385  }
386  // Get the thinned container and set the pointers and indexes into
387  // it (if we can find it)
388  ProductID thinnedCollectionPID = thinnedAssociation->thinnedCollectionID();
389  BasicHandle bhThinned = getByProductID(thinnedCollectionPID);
390  if(!bhThinned.isValid()) {
391  // Thinned container is not found, try looking recursively in thinned containers
392  // which were made by selecting elements from this thinned container.
393  getThinnedProducts(thinnedCollectionPID, foundContainers, thinnedIndexes);
394  for(unsigned k = 0; k < nKeys; ++k) {
395  if(foundContainers[k] == nullptr) continue;
396  if(thinnedIndexes[k] == doNotLookForThisIndex) continue;
397  keys[k] = thinnedIndexes[k];
398  }
399  } else {
400  for(unsigned k = 0; k < nKeys; ++k) {
401  if(thinnedIndexes[k] == doNotLookForThisIndex) continue;
402  keys[k] = thinnedIndexes[k];
403  foundContainers[k] = bhThinned.wrapper();
404  }
405  }
406  }
407  }
408 
409  Provenance
411  BranchID bid = pidToBid(pid);
412  return getProvenance(bid, mcc);
413  }
414 
415  void
416  EventPrincipal::setUnscheduledHandler(std::shared_ptr<UnscheduledHandler> iHandler) {
417  unscheduledHandler_ = iHandler;
418  }
419 
420  std::shared_ptr<UnscheduledHandler>
422  return unscheduledHandler_;
423  }
424 
427  return eventSelectionIDs_;
428  }
429 
430  BranchListIndexes const&
432  return branchListIndexes_;
433  }
434 
437 
438  ConstProductHolderPtr const phb = getProductHolder(branchID);
439 
440  if(phb == nullptr) {
442  << "EventPrincipal::getThinnedAssociation, ThinnedAssociation ProductHolder cannot be found\n"
443  << "This should never happen. Contact a Framework developer";
444  }
446  ProductData const* productData = phb->resolveProduct(status,false,nullptr);
447  if (productData == nullptr) {
448  return nullptr;
449  }
450  WrapperBase const* product = productData->wrapper_.get();
451  if(!(typeid(edm::ThinnedAssociation) == product->dynamicTypeInfo())) {
453  << "EventPrincipal::getThinnedProduct, product has wrong type, not a ThinnedAssociation.\n";
454  }
456  return wrapper->product();
457  }
458 
459  bool
461  ModuleCallingContext const* mcc) const {
462 
463  // If it is a module already currently running in unscheduled
464  // mode, then there is a circular dependency related to which
465  // EDProducts modules require and produce. There is no safe way
466  // to recover from this. Here we check for this problem and throw
467  // an exception.
468  std::vector<std::string>::const_iterator i =
469  find_in_all(moduleLabelsRunning_, moduleLabel);
470 
471  if(i != moduleLabelsRunning_.end()) {
473  << "Hit circular dependency while trying to run an unscheduled module.\n"
474  << "The last module on the stack shown above requested data from the\n"
475  << "module with label: '" << moduleLabel << "'.\n"
476  << "This is illegal because this module is already running (it is in the\n"
477  << "stack shown above, it might or might not be asking for data from itself).\n"
478  << "More information related to resolving circular dependences can be found here:\n"
479  << "https://twiki.cern.ch/twiki/bin/view/CMSPublic/SWGuideUnscheduledExecution#Circular_Dependence_Errors.";
480  }
481 
482  UnscheduledSentry sentry(&moduleLabelsRunning_, moduleLabel);
483 
484  if(unscheduledHandler_) {
485  if(mcc == nullptr) {
487  << "EventPrincipal::unscheduledFill, Attempting to run unscheduled production\n"
488  << "with a null pointer to the ModuleCalling Context. This should never happen.\n"
489  << "Contact a Framework developer";
490  }
492  std::shared_ptr<void> guard(nullptr,[this,mcc](const void*){
494  });
495  unscheduledHandler_->tryToFill(moduleLabel, *const_cast<EventPrincipal*>(this), mcc);
496  }
497  return true;
498  }
499 }
RunPrincipal const & runPrincipal() const
std::shared_ptr< ThinnedAssociationsHelper const > thinnedAssociationsHelper_
EventNumber_t event() const
Definition: EventID.h:41
void clearPrincipal()
Definition: Principal.cc:314
int i
Definition: DBlmapReader.cc:9
unsigned short BranchListIndex
WrapperBase * product() const
Definition: ProductHolder.h:81
void setLuminosityBlockPrincipal(std::shared_ptr< LuminosityBlockPrincipal > const &lbp)
EventSelectionIDVector const & eventSelectionIDs() const
ConstProductHolderPtr getProductHolder(BranchID const &oid) const
Definition: Principal.cc:439
StreamContext const * getStreamContext() const
std::shared_ptr< LuminosityBlockPrincipal > luminosityBlockPrincipal_
list parent
Definition: dbtoconf.py:74
std::type_info const & dynamicTypeInfo() const
Definition: WrapperBase.h:37
std::shared_ptr< UnscheduledHandler > unscheduledHandler_
BasicHandle getByProductID(ProductID const &oid) const
EventSelectionIDVector eventSelectionIDs_
BranchID pidToBid(ProductID const &pid) const
tuple lumi
Definition: fjr2json.py:35
BranchListIndexes branchListIndexes_
edm::ThinnedAssociation const * getThinnedAssociation(edm::BranchID const &branchID) const
assert(m_qm.get())
Provenance getProvenance(ProductID const &pid, ModuleCallingContext const *mcc) const
std::map< BranchListIndex, ProcessIndex > branchListIndexToProcessIndex_
unsigned long long EventNumber_t
std::shared_ptr< BranchIDListHelper const > branchIDListHelper_
virtual void readFromSource_(ProductHolderBase const &phb, ModuleCallingContext const *mcc) const override
EventAuxiliary aux_
void putOnRead(BranchDescription const &bd, std::unique_ptr< WrapperBase > edp, ProductProvenance const &productProvenance)
bool isValid() const
Definition: BranchID.h:24
BranchListIndexes const & branchListIndexes() const
unsigned int LuminosityBlockNumber_t
ProcessHistory const & processHistory() const
Definition: Principal.h:137
ProductID branchIDToProductID(BranchID const &bid) const
RunPrincipal const & runPrincipal() const
std::vector< EventSelectionID > EventSelectionIDVector
virtual bool unscheduledFill(std::string const &moduleLabel, ModuleCallingContext const *mcc) const override
virtual WrapperBase const * getThinnedProduct(ProductID const &pid, unsigned int &key) const override
static void throwProductDeletedException(ProductID const &pid, edm::EventPrincipal::ConstProductHolderPtr const phb)
void setUnscheduledHandler(std::shared_ptr< UnscheduledHandler > iHandler)
EventNumber_t const invalidEventNumber
std::vector< BranchListIndex > BranchListIndexes
BranchDescription const & branchDescription() const
Definition: ProductHolder.h:90
BranchID productIDToBranchID(ProductID const &pid, BranchIDLists const &lists, BranchListIndexes const &indexes)
ProcessHistoryID const & processHistoryID() const
Definition: Principal.h:141
std::shared_ptr< ProductProvenanceRetriever > productProvenanceRetrieverPtr() const
LuminosityBlockPrincipal const & luminosityBlockPrincipal() const
std::shared_ptr< HandleExceptionFactory > makeHandleExceptionFactory(T &&iFunctor)
void checkUniquenessAndType(WrapperBase const *prod, ProductHolderBase const *productHolder) const
Definition: Principal.cc:815
WrapperBase const * wrapper() const
Definition: BasicHandle.h:98
std::shared_ptr< WrapperBase > wrapper_
Definition: ProductData.h:46
virtual WrapperBase const * getIt(ProductID const &pid) const override
T const * getProduct(RefCore const &ref)
Definition: RefCoreGet.h:36
BranchID const & branchID() const
ProductID const & thinnedCollectionID() const
How EventSelector::AcceptEvent() decides whether to accept an event for output otherwise it is excluding the probing of A single or multiple positive and the trigger will pass if any such matching triggers are PASS or EXCEPTION[A criterion thatmatches no triggers at all is detected and causes a throw.] A single negative with an expectation of appropriate bit checking in the decision and the trigger will pass if any such matching triggers are FAIL or EXCEPTION A wildcarded negative criterion that matches more than one trigger in the trigger but the state exists so we define the behavior If all triggers are the negative crieriion will lead to accepting the event(this again matches the behavior of"!*"before the partial wildcard feature was incorporated).The per-event"cost"of each negative criterion with multiple relevant triggers is about the same as!*was in the past
std::multimap< BranchID, IndexPair > BranchIDToIndexMap
unsigned short ProcessIndex
Definition: ProductID.h:25
DelayedReader * reader() const
Definition: Principal.h:171
unsigned int value() const
Definition: StreamID.h:46
ForwardSequence::const_iterator find_in_all(ForwardSequence const &s, Datum const &d)
wrappers for std::find
Definition: Algorithms.h:32
virtual unsigned int transitionIndex_() const override
void put(BranchDescription const &bd, std::unique_ptr< WrapperBase > edp, ProductProvenance const &productProvenance)
T const * product() const
Definition: Wrapper.h:38
std::shared_ptr< UnscheduledHandler > unscheduledHandler() const
tuple pid
Definition: sysUtil.py:22
string const
Definition: compareJSON.py:14
void setProcessHistoryID(ProcessHistoryID const &phid)
bool isValid() const
Definition: BasicHandle.h:90
ProcessHistoryID const & processHistoryID() const
EventID const & id() const
void fillEventPrincipal(EventAuxiliary const &aux, ProcessHistoryRegistry const &processHistoryRegistry, DelayedReader *reader=0)
std::shared_ptr< ProductProvenanceRetriever > provRetrieverPtr_
Base::ConstProductHolderPtr ConstProductHolderPtr
unsigned short ProductIndex
Definition: ProductID.h:26
EventPrincipal(std::shared_ptr< ProductRegistry const > reg, std::shared_ptr< BranchIDListHelper const > branchIDListHelper, std::shared_ptr< ThinnedAssociationsHelper const > thinnedAssociationsHelper, ProcessConfiguration const &pc, HistoryAppender *historyAppender, unsigned int streamIndex=0)
unsigned int RunNumber_t
ProductID const & parentCollectionID() const
signalslot::Signal< void(StreamContext const &, ModuleCallingContext const &)> preModuleDelayedGetSignal_
bool isValid() const
Definition: ProductID.h:35
tuple status
Definition: ntuplemaker.py:245
bool hasParentIndex(unsigned int parentIndex, unsigned int &thinnedIndex) const
void setRunAndLumiNumber(RunNumber_t run, LuminosityBlockNumber_t lumi)
void fillPrincipal(ProcessHistoryID const &hist, ProcessHistoryRegistry const &phr, DelayedReader *reader)
Definition: Principal.cc:337
EventAuxiliary const & aux() const
virtual void getThinnedProducts(ProductID const &pid, std::vector< WrapperBase const * > &foundContainers, std::vector< unsigned int > &keys) const override
void putProduct(std::unique_ptr< WrapperBase > edp, ProductProvenance const &productProvenance)
EventNumber_t event() const
void emit(Args &&...args) const
Definition: Signal.h:47
ProductHolderBase * getExistingProduct(BranchID const &branchID)
Definition: Principal.cc:393
std::vector< std::string > moduleLabelsRunning_
static HepMC::HEPEVT_Wrapper wrapper
signalslot::Signal< void(StreamContext const &, ModuleCallingContext const &)> postModuleDelayedGetSignal_
bool productUnavailable() const
Definition: ProductHolder.h:69