fixed bug in instance discovery DCI deletion
[public/netxms.git] / src / server / core / template.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2016 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: template.cpp
20 **
21 **/
22
23 #include "nxcore.h"
24
25 /**
26 * Redefined status calculation for template group
27 */
28 void TemplateGroup::calculateCompoundStatus(BOOL bForcedRecalc)
29 {
30 m_status = STATUS_NORMAL;
31 }
32
33 /**
34 * Called by client session handler to check if threshold summary should be shown for this object.
35 */
36 bool TemplateGroup::showThresholdSummary()
37 {
38 return false;
39 }
40
41 /**
42 * Template object constructor
43 */
44 Template::Template() : NetObj()
45 {
46 m_dcObjects = new ObjectArray<DCObject>(8, 16, true);
47 m_dciLockStatus = -1;
48 m_flags = 0;
49 m_dwVersion = 0x00010000; // Initial version is 1.0
50 m_applyFilter = NULL;
51 m_applyFilterSource = NULL;
52 m_status = STATUS_NORMAL;
53 m_dciAccessLock = RWLockCreate();
54 m_dciListModified = false;
55 }
56
57 /**
58 * Constructor for new template object
59 */
60 Template::Template(const TCHAR *pszName) : NetObj()
61 {
62 nx_strncpy(m_name, pszName, MAX_OBJECT_NAME);
63 m_dcObjects = new ObjectArray<DCObject>(8, 16, true);
64 m_dciLockStatus = -1;
65 m_flags = 0;
66 m_dwVersion = 0x00010000; // Initial version is 1.0
67 m_applyFilter = NULL;
68 m_applyFilterSource = NULL;
69 m_status = STATUS_NORMAL;
70 m_isHidden = true;
71 m_dciAccessLock = RWLockCreate();
72 m_dciListModified = false;
73 }
74
75 /**
76 * Create template object from import file
77 */
78 Template::Template(ConfigEntry *config) : NetObj()
79 {
80 m_isHidden = true;
81 m_dciLockStatus = -1;
82 m_status = STATUS_NORMAL;
83 m_dciAccessLock = RWLockCreate();
84 m_dciListModified = false;
85
86 // GUID
87 uuid guid = config->getSubEntryValueAsUUID(_T("guid"));
88 if (!guid.isNull())
89 m_guid = guid;
90
91 // Name and version
92 nx_strncpy(m_name, config->getSubEntryValue(_T("name"), 0, _T("Unnamed Template")), MAX_OBJECT_NAME);
93 m_dwVersion = config->getSubEntryValueAsUInt(_T("version"), 0, 0x00010000);
94 m_flags = config->getSubEntryValueAsUInt(_T("flags"), 0, 0);
95
96 // Auto-apply filter
97 m_applyFilter = NULL;
98 m_applyFilterSource = NULL;
99 if (m_flags & TF_AUTO_APPLY)
100 setAutoApplyFilter(config->getSubEntryValue(_T("filter")));
101
102 // Data collection
103 m_dcObjects = new ObjectArray<DCObject>(8, 16, true);
104 ConfigEntry *dcRoot = config->findEntry(_T("dataCollection"));
105 if (dcRoot != NULL)
106 {
107 ObjectArray<ConfigEntry> *dcis = dcRoot->getSubEntries(_T("dci#*"));
108 for(int i = 0; i < dcis->size(); i++)
109 {
110 m_dcObjects->add(new DCItem(dcis->get(i), this));
111 }
112 delete dcis;
113
114 ObjectArray<ConfigEntry> *dctables = dcRoot->getSubEntries(_T("dctable#*"));
115 for(int i = 0; i < dctables->size(); i++)
116 {
117 m_dcObjects->add(new DCTable(dctables->get(i), this));
118 }
119 delete dctables;
120 }
121 }
122
123 /**
124 * Destructor
125 */
126 Template::~Template()
127 {
128 delete m_dcObjects;
129 delete m_applyFilter;
130 safe_free(m_applyFilterSource);
131 RWLockDestroy(m_dciAccessLock);
132 }
133
134 /**
135 * Destroy all related data collection items.
136 */
137 void Template::destroyItems()
138 {
139 m_dcObjects->clear();
140 }
141
142 /**
143 * Set auto apply filter.
144 *
145 * @param filter new filter script code or NULL to clear filter
146 */
147 void Template::setAutoApplyFilter(const TCHAR *filter)
148 {
149 lockProperties();
150 safe_free(m_applyFilterSource);
151 delete m_applyFilter;
152 if (filter != NULL)
153 {
154 TCHAR error[256];
155
156 m_applyFilterSource = _tcsdup(filter);
157 m_applyFilter = NXSLCompile(m_applyFilterSource, error, 256, NULL);
158 if (m_applyFilter == NULL)
159 {
160 TCHAR buffer[1024];
161 _sntprintf(buffer, 1024, _T("Template::%s::%d"), m_name, m_id);
162 PostEvent(EVENT_SCRIPT_ERROR, g_dwMgmtNode, "ssd", buffer, error, m_id);
163 nxlog_write(MSG_TEMPLATE_SCRIPT_COMPILATION_ERROR, NXLOG_WARNING, "dss", m_id, m_name, error);
164 }
165 }
166 else
167 {
168 m_applyFilterSource = NULL;
169 m_applyFilter = NULL;
170 }
171 setModified();
172 unlockProperties();
173 }
174
175 /**
176 * Create template object from database data
177 *
178 * @param dwId object ID
179 */
180 bool Template::loadFromDatabase(DB_HANDLE hdb, UINT32 dwId)
181 {
182 TCHAR szQuery[256];
183 UINT32 i, dwNumNodes, dwNodeId;
184 NetObj *pObject;
185
186 m_id = dwId;
187
188 if (!loadCommonProperties(hdb))
189 return false;
190
191 _sntprintf(szQuery, sizeof(szQuery) / sizeof(TCHAR), _T("SELECT version,flags,apply_filter FROM templates WHERE id=%d"), dwId);
192 DB_RESULT hResult = DBSelect(hdb, szQuery);
193 if (hResult == NULL)
194 return false;
195
196 if (DBGetNumRows(hResult) == 0)
197 {
198 // No object with given ID in database
199 DBFreeResult(hResult);
200 return false;
201 }
202
203 bool success = true;
204
205 m_dwVersion = DBGetFieldULong(hResult, 0, 0);
206 m_flags = DBGetFieldULong(hResult, 0, 1);
207 m_applyFilterSource = DBGetField(hResult, 0, 2, NULL, 0);
208 if (m_applyFilterSource != NULL)
209 {
210 TCHAR error[256];
211 m_applyFilter = NXSLCompile(m_applyFilterSource, error, 256, NULL);
212 if (m_applyFilter == NULL)
213 {
214 TCHAR buffer[1024];
215 _sntprintf(buffer, 1024, _T("Template::%s::%d"), m_name, m_id);
216 PostEvent(EVENT_SCRIPT_ERROR, g_dwMgmtNode, "ssd", buffer, error, m_id);
217 nxlog_write(MSG_TEMPLATE_SCRIPT_COMPILATION_ERROR, EVENTLOG_WARNING_TYPE, "dss", m_id, m_name, error);
218 }
219 }
220 DBFreeResult(hResult);
221
222 // Load DCI and access list
223 loadACLFromDB(hdb);
224 loadItemsFromDB(hdb);
225 for(i = 0; i < (UINT32)m_dcObjects->size(); i++)
226 if (!m_dcObjects->get(i)->loadThresholdsFromDB(hdb))
227 success = false;
228
229 // Load related nodes list
230 if (!m_isDeleted)
231 {
232 _sntprintf(szQuery, sizeof(szQuery) / sizeof(TCHAR), _T("SELECT node_id FROM dct_node_map WHERE template_id=%d"), m_id);
233 hResult = DBSelect(hdb, szQuery);
234 if (hResult != NULL)
235 {
236 dwNumNodes = DBGetNumRows(hResult);
237 for(i = 0; i < dwNumNodes; i++)
238 {
239 dwNodeId = DBGetFieldULong(hResult, i, 0);
240 pObject = FindObjectById(dwNodeId);
241 if (pObject != NULL)
242 {
243 if ((pObject->getObjectClass() == OBJECT_NODE) || (pObject->getObjectClass() == OBJECT_CLUSTER) || (pObject->getObjectClass() == OBJECT_MOBILEDEVICE))
244 {
245 addChild(pObject);
246 pObject->addParent(this);
247 }
248 else
249 {
250 nxlog_write(MSG_DCT_MAP_NOT_NODE, EVENTLOG_ERROR_TYPE, "dd", m_id, dwNodeId);
251 }
252 }
253 else
254 {
255 nxlog_write(MSG_INVALID_DCT_MAP, EVENTLOG_ERROR_TYPE, "dd", m_id, dwNodeId);
256 }
257 }
258 DBFreeResult(hResult);
259 }
260 }
261
262 m_status = STATUS_NORMAL;
263
264 return success;
265 }
266
267 /**
268 * Save object to database
269 */
270 BOOL Template::saveToDatabase(DB_HANDLE hdb)
271 {
272 lockProperties();
273
274 if (!saveCommonProperties(hdb))
275 {
276 unlockProperties();
277 return FALSE;
278 }
279
280 DB_STATEMENT hStmt;
281 if (IsDatabaseRecordExist(hdb, _T("templates"), _T("id"), m_id))
282 {
283 hStmt = DBPrepare(hdb, _T("UPDATE templates SET version=?,flags=?,apply_filter=? WHERE id=?"));
284 }
285 else
286 {
287 hStmt = DBPrepare(hdb, _T("INSERT INTO templates (version,flags,apply_filter,id) VALUES (?,?,?,?)"));
288 }
289 if (hStmt == NULL)
290 {
291 unlockProperties();
292 return FALSE;
293 }
294
295 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_dwVersion);
296 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, m_flags);
297 DBBind(hStmt, 3, DB_SQLTYPE_TEXT, m_applyFilterSource, DB_BIND_STATIC);
298 DBBind(hStmt, 4, DB_SQLTYPE_INTEGER, m_id);
299 BOOL success = DBExecute(hStmt);
300 DBFreeStatement(hStmt);
301
302 if (success)
303 {
304 TCHAR query[256];
305
306 // Update members list
307 _sntprintf(query, 256, _T("DELETE FROM dct_node_map WHERE template_id=%d"), m_id);
308 DBQuery(hdb, query);
309 lockChildList(false);
310 for(int i = 0; i < m_childList->size(); i++)
311 {
312 _sntprintf(query, 256, _T("INSERT INTO dct_node_map (template_id,node_id) VALUES (%d,%d)"), m_id, m_childList->get(i)->getId());
313 DBQuery(hdb, query);
314 }
315 unlockChildList();
316
317 // Save access list
318 saveACLToDB(hdb);
319 }
320
321 unlockProperties();
322
323 // Save data collection items
324 lockDciAccess(false);
325 for(int i = 0; i < m_dcObjects->size(); i++)
326 m_dcObjects->get(i)->saveToDatabase(hdb);
327 unlockDciAccess();
328
329 // Clear modifications flag
330 lockProperties();
331 m_isModified = false;
332 unlockProperties();
333
334 return success;
335 }
336
337 /**
338 * Delete object from database
339 */
340 bool Template::deleteFromDatabase(DB_HANDLE hdb)
341 {
342 bool success = NetObj::deleteFromDatabase(hdb);
343 if (success)
344 {
345 if (getObjectClass() == OBJECT_TEMPLATE)
346 {
347 success = executeQueryOnObject(hdb, _T("DELETE FROM templates WHERE id=?"));
348 if (success)
349 success = executeQueryOnObject(hdb, _T("DELETE FROM dct_node_map WHERE template_id=?"));
350 }
351 else
352 {
353 success = executeQueryOnObject(hdb, _T("DELETE FROM dct_node_map WHERE node_id=?"));
354 }
355 if (success)
356 success = executeQueryOnObject(hdb, _T("DELETE FROM items WHERE node_id=?"));
357 if (success)
358 success = executeQueryOnObject(hdb, _T("UPDATE items SET template_id=0 WHERE template_id=?"));
359 }
360 return success;
361 }
362
363 /**
364 * Load data collection items from database
365 */
366 void Template::loadItemsFromDB(DB_HANDLE hdb)
367 {
368 DB_STATEMENT hStmt = DBPrepare(hdb,
369 _T("SELECT item_id,name,source,datatype,polling_interval,retention_time,")
370 _T("status,delta_calculation,transformation,template_id,description,")
371 _T("instance,template_item_id,flags,resource_id,")
372 _T("proxy_node,base_units,unit_multiplier,custom_units_name,")
373 _T("perftab_settings,system_tag,snmp_port,snmp_raw_value_type,")
374 _T("instd_method,instd_data,instd_filter,samples,comments,guid FROM items WHERE node_id=?"));
375 if (hStmt != NULL)
376 {
377 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
378 DB_RESULT hResult = DBSelectPrepared(hStmt);
379 if (hResult != NULL)
380 {
381 int count = DBGetNumRows(hResult);
382 for(int i = 0; i < count; i++)
383 m_dcObjects->add(new DCItem(hdb, hResult, i, this));
384 DBFreeResult(hResult);
385 }
386 DBFreeStatement(hStmt);
387 }
388
389 hStmt = DBPrepare(hdb,
390 _T("SELECT item_id,template_id,template_item_id,name,")
391 _T("description,flags,source,snmp_port,polling_interval,retention_time,")
392 _T("status,system_tag,resource_id,proxy_node,perftab_settings,")
393 _T("transformation_script,comments,guid FROM dc_tables WHERE node_id=?"));
394 if (hStmt != NULL)
395 {
396 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
397 DB_RESULT hResult = DBSelectPrepared(hStmt);
398 if (hResult != NULL)
399 {
400 int count = DBGetNumRows(hResult);
401 for(int i = 0; i < count; i++)
402 m_dcObjects->add(new DCTable(hdb, hResult, i, this));
403 DBFreeResult(hResult);
404 }
405 DBFreeStatement(hStmt);
406 }
407 }
408
409 /**
410 * Add data collection object to node
411 */
412 bool Template::addDCObject(DCObject *object, bool alreadyLocked)
413 {
414 int i;
415 bool success = false;
416
417 if (!alreadyLocked)
418 lockDciAccess(true); // write lock
419
420 // Check if that object exists
421 for(i = 0; i < m_dcObjects->size(); i++)
422 if (m_dcObjects->get(i)->getId() == object->getId())
423 break; // Object with specified id already exist
424
425 if (i == m_dcObjects->size()) // Add new item
426 {
427 m_dcObjects->add(object);
428 object->setLastPollTime(0); // Cause item to be polled immediatelly
429 if (object->getStatus() != ITEM_STATUS_DISABLED)
430 object->setStatus(ITEM_STATUS_ACTIVE, false);
431 object->setBusyFlag(FALSE);
432 m_isModified = true;
433 success = true;
434 }
435
436 if (!alreadyLocked)
437 unlockDciAccess();
438
439 if (success)
440 {
441 lockProperties();
442 setModified();
443 unlockProperties();
444 }
445 return success;
446 }
447
448 /**
449 * Delete data collection object from node
450 */
451 bool Template::deleteDCObject(UINT32 dcObjectId, bool needLock)
452 {
453 bool success = false;
454
455 if (needLock)
456 lockDciAccess(true); // write lock
457
458 // Check if that item exists
459 for(int i = 0; i < m_dcObjects->size(); i++)
460 {
461 DCObject *object = m_dcObjects->get(i);
462 if (object->getId() == dcObjectId)
463 {
464 // Check if it is instance DCI
465 if ((object->getType() == DCO_TYPE_ITEM) && (((DCItem *)object)->getInstanceDiscoveryMethod() != IDM_NONE))
466 {
467 deleteChildDCIs(dcObjectId);
468
469 // Index may be incorrect at this point
470 if (m_dcObjects->get(i) != object)
471 i = m_dcObjects->indexOf(object);
472 }
473 // Destroy item
474 DbgPrintf(7, _T("Template::DeleteDCObject: deleting DCObject %d from object %d"), (int)dcObjectId, (int)m_id);
475 destroyItem(object, i);
476 m_isModified = true;
477 success = true;
478 DbgPrintf(7, _T("Template::DeleteDCObject: DCO deleted from object %d"), (int)m_id);
479 break;
480 }
481 }
482
483 if (needLock)
484 unlockDciAccess();
485 return success;
486 }
487
488 /**
489 * Delets child DCI objects of instance discovery DCI.
490 * It is assumed that list is already locked
491 */
492 void Template::deleteChildDCIs(UINT32 dcObjectId)
493 {
494 for(int i = 0; i < m_dcObjects->size(); i++)
495 {
496 DCObject *subObject = m_dcObjects->get(i);
497 if (subObject->getTemplateItemId() == dcObjectId)
498 {
499 destroyItem(subObject, i);
500 i--;
501 DbgPrintf(7, _T("Template::DeleteDCObject: deleting DCObject %d created by DCObject %d instance discovery from object %d"), (int)subObject->getId(), (int)dcObjectId, (int)m_id);
502 }
503 }
504 }
505
506 /**
507 * Delete DCI object.
508 * Deletes or schedules deletion from DB and removes it from index
509 * It is assumed that list is already locked
510 */
511 void Template::destroyItem(DCObject *object, int index)
512 {
513 if (object->prepareForDeletion())
514 {
515 // Physically delete DCI only if it is not busy
516 // Busy DCIs will be deleted by data collector
517 object->deleteFromDatabase();
518 m_dcObjects->remove(index);
519 }
520 else
521 {
522 m_dcObjects->unlink(index);
523 DbgPrintf(7, _T("Template::DeleteItem: destruction of DCO %d delayed"), (int)object->getId());
524 }
525 }
526
527 /**
528 * Modify data collection object from NXCP message
529 */
530 bool Template::updateDCObject(UINT32 dwItemId, NXCPMessage *pMsg, UINT32 *pdwNumMaps, UINT32 **ppdwMapIndex, UINT32 **ppdwMapId)
531 {
532 bool success = false;
533
534 lockDciAccess(false);
535
536 // Check if that item exists
537 for(int i = 0; i < m_dcObjects->size(); i++)
538 {
539 DCObject *object = m_dcObjects->get(i);
540 if (object->getId() == dwItemId)
541 {
542 if (object->getType() == DCO_TYPE_ITEM)
543 {
544 ((DCItem *)object)->updateFromMessage(pMsg, pdwNumMaps, ppdwMapIndex, ppdwMapId);
545 if (((DCItem *)object)->getInstanceDiscoveryMethod() != IDM_NONE)
546 {
547 updateInstanceDiscoveryItems((DCItem *)object);
548 }
549 }
550 else
551 {
552 object->updateFromMessage(pMsg);
553 }
554 success = true;
555 m_isModified = true;
556 break;
557 }
558 }
559
560 unlockDciAccess();
561 return success;
562 }
563
564 /**
565 * Update DCIs created by instance dicovery.
566 * This method expects DCI access already locked.
567 *
568 * @param dci instance discovery template DCI
569 */
570 void Template::updateInstanceDiscoveryItems(DCItem *dci)
571 {
572 for(int i = 0; i < m_dcObjects->size(); i++)
573 {
574 DCObject *object = m_dcObjects->get(i);
575 if ((object->getType() == DCO_TYPE_ITEM) && (object->getTemplateId() == m_id) && (object->getTemplateItemId() == dci->getId()))
576 {
577 object->updateFromTemplate(dci);
578 }
579 }
580 }
581
582 /**
583 * Set status for group of DCIs
584 */
585 bool Template::setItemStatus(UINT32 dwNumItems, UINT32 *pdwItemList, int iStatus)
586 {
587 bool success = true;
588
589 lockDciAccess(false);
590 for(UINT32 i = 0; i < dwNumItems; i++)
591 {
592 int j;
593 for(j = 0; j < m_dcObjects->size(); j++)
594 {
595 if (m_dcObjects->get(j)->getId() == pdwItemList[i])
596 {
597 m_dcObjects->get(j)->setStatus(iStatus, true);
598 break;
599 }
600 }
601 if (j == m_dcObjects->size())
602 success = false; // Invalid DCI ID provided
603 }
604 unlockDciAccess();
605 return success;
606 }
607
608 /**
609 * Lock data collection items list
610 */
611 bool Template::lockDCIList(int sessionId, const TCHAR *pszNewOwner, TCHAR *pszCurrOwner)
612 {
613 bool success;
614
615 lockProperties();
616 if (m_dciLockStatus == -1)
617 {
618 m_dciLockStatus = sessionId;
619 m_dciListModified = false;
620 nx_strncpy(m_szCurrDCIOwner, pszNewOwner, MAX_SESSION_NAME);
621 success = true;
622 }
623 else
624 {
625 if (pszCurrOwner != NULL)
626 _tcscpy(pszCurrOwner, m_szCurrDCIOwner);
627 success = false;
628 }
629 unlockProperties();
630 return success;
631 }
632
633 /**
634 * Unlock data collection items list
635 */
636 bool Template::unlockDCIList(int sessionId)
637 {
638 bool success = false;
639 bool callChangeHook = false;
640
641 lockProperties();
642 if (m_dciLockStatus == sessionId)
643 {
644 m_dciLockStatus = -1;
645 if (m_dciListModified)
646 {
647 if (getObjectClass() == OBJECT_TEMPLATE)
648 m_dwVersion++;
649 setModified();
650 callChangeHook = true;
651 }
652 m_dciListModified = false;
653 success = true;
654 }
655 unlockProperties();
656
657 if (callChangeHook)
658 onDataCollectionChange();
659
660 return success;
661 }
662
663 /**
664 * Send DCI list to client
665 */
666 void Template::sendItemsToClient(ClientSession *pSession, UINT32 dwRqId)
667 {
668 NXCPMessage msg;
669
670 // Prepare message
671 msg.setId(dwRqId);
672 msg.setCode(CMD_NODE_DCI);
673
674 lockDciAccess(false);
675
676 // Walk through items list
677 for(int i = 0; i < m_dcObjects->size(); i++)
678 {
679 m_dcObjects->get(i)->createMessage(&msg);
680 pSession->sendMessage(&msg);
681 msg.deleteAllFields();
682 }
683
684 unlockDciAccess();
685
686 // Send end-of-list indicator
687 msg.setEndOfSequence();
688 pSession->sendMessage(&msg);
689 }
690
691 /**
692 * Get DCI's data type
693 */
694 int Template::getItemType(UINT32 dwItemId)
695 {
696 int iType = -1;
697
698 lockDciAccess(false);
699 // Check if that item exists
700 for(int i = 0; i < m_dcObjects->size(); i++)
701 {
702 DCObject *object = m_dcObjects->get(i);
703 if (object->getId() == dwItemId)
704 {
705 if (object->getType() == DCO_TYPE_ITEM)
706 {
707 iType = ((DCItem *)object)->getDataType();
708 }
709 break;
710 }
711 }
712
713 unlockDciAccess();
714 return iType;
715 }
716
717 /**
718 * Get item by it's id
719 */
720 DCObject *Template::getDCObjectById(UINT32 itemId, bool lock)
721 {
722 DCObject *object = NULL;
723
724 if (lock)
725 lockDciAccess(false);
726
727 for(int i = 0; i < m_dcObjects->size(); i++)
728 {
729 DCObject *curr = m_dcObjects->get(i);
730 if (curr->getId() == itemId)
731 {
732 object = curr;
733 break;
734 }
735 }
736
737 if (lock)
738 unlockDciAccess();
739 return object;
740 }
741
742 /**
743 * Get item by template item id
744 */
745 DCObject *Template::getDCObjectByTemplateId(UINT32 tmplItemId)
746 {
747 DCObject *object = NULL;
748
749 lockDciAccess(false);
750 // Check if that item exists
751 for(int i = 0; i < m_dcObjects->size(); i++)
752 {
753 DCObject *curr = m_dcObjects->get(i);
754 if (curr->getTemplateItemId() == tmplItemId)
755 {
756 object = curr;
757 break;
758 }
759 }
760
761 unlockDciAccess();
762 return object;
763 }
764
765 /**
766 * Get item by it's name (case-insensetive)
767 */
768 DCObject *Template::getDCObjectByName(const TCHAR *name)
769 {
770 DCObject *object = NULL;
771
772 lockDciAccess(false);
773 // Check if that item exists
774 for(int i = 0; i < m_dcObjects->size(); i++)
775 {
776 DCObject *curr = m_dcObjects->get(i);
777 if (!_tcsicmp(curr->getName(), name))
778 {
779 object = curr;
780 break;
781 }
782 }
783 unlockDciAccess();
784 return object;
785 }
786
787 /**
788 * Get item by it's description (case-insensetive)
789 */
790 DCObject *Template::getDCObjectByDescription(const TCHAR *description)
791 {
792 DCObject *object = NULL;
793
794 lockDciAccess(false);
795 // Check if that item exists
796 for(int i = 0; i < m_dcObjects->size(); i++)
797 {
798 DCObject *curr = m_dcObjects->get(i);
799 if (!_tcsicmp(curr->getDescription(), description))
800 {
801 object = curr;
802 break;
803 }
804 }
805 unlockDciAccess();
806 return object;
807 }
808
809 /**
810 * Get item by GUID
811 */
812 DCObject *Template::getDCObjectByGUID(const uuid& guid, bool lock)
813 {
814 DCObject *object = NULL;
815
816 if (lock)
817 lockDciAccess(false);
818
819 // Check if that item exists
820 for(int i = 0; i < m_dcObjects->size(); i++)
821 {
822 DCObject *curr = m_dcObjects->get(i);
823 if (guid.equals(curr->getGuid()))
824 {
825 object = curr;
826 break;
827 }
828 }
829
830 if (lock)
831 unlockDciAccess();
832 return object;
833 }
834
835 /**
836 * Get item by it's index
837 */
838 DCObject *Template::getDCObjectByIndex(int index)
839 {
840 lockDciAccess(false);
841 DCObject *object = m_dcObjects->get(index);
842 unlockDciAccess();
843 return object;
844 }
845
846 /**
847 * Get all DC objects with matching name and description
848 */
849 NXSL_Value *Template::getAllDCObjectsForNXSL(const TCHAR *name, const TCHAR *description)
850 {
851 NXSL_Array *list = new NXSL_Array();
852 lockDciAccess(false);
853 for(int i = 0; i < m_dcObjects->size(); i++)
854 {
855 DCObject *curr = m_dcObjects->get(i);
856 if (((name == NULL) || MatchString(name, curr->getName(), false)) &&
857 ((description == NULL) || MatchString(description, curr->getDescription(), false)))
858 {
859 list->set(list->size(), curr->createNXSLObject());
860 }
861 }
862 unlockDciAccess();
863 return new NXSL_Value(list);
864 }
865
866 /**
867 * Redefined status calculation for template
868 */
869 void Template::calculateCompoundStatus(BOOL bForcedRecalc)
870 {
871 m_status = STATUS_NORMAL;
872 }
873
874 /**
875 * Create NXCP message with object's data
876 */
877 void Template::fillMessageInternal(NXCPMessage *pMsg)
878 {
879 NetObj::fillMessageInternal(pMsg);
880 pMsg->setField(VID_TEMPLATE_VERSION, m_dwVersion);
881 pMsg->setField(VID_FLAGS, m_flags);
882 pMsg->setField(VID_AUTOBIND_FILTER, CHECK_NULL_EX(m_applyFilterSource));
883 }
884
885 /**
886 * Modify object from NXCP message
887 */
888 UINT32 Template::modifyFromMessageInternal(NXCPMessage *pRequest)
889 {
890 // Change template version
891 if (pRequest->isFieldExist(VID_TEMPLATE_VERSION))
892 m_dwVersion = pRequest->getFieldAsUInt32(VID_TEMPLATE_VERSION);
893
894 // Change flags
895 if (pRequest->isFieldExist(VID_FLAGS))
896 m_flags = pRequest->getFieldAsUInt32(VID_FLAGS);
897
898 // Change apply filter
899 if (pRequest->isFieldExist(VID_AUTOBIND_FILTER))
900 {
901 free(m_applyFilterSource);
902 delete m_applyFilter;
903 m_applyFilterSource = pRequest->getFieldAsString(VID_AUTOBIND_FILTER);
904 if (m_applyFilterSource != NULL)
905 {
906 TCHAR error[256];
907
908 m_applyFilter = NXSLCompile(m_applyFilterSource, error, 256, NULL);
909 if (m_applyFilter == NULL)
910 {
911 TCHAR buffer[1024];
912 _sntprintf(buffer, 1024, _T("Template::%s::%d"), m_name, m_id);
913 PostEvent(EVENT_SCRIPT_ERROR, g_dwMgmtNode, "ssd", buffer, error, m_id);
914 nxlog_write(MSG_TEMPLATE_SCRIPT_COMPILATION_ERROR, EVENTLOG_WARNING_TYPE, "dss", m_id, m_name, error);
915 }
916 }
917 else
918 {
919 m_applyFilter = NULL;
920 }
921 }
922
923 return NetObj::modifyFromMessageInternal(pRequest);
924 }
925
926 /**
927 * Apply template to data collection target
928 */
929 BOOL Template::applyToTarget(DataCollectionTarget *target)
930 {
931 UINT32 *pdwItemList;
932 BOOL bErrors = FALSE;
933
934 // Link node to template
935 if (!isChild(target->getId()))
936 {
937 addChild(target);
938 target->addParent(this);
939 }
940
941 pdwItemList = (UINT32 *)malloc(sizeof(UINT32) * m_dcObjects->size());
942 DbgPrintf(2, _T("Apply %d items from template \"%s\" to target \"%s\""),
943 m_dcObjects->size(), m_name, target->getName());
944
945 // Copy items
946 for(int i = 0; i < m_dcObjects->size(); i++)
947 {
948 DCObject *object = m_dcObjects->get(i);
949 pdwItemList[i] = object->getId();
950 if (!target->applyTemplateItem(m_id, object))
951 {
952 bErrors = TRUE;
953 }
954 }
955
956 // Clean items deleted from template
957 target->cleanDeletedTemplateItems(m_id, m_dcObjects->size(), pdwItemList);
958
959 // Cleanup
960 free(pdwItemList);
961
962 target->onDataCollectionChange();
963
964 // Queue update if target is a cluster
965 if (target->getObjectClass() == OBJECT_CLUSTER)
966 {
967 target->queueUpdate();
968 }
969
970 return bErrors;
971 }
972
973 /**
974 * Queue template update
975 */
976 void Template::queueUpdate()
977 {
978 lockChildList(false);
979 for(int i = 0; i < m_childList->size(); i++)
980 {
981 NetObj *object = m_childList->get(i);
982 if (object->isDataCollectionTarget())
983 {
984 incRefCount();
985 TEMPLATE_UPDATE_INFO *pInfo = (TEMPLATE_UPDATE_INFO *)malloc(sizeof(TEMPLATE_UPDATE_INFO));
986 pInfo->updateType = APPLY_TEMPLATE;
987 pInfo->pTemplate = this;
988 pInfo->targetId = object->getId();
989 pInfo->removeDCI = false;
990 g_pTemplateUpdateQueue->put(pInfo);
991 }
992 }
993 unlockChildList();
994 }
995
996 /**
997 * Queue template remove from node
998 */
999 void Template::queueRemoveFromTarget(UINT32 targetId, bool removeDCI)
1000 {
1001 lockProperties();
1002 incRefCount();
1003 TEMPLATE_UPDATE_INFO *pInfo = (TEMPLATE_UPDATE_INFO *)malloc(sizeof(TEMPLATE_UPDATE_INFO));
1004 pInfo->updateType = REMOVE_TEMPLATE;
1005 pInfo->pTemplate = this;
1006 pInfo->targetId = targetId;
1007 pInfo->removeDCI = removeDCI;
1008 g_pTemplateUpdateQueue->put(pInfo);
1009 unlockProperties();
1010 }
1011
1012 /**
1013 * Get list of events used by DCIs
1014 */
1015 UINT32 *Template::getDCIEventsList(UINT32 *pdwCount)
1016 {
1017 UINT32 i, j, *pdwList;
1018
1019 pdwList = NULL;
1020 *pdwCount = 0;
1021
1022 lockDciAccess(false);
1023 for(i = 0; i < (UINT32)m_dcObjects->size(); i++)
1024 {
1025 m_dcObjects->get(i)->getEventList(&pdwList, pdwCount);
1026 }
1027 unlockDciAccess();
1028
1029 // Clean list from duplicates
1030 for(i = 0; i < *pdwCount; i++)
1031 {
1032 for(j = i + 1; j < *pdwCount; j++)
1033 {
1034 if (pdwList[i] == pdwList[j])
1035 {
1036 (*pdwCount)--;
1037 memmove(&pdwList[j], &pdwList[j + 1], sizeof(UINT32) * (*pdwCount - j));
1038 j--;
1039 }
1040 }
1041 }
1042
1043 return pdwList;
1044 }
1045
1046 /**
1047 * Get list of scripts used by DCIs
1048 */
1049 StringSet *Template::getDCIScriptList()
1050 {
1051 StringSet *list = new StringSet;
1052
1053 lockDciAccess(false);
1054 for(int i = 0; i < m_dcObjects->size(); i++)
1055 {
1056 DCObject *o = m_dcObjects->get(i);
1057 if (o->getDataSource() == DS_SCRIPT)
1058 {
1059 const TCHAR *name = o->getName();
1060 const TCHAR *p = _tcschr(name, _T('('));
1061 if (p != NULL)
1062 {
1063 TCHAR buffer[256];
1064 nx_strncpy(buffer, name, p - name + 1);
1065 list->add(buffer);
1066 }
1067 else
1068 {
1069 list->add(name);
1070 }
1071 }
1072 }
1073 unlockDciAccess();
1074 return list;
1075 }
1076
1077 /**
1078 * Create management pack record
1079 */
1080 void Template::createExportRecord(String &str)
1081 {
1082 TCHAR guid[48];
1083 str.appendFormattedString(_T("\t\t<template id=\"%d\">\n\t\t\t<guid>%s</guid>\n\t\t\t<name>%s</name>\n\t\t\t<flags>%d</flags>\n"),
1084 m_id, m_guid.toString(guid), (const TCHAR *)EscapeStringForXML2(m_name), m_flags);
1085
1086 // Path in groups
1087 StringList path;
1088 ObjectArray<NetObj> *list = getParentList(OBJECT_TEMPLATEGROUP);
1089 TemplateGroup *parent = NULL;
1090 while(list->size() > 0)
1091 {
1092 parent = (TemplateGroup *)list->get(0);
1093 path.add(parent->getName());
1094 delete list;
1095 list = parent->getParentList(OBJECT_TEMPLATEGROUP);
1096 }
1097 delete list;
1098
1099 str.append(_T("\t\t\t<path>\n"));
1100 for(int j = path.size() - 1, id = 1; j >= 0; j--, id++)
1101 {
1102 str.append(_T("\t\t\t\t<element id=\""));
1103 str.append(id);
1104 str.append(_T("\">"));
1105 str.append(path.get(j));
1106 str.append(_T("</element>\n"));
1107 }
1108 str.append(_T("\t\t\t</path>\n\t\t\t<dataCollection>\n"));
1109
1110 lockDciAccess(false);
1111 for(int i = 0; i < m_dcObjects->size(); i++)
1112 m_dcObjects->get(i)->createExportRecord(str);
1113 unlockDciAccess();
1114
1115 str.append(_T("\t\t\t</dataCollection>\n"));
1116 lockProperties();
1117 if (m_applyFilterSource != NULL)
1118 {
1119 str.append(_T("\t\t\t<filter>"));
1120 str.appendPreallocated(EscapeStringForXML(m_applyFilterSource, -1));
1121 str.append(_T("</filter>\n"));
1122 }
1123 unlockProperties();
1124 str.append(_T("\t\t</template>\n"));
1125 }
1126
1127 /**
1128 * Enumerate all DCIs
1129 */
1130 bool Template::enumDCObjects(bool (* pfCallback)(DCObject *, UINT32, void *), void *pArg)
1131 {
1132 bool success = true;
1133
1134 lockDciAccess(false);
1135 for(int i = 0; i < m_dcObjects->size(); i++)
1136 {
1137 if (!pfCallback(m_dcObjects->get(i), i, pArg))
1138 {
1139 success = false;
1140 break;
1141 }
1142 }
1143 unlockDciAccess();
1144 return success;
1145 }
1146
1147 /**
1148 * (Re)associate all DCIs
1149 */
1150 void Template::associateItems()
1151 {
1152 lockDciAccess(false);
1153 for(int i = 0; i < m_dcObjects->size(); i++)
1154 m_dcObjects->get(i)->changeBinding(0, this, FALSE);
1155 unlockDciAccess();
1156 }
1157
1158 /**
1159 * Prepare template for deletion
1160 */
1161 void Template::prepareForDeletion()
1162 {
1163 if (getObjectClass() == OBJECT_TEMPLATE)
1164 {
1165 lockChildList(false);
1166 for(int i = 0; i < m_childList->size(); i++)
1167 {
1168 NetObj *object = m_childList->get(i);
1169 if (object->isDataCollectionTarget())
1170 queueRemoveFromTarget(object->getId(), true);
1171 }
1172 unlockChildList();
1173 }
1174 NetObj::prepareForDeletion();
1175 }
1176
1177 /**
1178 * Check if template should be automatically applied to node
1179 * Returns AutoBindDecision_Bind if applicable, AutoBindDecision_Unbind if not,
1180 * AutoBindDecision_Ignore if no change required (script error or no auto apply)
1181 */
1182 AutoBindDecision Template::isApplicable(Node *node)
1183 {
1184 AutoBindDecision result = AutoBindDecision_Ignore;
1185
1186 NXSL_VM *filter = NULL;
1187 lockProperties();
1188 if ((m_flags & TF_AUTO_APPLY) && (m_applyFilter != NULL))
1189 {
1190 filter = new NXSL_VM(new NXSL_ServerEnv());
1191 if (!filter->load(m_applyFilter))
1192 {
1193 TCHAR buffer[1024];
1194 _sntprintf(buffer, 1024, _T("Template::%s::%d"), m_name, m_id);
1195 PostEvent(EVENT_SCRIPT_ERROR, g_dwMgmtNode, "ssd", buffer, filter->getErrorText(), m_id);
1196 nxlog_write(MSG_TEMPLATE_SCRIPT_EXECUTION_ERROR, EVENTLOG_WARNING_TYPE, "dss", m_id, m_name, filter->getErrorText());
1197 delete_and_null(filter);
1198 }
1199 }
1200 unlockProperties();
1201
1202 if (filter == NULL)
1203 return result;
1204
1205 filter->setGlobalVariable(_T("$node"), new NXSL_Value(new NXSL_Object(&g_nxslNodeClass, node)));
1206 if (filter->run())
1207 {
1208 NXSL_Value *value = filter->getResult();
1209 result = ((value != NULL) && (value->getValueAsInt32() != 0)) ? AutoBindDecision_Bind : AutoBindDecision_Unbind;
1210 }
1211 else
1212 {
1213 lockProperties();
1214 TCHAR buffer[1024];
1215 _sntprintf(buffer, 1024, _T("Template::%s::%d"), m_name, m_id);
1216 PostEvent(EVENT_SCRIPT_ERROR, g_dwMgmtNode, "ssd", buffer, filter->getErrorText(), m_id);
1217 nxlog_write(MSG_TEMPLATE_SCRIPT_EXECUTION_ERROR, EVENTLOG_WARNING_TYPE, "dss", m_id, m_name, filter->getErrorText());
1218 unlockProperties();
1219 }
1220 delete filter;
1221 return result;
1222 }
1223
1224 /**
1225 * Get last (current) DCI values. Moved to Template from DataCollectionTarget to allow
1226 * simplified creation of DCI selection dialog in management console. For classes not
1227 * derived from DataCollectionTarget actual values will always be empty strings
1228 * with data type DCI_DT_NULL.
1229 */
1230 UINT32 Template::getLastValues(NXCPMessage *msg, bool objectTooltipOnly, bool overviewOnly, bool includeNoValueObjects)
1231 {
1232 lockDciAccess(false);
1233
1234 UINT32 dwId = VID_DCI_VALUES_BASE, dwCount = 0;
1235 for(int i = 0; i < m_dcObjects->size(); i++)
1236 {
1237 DCObject *object = m_dcObjects->get(i);
1238 if ((object->hasValue() || includeNoValueObjects) &&
1239 (!objectTooltipOnly || object->isShowOnObjectTooltip()) &&
1240 (!overviewOnly || object->isShowInObjectOverview()))
1241 {
1242 if (object->getType() == DCO_TYPE_ITEM)
1243 {
1244 ((DCItem *)object)->fillLastValueMessage(msg, dwId);
1245 dwId += 50;
1246 dwCount++;
1247 }
1248 else if (object->getType() == DCO_TYPE_TABLE)
1249 {
1250 ((DCTable *)object)->fillLastValueSummaryMessage(msg, dwId);
1251 dwId += 50;
1252 dwCount++;
1253 }
1254 }
1255 }
1256 msg->setField(VID_NUM_ITEMS, dwCount);
1257
1258 unlockDciAccess();
1259 return RCC_SUCCESS;
1260 }
1261
1262 /**
1263 * Called when data collection configuration changed
1264 */
1265 void Template::onDataCollectionChange()
1266 {
1267 // Do not queue updates for subclasses
1268 if (getObjectClass() == OBJECT_TEMPLATE)
1269 queueUpdate();
1270 }
1271
1272 /**
1273 * Update template from import
1274 */
1275 void Template::updateFromImport(ConfigEntry *config)
1276 {
1277 // Name and version
1278 lockProperties();
1279 m_dwVersion = config->getSubEntryValueAsUInt(_T("version"), 0, m_dwVersion);
1280 m_flags = config->getSubEntryValueAsUInt(_T("flags"), 0, m_flags);
1281 unlockProperties();
1282
1283 // Auto-apply filter
1284 setAutoApplyFilter(config->getSubEntryValue(_T("filter")));
1285
1286 // Data collection
1287 ObjectArray<uuid> guidList(32, 32, true);
1288
1289 lockDciAccess(true);
1290 ConfigEntry *dcRoot = config->findEntry(_T("dataCollection"));
1291 if (dcRoot != NULL)
1292 {
1293 ObjectArray<ConfigEntry> *dcis = dcRoot->getSubEntries(_T("dci#*"));
1294 for(int i = 0; i < dcis->size(); i++)
1295 {
1296 ConfigEntry *e = dcis->get(i);
1297 uuid guid = e->getSubEntryValueAsUUID(_T("guid"));
1298 DCObject *curr = !guid.isNull() ? getDCObjectByGUID(guid, false) : NULL;
1299 if ((curr != NULL) && (curr->getType() == DCO_TYPE_ITEM))
1300 {
1301 curr->updateFromImport(e);
1302 }
1303 else
1304 {
1305 m_dcObjects->add(new DCItem(e, this));
1306 }
1307 guidList.add(new uuid(guid));
1308 }
1309 delete dcis;
1310
1311 ObjectArray<ConfigEntry> *dctables = dcRoot->getSubEntries(_T("dctable#*"));
1312 for(int i = 0; i < dctables->size(); i++)
1313 {
1314 ConfigEntry *e = dctables->get(i);
1315 uuid guid = e->getSubEntryValueAsUUID(_T("guid"));
1316 DCObject *curr = !guid.isNull() ? getDCObjectByGUID(guid, false) : NULL;
1317 if ((curr != NULL) && (curr->getType() == DCO_TYPE_TABLE))
1318 {
1319 curr->updateFromImport(e);
1320 }
1321 else
1322 {
1323 m_dcObjects->add(new DCTable(e, this));
1324 }
1325 guidList.add(new uuid(guid));
1326 }
1327 delete dctables;
1328 }
1329
1330 // Delete DCIs missing in import
1331 IntegerArray<UINT32> deleteList;
1332 for(int i = 0; i < m_dcObjects->size(); i++)
1333 {
1334 bool found = false;
1335 for(int j = 0; j < guidList.size(); j++)
1336 {
1337 if (guidList.get(j)->equals(m_dcObjects->get(i)->getGuid()))
1338 {
1339 found = true;
1340 break;
1341 }
1342 }
1343
1344 if (!found)
1345 {
1346 deleteList.add(m_dcObjects->get(i)->getId());
1347 }
1348 }
1349
1350 for(int i = 0; i < deleteList.size(); i++)
1351 deleteDCObject(deleteList.get(i), false);
1352
1353 unlockDciAccess();
1354
1355 queueUpdate();
1356 }