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