439ae892fec54b5d914d91d375a1b9e639865598
[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 // Destroy item
470 DbgPrintf(7, _T("Template::DeleteDCObject: deleting DCObject %d from object %d"), (int)dcObjectId, (int)m_id);
471 destroyItem(object, i);
472 m_isModified = true;
473 success = true;
474 DbgPrintf(7, _T("Template::DeleteDCObject: DCO deleted from object %d"), (int)m_id);
475 break;
476 }
477 }
478
479 if (needLock)
480 unlockDciAccess();
481 return success;
482 }
483
484 /**
485 * Delets child DCI objects of instance discovery DCI.
486 * It is assumed that list is already locked
487 */
488 void Template::deleteChildDCIs(UINT32 dcObjectId)
489 {
490 for(int i = 0; i < m_dcObjects->size(); i++)
491 {
492 DCObject *subObject = m_dcObjects->get(i);
493 if (subObject->getTemplateItemId() == dcObjectId)
494 {
495 destroyItem(subObject, i);
496 i--;
497 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);
498 }
499 }
500 }
501
502 /**
503 * Delete DCI object.
504 * Deletes or schedules deletion from DB and removes it from index
505 * It is assumed that list is already locked
506 */
507 void Template::destroyItem(DCObject *object, int index)
508 {
509 if (object->prepareForDeletion())
510 {
511 // Physically delete DCI only if it is not busy
512 // Busy DCIs will be deleted by data collector
513 object->deleteFromDatabase();
514 m_dcObjects->remove(index);
515 }
516 else
517 {
518 m_dcObjects->unlink(index);
519 DbgPrintf(7, _T("Template::DeleteItem: destruction of DCO %d delayed"), (int)object->getId());
520 }
521 }
522
523 /**
524 * Modify data collection object from NXCP message
525 */
526 bool Template::updateDCObject(UINT32 dwItemId, NXCPMessage *pMsg, UINT32 *pdwNumMaps, UINT32 **ppdwMapIndex, UINT32 **ppdwMapId)
527 {
528 bool success = false;
529
530 lockDciAccess(false);
531
532 // Check if that item exists
533 for(int i = 0; i < m_dcObjects->size(); i++)
534 {
535 DCObject *object = m_dcObjects->get(i);
536 if (object->getId() == dwItemId)
537 {
538 if (object->getType() == DCO_TYPE_ITEM)
539 {
540 ((DCItem *)object)->updateFromMessage(pMsg, pdwNumMaps, ppdwMapIndex, ppdwMapId);
541 if (((DCItem *)object)->getInstanceDiscoveryMethod() != IDM_NONE)
542 {
543 updateInstanceDiscoveryItems((DCItem *)object);
544 }
545 }
546 else
547 {
548 object->updateFromMessage(pMsg);
549 }
550 success = true;
551 m_isModified = true;
552 break;
553 }
554 }
555
556 unlockDciAccess();
557 return success;
558 }
559
560 /**
561 * Update DCIs created by instance dicovery.
562 * This method expects DCI access already locked.
563 *
564 * @param dci instance discovery template DCI
565 */
566 void Template::updateInstanceDiscoveryItems(DCItem *dci)
567 {
568 for(int i = 0; i < m_dcObjects->size(); i++)
569 {
570 DCObject *object = m_dcObjects->get(i);
571 if ((object->getType() == DCO_TYPE_ITEM) && (object->getTemplateId() == m_id) && (object->getTemplateItemId() == dci->getId()))
572 {
573 object->updateFromTemplate(dci);
574 }
575 }
576 }
577
578 /**
579 * Set status for group of DCIs
580 */
581 bool Template::setItemStatus(UINT32 dwNumItems, UINT32 *pdwItemList, int iStatus)
582 {
583 bool success = true;
584
585 lockDciAccess(false);
586 for(UINT32 i = 0; i < dwNumItems; i++)
587 {
588 int j;
589 for(j = 0; j < m_dcObjects->size(); j++)
590 {
591 if (m_dcObjects->get(j)->getId() == pdwItemList[i])
592 {
593 m_dcObjects->get(j)->setStatus(iStatus, true);
594 break;
595 }
596 }
597 if (j == m_dcObjects->size())
598 success = false; // Invalid DCI ID provided
599 }
600 unlockDciAccess();
601 return success;
602 }
603
604 /**
605 * Lock data collection items list
606 */
607 bool Template::lockDCIList(int sessionId, const TCHAR *pszNewOwner, TCHAR *pszCurrOwner)
608 {
609 bool success;
610
611 lockProperties();
612 if (m_dciLockStatus == -1)
613 {
614 m_dciLockStatus = sessionId;
615 m_dciListModified = false;
616 nx_strncpy(m_szCurrDCIOwner, pszNewOwner, MAX_SESSION_NAME);
617 success = true;
618 }
619 else
620 {
621 if (pszCurrOwner != NULL)
622 _tcscpy(pszCurrOwner, m_szCurrDCIOwner);
623 success = false;
624 }
625 unlockProperties();
626 return success;
627 }
628
629 /**
630 * Unlock data collection items list
631 */
632 bool Template::unlockDCIList(int sessionId)
633 {
634 bool success = false;
635 bool callChangeHook = false;
636
637 lockProperties();
638 if (m_dciLockStatus == sessionId)
639 {
640 m_dciLockStatus = -1;
641 if (m_dciListModified)
642 {
643 if (getObjectClass() == OBJECT_TEMPLATE)
644 m_dwVersion++;
645 setModified();
646 callChangeHook = true;
647 }
648 m_dciListModified = false;
649 success = true;
650 }
651 unlockProperties();
652
653 if (callChangeHook)
654 onDataCollectionChange();
655
656 return success;
657 }
658
659 /**
660 * Send DCI list to client
661 */
662 void Template::sendItemsToClient(ClientSession *pSession, UINT32 dwRqId)
663 {
664 NXCPMessage msg;
665
666 // Prepare message
667 msg.setId(dwRqId);
668 msg.setCode(CMD_NODE_DCI);
669
670 lockDciAccess(false);
671
672 // Walk through items list
673 for(int i = 0; i < m_dcObjects->size(); i++)
674 {
675 m_dcObjects->get(i)->createMessage(&msg);
676 pSession->sendMessage(&msg);
677 msg.deleteAllFields();
678 }
679
680 unlockDciAccess();
681
682 // Send end-of-list indicator
683 msg.setEndOfSequence();
684 pSession->sendMessage(&msg);
685 }
686
687 /**
688 * Get DCI's data type
689 */
690 int Template::getItemType(UINT32 dwItemId)
691 {
692 int iType = -1;
693
694 lockDciAccess(false);
695 // Check if that item exists
696 for(int i = 0; i < m_dcObjects->size(); i++)
697 {
698 DCObject *object = m_dcObjects->get(i);
699 if (object->getId() == dwItemId)
700 {
701 if (object->getType() == DCO_TYPE_ITEM)
702 {
703 iType = ((DCItem *)object)->getDataType();
704 }
705 break;
706 }
707 }
708
709 unlockDciAccess();
710 return iType;
711 }
712
713 /**
714 * Get item by it's id
715 */
716 DCObject *Template::getDCObjectById(UINT32 itemId, bool lock)
717 {
718 DCObject *object = NULL;
719
720 if (lock)
721 lockDciAccess(false);
722
723 for(int i = 0; i < m_dcObjects->size(); i++)
724 {
725 DCObject *curr = m_dcObjects->get(i);
726 if (curr->getId() == itemId)
727 {
728 object = curr;
729 break;
730 }
731 }
732
733 if (lock)
734 unlockDciAccess();
735 return object;
736 }
737
738 /**
739 * Get item by template item id
740 */
741 DCObject *Template::getDCObjectByTemplateId(UINT32 tmplItemId)
742 {
743 DCObject *object = NULL;
744
745 lockDciAccess(false);
746 // Check if that item exists
747 for(int i = 0; i < m_dcObjects->size(); i++)
748 {
749 DCObject *curr = m_dcObjects->get(i);
750 if (curr->getTemplateItemId() == tmplItemId)
751 {
752 object = curr;
753 break;
754 }
755 }
756
757 unlockDciAccess();
758 return object;
759 }
760
761 /**
762 * Get item by it's name (case-insensetive)
763 */
764 DCObject *Template::getDCObjectByName(const TCHAR *name)
765 {
766 DCObject *object = NULL;
767
768 lockDciAccess(false);
769 // Check if that item exists
770 for(int i = 0; i < m_dcObjects->size(); i++)
771 {
772 DCObject *curr = m_dcObjects->get(i);
773 if (!_tcsicmp(curr->getName(), name))
774 {
775 object = curr;
776 break;
777 }
778 }
779 unlockDciAccess();
780 return object;
781 }
782
783 /**
784 * Get item by it's description (case-insensetive)
785 */
786 DCObject *Template::getDCObjectByDescription(const TCHAR *description)
787 {
788 DCObject *object = NULL;
789
790 lockDciAccess(false);
791 // Check if that item exists
792 for(int i = 0; i < m_dcObjects->size(); i++)
793 {
794 DCObject *curr = m_dcObjects->get(i);
795 if (!_tcsicmp(curr->getDescription(), description))
796 {
797 object = curr;
798 break;
799 }
800 }
801 unlockDciAccess();
802 return object;
803 }
804
805 /**
806 * Get item by GUID
807 */
808 DCObject *Template::getDCObjectByGUID(const uuid& guid, bool lock)
809 {
810 DCObject *object = NULL;
811
812 if (lock)
813 lockDciAccess(false);
814
815 // Check if that item exists
816 for(int i = 0; i < m_dcObjects->size(); i++)
817 {
818 DCObject *curr = m_dcObjects->get(i);
819 if (guid.equals(curr->getGuid()))
820 {
821 object = curr;
822 break;
823 }
824 }
825
826 if (lock)
827 unlockDciAccess();
828 return object;
829 }
830
831 /**
832 * Get item by it's index
833 */
834 DCObject *Template::getDCObjectByIndex(int index)
835 {
836 lockDciAccess(false);
837 DCObject *object = m_dcObjects->get(index);
838 unlockDciAccess();
839 return object;
840 }
841
842 /**
843 * Get all DC objects with matching name and description
844 */
845 NXSL_Value *Template::getAllDCObjectsForNXSL(const TCHAR *name, const TCHAR *description)
846 {
847 NXSL_Array *list = new NXSL_Array();
848 lockDciAccess(false);
849 for(int i = 0; i < m_dcObjects->size(); i++)
850 {
851 DCObject *curr = m_dcObjects->get(i);
852 if (((name == NULL) || MatchString(name, curr->getName(), false)) &&
853 ((description == NULL) || MatchString(description, curr->getDescription(), false)))
854 {
855 list->set(list->size(), curr->createNXSLObject());
856 }
857 }
858 unlockDciAccess();
859 return new NXSL_Value(list);
860 }
861
862 /**
863 * Redefined status calculation for template
864 */
865 void Template::calculateCompoundStatus(BOOL bForcedRecalc)
866 {
867 m_status = STATUS_NORMAL;
868 }
869
870 /**
871 * Create NXCP message with object's data
872 */
873 void Template::fillMessageInternal(NXCPMessage *pMsg)
874 {
875 NetObj::fillMessageInternal(pMsg);
876 pMsg->setField(VID_TEMPLATE_VERSION, m_dwVersion);
877 pMsg->setField(VID_FLAGS, m_flags);
878 pMsg->setField(VID_AUTOBIND_FILTER, CHECK_NULL_EX(m_applyFilterSource));
879 }
880
881 /**
882 * Modify object from NXCP message
883 */
884 UINT32 Template::modifyFromMessageInternal(NXCPMessage *pRequest)
885 {
886 // Change template version
887 if (pRequest->isFieldExist(VID_TEMPLATE_VERSION))
888 m_dwVersion = pRequest->getFieldAsUInt32(VID_TEMPLATE_VERSION);
889
890 // Change flags
891 if (pRequest->isFieldExist(VID_FLAGS))
892 m_flags = pRequest->getFieldAsUInt32(VID_FLAGS);
893
894 // Change apply filter
895 if (pRequest->isFieldExist(VID_AUTOBIND_FILTER))
896 {
897 free(m_applyFilterSource);
898 delete m_applyFilter;
899 m_applyFilterSource = pRequest->getFieldAsString(VID_AUTOBIND_FILTER);
900 if (m_applyFilterSource != NULL)
901 {
902 TCHAR error[256];
903
904 m_applyFilter = NXSLCompile(m_applyFilterSource, error, 256, NULL);
905 if (m_applyFilter == NULL)
906 {
907 TCHAR buffer[1024];
908 _sntprintf(buffer, 1024, _T("Template::%s::%d"), m_name, m_id);
909 PostEvent(EVENT_SCRIPT_ERROR, g_dwMgmtNode, "ssd", buffer, error, m_id);
910 nxlog_write(MSG_TEMPLATE_SCRIPT_COMPILATION_ERROR, EVENTLOG_WARNING_TYPE, "dss", m_id, m_name, error);
911 }
912 }
913 else
914 {
915 m_applyFilter = NULL;
916 }
917 }
918
919 return NetObj::modifyFromMessageInternal(pRequest);
920 }
921
922 /**
923 * Apply template to data collection target
924 */
925 BOOL Template::applyToTarget(DataCollectionTarget *target)
926 {
927 UINT32 *pdwItemList;
928 BOOL bErrors = FALSE;
929
930 // Link node to template
931 if (!isChild(target->getId()))
932 {
933 addChild(target);
934 target->addParent(this);
935 }
936
937 pdwItemList = (UINT32 *)malloc(sizeof(UINT32) * m_dcObjects->size());
938 DbgPrintf(2, _T("Apply %d items from template \"%s\" to target \"%s\""),
939 m_dcObjects->size(), m_name, target->getName());
940
941 // Copy items
942 for(int i = 0; i < m_dcObjects->size(); i++)
943 {
944 DCObject *object = m_dcObjects->get(i);
945 pdwItemList[i] = object->getId();
946 if (!target->applyTemplateItem(m_id, object))
947 {
948 bErrors = TRUE;
949 }
950 }
951
952 // Clean items deleted from template
953 target->cleanDeletedTemplateItems(m_id, m_dcObjects->size(), pdwItemList);
954
955 // Cleanup
956 free(pdwItemList);
957
958 target->onDataCollectionChange();
959
960 // Queue update if target is a cluster
961 if (target->getObjectClass() == OBJECT_CLUSTER)
962 {
963 target->queueUpdate();
964 }
965
966 return bErrors;
967 }
968
969 /**
970 * Queue template update
971 */
972 void Template::queueUpdate()
973 {
974 lockChildList(false);
975 for(int i = 0; i < m_childList->size(); i++)
976 {
977 NetObj *object = m_childList->get(i);
978 if (object->isDataCollectionTarget())
979 {
980 incRefCount();
981 TEMPLATE_UPDATE_INFO *pInfo = (TEMPLATE_UPDATE_INFO *)malloc(sizeof(TEMPLATE_UPDATE_INFO));
982 pInfo->updateType = APPLY_TEMPLATE;
983 pInfo->pTemplate = this;
984 pInfo->targetId = object->getId();
985 pInfo->removeDCI = false;
986 g_pTemplateUpdateQueue->put(pInfo);
987 }
988 }
989 unlockChildList();
990 }
991
992 /**
993 * Queue template remove from node
994 */
995 void Template::queueRemoveFromTarget(UINT32 targetId, bool removeDCI)
996 {
997 lockProperties();
998 incRefCount();
999 TEMPLATE_UPDATE_INFO *pInfo = (TEMPLATE_UPDATE_INFO *)malloc(sizeof(TEMPLATE_UPDATE_INFO));
1000 pInfo->updateType = REMOVE_TEMPLATE;
1001 pInfo->pTemplate = this;
1002 pInfo->targetId = targetId;
1003 pInfo->removeDCI = removeDCI;
1004 g_pTemplateUpdateQueue->put(pInfo);
1005 unlockProperties();
1006 }
1007
1008 /**
1009 * Get list of events used by DCIs
1010 */
1011 UINT32 *Template::getDCIEventsList(UINT32 *pdwCount)
1012 {
1013 UINT32 i, j, *pdwList;
1014
1015 pdwList = NULL;
1016 *pdwCount = 0;
1017
1018 lockDciAccess(false);
1019 for(i = 0; i < (UINT32)m_dcObjects->size(); i++)
1020 {
1021 m_dcObjects->get(i)->getEventList(&pdwList, pdwCount);
1022 }
1023 unlockDciAccess();
1024
1025 // Clean list from duplicates
1026 for(i = 0; i < *pdwCount; i++)
1027 {
1028 for(j = i + 1; j < *pdwCount; j++)
1029 {
1030 if (pdwList[i] == pdwList[j])
1031 {
1032 (*pdwCount)--;
1033 memmove(&pdwList[j], &pdwList[j + 1], sizeof(UINT32) * (*pdwCount - j));
1034 j--;
1035 }
1036 }
1037 }
1038
1039 return pdwList;
1040 }
1041
1042 /**
1043 * Get list of scripts used by DCIs
1044 */
1045 StringSet *Template::getDCIScriptList()
1046 {
1047 StringSet *list = new StringSet;
1048
1049 lockDciAccess(false);
1050 for(int i = 0; i < m_dcObjects->size(); i++)
1051 {
1052 DCObject *o = m_dcObjects->get(i);
1053 if (o->getDataSource() == DS_SCRIPT)
1054 {
1055 const TCHAR *name = o->getName();
1056 const TCHAR *p = _tcschr(name, _T('('));
1057 if (p != NULL)
1058 {
1059 TCHAR buffer[256];
1060 nx_strncpy(buffer, name, p - name + 1);
1061 list->add(buffer);
1062 }
1063 else
1064 {
1065 list->add(name);
1066 }
1067 }
1068 }
1069 unlockDciAccess();
1070 return list;
1071 }
1072
1073 /**
1074 * Create management pack record
1075 */
1076 void Template::createExportRecord(String &str)
1077 {
1078 TCHAR guid[48];
1079 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"),
1080 m_id, m_guid.toString(guid), (const TCHAR *)EscapeStringForXML2(m_name), m_flags);
1081
1082 // Path in groups
1083 StringList path;
1084 ObjectArray<NetObj> *list = getParentList(OBJECT_TEMPLATEGROUP);
1085 TemplateGroup *parent = NULL;
1086 while(list->size() > 0)
1087 {
1088 parent = (TemplateGroup *)list->get(0);
1089 path.add(parent->getName());
1090 delete list;
1091 list = parent->getParentList(OBJECT_TEMPLATEGROUP);
1092 }
1093 delete list;
1094
1095 str.append(_T("\t\t\t<path>\n"));
1096 for(int j = path.size() - 1, id = 1; j >= 0; j--, id++)
1097 {
1098 str.append(_T("\t\t\t\t<element id=\""));
1099 str.append(id);
1100 str.append(_T("\">"));
1101 str.append(path.get(j));
1102 str.append(_T("</element>\n"));
1103 }
1104 str.append(_T("\t\t\t</path>\n\t\t\t<dataCollection>\n"));
1105
1106 lockDciAccess(false);
1107 for(int i = 0; i < m_dcObjects->size(); i++)
1108 m_dcObjects->get(i)->createExportRecord(str);
1109 unlockDciAccess();
1110
1111 str.append(_T("\t\t\t</dataCollection>\n"));
1112 lockProperties();
1113 if (m_applyFilterSource != NULL)
1114 {
1115 str.append(_T("\t\t\t<filter>"));
1116 str.appendPreallocated(EscapeStringForXML(m_applyFilterSource, -1));
1117 str.append(_T("</filter>\n"));
1118 }
1119 unlockProperties();
1120 str.append(_T("\t\t</template>\n"));
1121 }
1122
1123 /**
1124 * Enumerate all DCIs
1125 */
1126 bool Template::enumDCObjects(bool (* pfCallback)(DCObject *, UINT32, void *), void *pArg)
1127 {
1128 bool success = true;
1129
1130 lockDciAccess(false);
1131 for(int i = 0; i < m_dcObjects->size(); i++)
1132 {
1133 if (!pfCallback(m_dcObjects->get(i), i, pArg))
1134 {
1135 success = false;
1136 break;
1137 }
1138 }
1139 unlockDciAccess();
1140 return success;
1141 }
1142
1143 /**
1144 * (Re)associate all DCIs
1145 */
1146 void Template::associateItems()
1147 {
1148 lockDciAccess(false);
1149 for(int i = 0; i < m_dcObjects->size(); i++)
1150 m_dcObjects->get(i)->changeBinding(0, this, FALSE);
1151 unlockDciAccess();
1152 }
1153
1154 /**
1155 * Prepare template for deletion
1156 */
1157 void Template::prepareForDeletion()
1158 {
1159 if (getObjectClass() == OBJECT_TEMPLATE)
1160 {
1161 lockChildList(false);
1162 for(int i = 0; i < m_childList->size(); i++)
1163 {
1164 NetObj *object = m_childList->get(i);
1165 if (object->isDataCollectionTarget())
1166 queueRemoveFromTarget(object->getId(), true);
1167 }
1168 unlockChildList();
1169 }
1170 NetObj::prepareForDeletion();
1171 }
1172
1173 /**
1174 * Check if template should be automatically applied to node
1175 * Returns AutoBindDecision_Bind if applicable, AutoBindDecision_Unbind if not,
1176 * AutoBindDecision_Ignore if no change required (script error or no auto apply)
1177 */
1178 AutoBindDecision Template::isApplicable(Node *node)
1179 {
1180 AutoBindDecision result = AutoBindDecision_Ignore;
1181
1182 NXSL_VM *filter = NULL;
1183 lockProperties();
1184 if ((m_flags & TF_AUTO_APPLY) && (m_applyFilter != NULL))
1185 {
1186 filter = new NXSL_VM(new NXSL_ServerEnv());
1187 if (!filter->load(m_applyFilter))
1188 {
1189 TCHAR buffer[1024];
1190 _sntprintf(buffer, 1024, _T("Template::%s::%d"), m_name, m_id);
1191 PostEvent(EVENT_SCRIPT_ERROR, g_dwMgmtNode, "ssd", buffer, filter->getErrorText(), m_id);
1192 nxlog_write(MSG_TEMPLATE_SCRIPT_EXECUTION_ERROR, EVENTLOG_WARNING_TYPE, "dss", m_id, m_name, filter->getErrorText());
1193 delete_and_null(filter);
1194 }
1195 }
1196 unlockProperties();
1197
1198 if (filter == NULL)
1199 return result;
1200
1201 filter->setGlobalVariable(_T("$node"), new NXSL_Value(new NXSL_Object(&g_nxslNodeClass, node)));
1202 if (filter->run())
1203 {
1204 NXSL_Value *value = filter->getResult();
1205 result = ((value != NULL) && (value->getValueAsInt32() != 0)) ? AutoBindDecision_Bind : AutoBindDecision_Unbind;
1206 }
1207 else
1208 {
1209 lockProperties();
1210 TCHAR buffer[1024];
1211 _sntprintf(buffer, 1024, _T("Template::%s::%d"), m_name, m_id);
1212 PostEvent(EVENT_SCRIPT_ERROR, g_dwMgmtNode, "ssd", buffer, filter->getErrorText(), m_id);
1213 nxlog_write(MSG_TEMPLATE_SCRIPT_EXECUTION_ERROR, EVENTLOG_WARNING_TYPE, "dss", m_id, m_name, filter->getErrorText());
1214 unlockProperties();
1215 }
1216 delete filter;
1217 return result;
1218 }
1219
1220 /**
1221 * Get last (current) DCI values. Moved to Template from DataCollectionTarget to allow
1222 * simplified creation of DCI selection dialog in management console. For classes not
1223 * derived from DataCollectionTarget actual values will always be empty strings
1224 * with data type DCI_DT_NULL.
1225 */
1226 UINT32 Template::getLastValues(NXCPMessage *msg, bool objectTooltipOnly, bool overviewOnly, bool includeNoValueObjects)
1227 {
1228 lockDciAccess(false);
1229
1230 UINT32 dwId = VID_DCI_VALUES_BASE, dwCount = 0;
1231 for(int i = 0; i < m_dcObjects->size(); i++)
1232 {
1233 DCObject *object = m_dcObjects->get(i);
1234 if ((object->hasValue() || includeNoValueObjects) &&
1235 (!objectTooltipOnly || object->isShowOnObjectTooltip()) &&
1236 (!overviewOnly || object->isShowInObjectOverview()))
1237 {
1238 if (object->getType() == DCO_TYPE_ITEM)
1239 {
1240 ((DCItem *)object)->fillLastValueMessage(msg, dwId);
1241 dwId += 50;
1242 dwCount++;
1243 }
1244 else if (object->getType() == DCO_TYPE_TABLE)
1245 {
1246 ((DCTable *)object)->fillLastValueSummaryMessage(msg, dwId);
1247 dwId += 50;
1248 dwCount++;
1249 }
1250 }
1251 }
1252 msg->setField(VID_NUM_ITEMS, dwCount);
1253
1254 unlockDciAccess();
1255 return RCC_SUCCESS;
1256 }
1257
1258 /**
1259 * Called when data collection configuration changed
1260 */
1261 void Template::onDataCollectionChange()
1262 {
1263 // Do not queue updates for subclasses
1264 if (getObjectClass() == OBJECT_TEMPLATE)
1265 queueUpdate();
1266 }
1267
1268 /**
1269 * Update template from import
1270 */
1271 void Template::updateFromImport(ConfigEntry *config)
1272 {
1273 // Name and version
1274 lockProperties();
1275 m_dwVersion = config->getSubEntryValueAsUInt(_T("version"), 0, m_dwVersion);
1276 m_flags = config->getSubEntryValueAsUInt(_T("flags"), 0, m_flags);
1277 unlockProperties();
1278
1279 // Auto-apply filter
1280 setAutoApplyFilter(config->getSubEntryValue(_T("filter")));
1281
1282 // Data collection
1283 ObjectArray<uuid> guidList(32, 32, true);
1284
1285 lockDciAccess(true);
1286 ConfigEntry *dcRoot = config->findEntry(_T("dataCollection"));
1287 if (dcRoot != NULL)
1288 {
1289 ObjectArray<ConfigEntry> *dcis = dcRoot->getSubEntries(_T("dci#*"));
1290 for(int i = 0; i < dcis->size(); i++)
1291 {
1292 ConfigEntry *e = dcis->get(i);
1293 uuid guid = e->getSubEntryValueAsUUID(_T("guid"));
1294 DCObject *curr = !guid.isNull() ? getDCObjectByGUID(guid, false) : NULL;
1295 if ((curr != NULL) && (curr->getType() == DCO_TYPE_ITEM))
1296 {
1297 curr->updateFromImport(e);
1298 }
1299 else
1300 {
1301 m_dcObjects->add(new DCItem(e, this));
1302 }
1303 guidList.add(new uuid(guid));
1304 }
1305 delete dcis;
1306
1307 ObjectArray<ConfigEntry> *dctables = dcRoot->getSubEntries(_T("dctable#*"));
1308 for(int i = 0; i < dctables->size(); i++)
1309 {
1310 ConfigEntry *e = dctables->get(i);
1311 uuid guid = e->getSubEntryValueAsUUID(_T("guid"));
1312 DCObject *curr = !guid.isNull() ? getDCObjectByGUID(guid, false) : NULL;
1313 if ((curr != NULL) && (curr->getType() == DCO_TYPE_TABLE))
1314 {
1315 curr->updateFromImport(e);
1316 }
1317 else
1318 {
1319 m_dcObjects->add(new DCTable(e, this));
1320 }
1321 guidList.add(new uuid(guid));
1322 }
1323 delete dctables;
1324 }
1325
1326 // Delete DCIs missing in import
1327 IntegerArray<UINT32> deleteList;
1328 for(int i = 0; i < m_dcObjects->size(); i++)
1329 {
1330 bool found = false;
1331 for(int j = 0; j < guidList.size(); j++)
1332 {
1333 if (guidList.get(j)->equals(m_dcObjects->get(i)->getGuid()))
1334 {
1335 found = true;
1336 break;
1337 }
1338 }
1339
1340 if (!found)
1341 {
1342 deleteList.add(m_dcObjects->get(i)->getId());
1343 }
1344 }
1345
1346 for(int i = 0; i < deleteList.size(); i++)
1347 deleteDCObject(deleteList.get(i), false);
1348
1349 unlockDciAccess();
1350
1351 queueUpdate();
1352 }