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