Implemented object caching on client side
[public/netxms.git] / src / server / core / netobj.cpp
1 /*
2 ** Project X - Network Management System
3 ** Copyright (C) 2003 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 ** $module: netobj.cpp
20 **
21 **/
22
23 #include "nxcore.h"
24
25
26 //
27 // NetObj class constructor
28 //
29
30 NetObj::NetObj()
31 {
32 m_dwRefCount = 0;
33 m_hMutex = MutexCreate();
34 m_mutexRefCount = MutexCreate();
35 m_iStatus = STATUS_UNKNOWN;
36 m_szName[0] = 0;
37 m_bIsModified = FALSE;
38 m_bIsDeleted = FALSE;
39 m_dwIpAddr = 0;
40 m_dwChildCount = 0;
41 m_pChildList = NULL;
42 m_dwParentCount = 0;
43 m_pParentList = NULL;
44 m_pAccessList = new AccessList;
45 m_bInheritAccessRights = TRUE;
46 m_dwImageId = IMG_DEFAULT; // Default image
47 m_pPollRequestor = NULL;
48 }
49
50
51 //
52 // NetObj class destructor
53 //
54
55 NetObj::~NetObj()
56 {
57 MutexDestroy(m_hMutex);
58 MutexDestroy(m_mutexRefCount);
59 if (m_pChildList != NULL)
60 free(m_pChildList);
61 if (m_pParentList != NULL)
62 free(m_pParentList);
63 delete m_pAccessList;
64 }
65
66
67 //
68 // Create object from database data
69 //
70
71 BOOL NetObj::CreateFromDB(DWORD dwId)
72 {
73 return FALSE; // Abstract objects cannot be loaded from database
74 }
75
76
77 //
78 // Save object to database
79 //
80
81 BOOL NetObj::SaveToDB(void)
82 {
83 return FALSE; // Abstract objects cannot be saved to database
84 }
85
86
87 //
88 // Delete object from database
89 //
90
91 BOOL NetObj::DeleteFromDB(void)
92 {
93 char szQuery[256];
94
95 // Delete ACL
96 sprintf(szQuery, "DELETE FROM acl WHERE object_id=%d", m_dwId);
97 QueueSQLRequest(szQuery);
98 sprintf(szQuery, "DELETE FROM object_properties WHERE object_id=%d", m_dwId);
99 QueueSQLRequest(szQuery);
100 return TRUE;
101 }
102
103
104 //
105 // Load common object properties from database
106 //
107
108 BOOL NetObj::LoadCommonProperties(void)
109 {
110 DB_RESULT hResult;
111 TCHAR szQuery[256];
112 BOOL bResult = FALSE;
113
114 // Load access options
115 _sntprintf(szQuery, 256, _T("SELECT name,status,is_deleted,image_id,"
116 "inherit_access_rights,last_modified "
117 "FROM object_properties WHERE object_id=%ld"), m_dwId);
118 hResult = DBSelect(g_hCoreDB, szQuery);
119 if (hResult != NULL)
120 {
121 if (DBGetNumRows(hResult) > 0)
122 {
123 _tcsncpy(m_szName, DBGetField(hResult, 0, 0), MAX_OBJECT_NAME);
124 m_iStatus = DBGetFieldLong(hResult, 0, 1);
125 m_bIsDeleted = DBGetFieldLong(hResult, 0, 2) ? TRUE : FALSE;
126 m_dwImageId = DBGetFieldULong(hResult, 0, 3);
127 m_bInheritAccessRights = DBGetFieldLong(hResult, 0, 4) ? TRUE : FALSE;
128 m_dwTimeStamp = DBGetFieldULong(hResult, 0, 5);
129 bResult = TRUE;
130 }
131 DBFreeResult(hResult);
132 }
133 return bResult;
134 }
135
136
137 //
138 // Save common object properties to database
139 //
140
141 BOOL NetObj::SaveCommonProperties(void)
142 {
143 TCHAR szQuery[512];
144 DB_RESULT hResult;
145 BOOL bResult = FALSE;
146
147 // Save access options
148 _sntprintf(szQuery, 512, _T("SELECT object_id FROM object_properties WHERE object_id=%ld"), m_dwId);
149 hResult = DBSelect(g_hCoreDB, szQuery);
150 if (hResult != NULL)
151 {
152 if (DBGetNumRows(hResult) > 0)
153 _sntprintf(szQuery, 512,
154 _T("UPDATE object_properties SET name='%s',status=%d,"
155 "is_deleted=%d,image_id=%ld,inherit_access_rights=%d,"
156 "last_modified=%ld WHERE object_id=%ld"),
157 m_szName, m_iStatus, m_bIsDeleted, m_dwImageId,
158 m_bInheritAccessRights, m_dwTimeStamp, m_dwId);
159 else
160 _sntprintf(szQuery, 512,
161 _T("INSERT INTO object_properties (object_id,name,status,is_deleted,"
162 "image_id,inherit_access_rights,last_modified) "
163 "VALUES (%ld,'%s',%d,%d,%ld,%d,%ld)"),
164 m_dwId, m_szName, m_iStatus, m_bIsDeleted, m_dwImageId,
165 m_bInheritAccessRights, m_dwTimeStamp);
166 DBFreeResult(hResult);
167 bResult = DBQuery(g_hCoreDB, szQuery);
168 }
169 return bResult;
170 }
171
172
173 //
174 // Add reference to the new child object
175 //
176
177 void NetObj::AddChild(NetObj *pObject)
178 {
179 DWORD i;
180
181 Lock();
182 for(i = 0; i < m_dwChildCount; i++)
183 if (m_pChildList[i] == pObject)
184 {
185 Unlock();
186 return; // Already in the child list
187 }
188 m_pChildList = (NetObj **)realloc(m_pChildList, sizeof(NetObj *) * (m_dwChildCount + 1));
189 m_pChildList[m_dwChildCount++] = pObject;
190 Modify();
191 Unlock();
192 }
193
194
195 //
196 // Add reference to parent object
197 //
198
199 void NetObj::AddParent(NetObj *pObject)
200 {
201 DWORD i;
202
203 Lock();
204 for(i = 0; i < m_dwParentCount; i++)
205 if (m_pParentList[i] == pObject)
206 {
207 Unlock();
208 return; // Already in the parents list
209 }
210 m_pParentList = (NetObj **)realloc(m_pParentList, sizeof(NetObj *) * (m_dwParentCount + 1));
211 m_pParentList[m_dwParentCount++] = pObject;
212 Modify();
213 Unlock();
214 }
215
216
217 //
218 // Delete reference to child object
219 //
220
221 void NetObj::DeleteChild(NetObj *pObject)
222 {
223 DWORD i;
224
225 Lock();
226 for(i = 0; i < m_dwChildCount; i++)
227 if (m_pChildList[i] == pObject)
228 break;
229
230 if (i == m_dwChildCount) // No such object
231 {
232 Unlock();
233 return;
234 }
235 m_dwChildCount--;
236 if (m_dwChildCount > 0)
237 {
238 memmove(&m_pChildList[i], &m_pChildList[i + 1], sizeof(NetObj *) * (m_dwChildCount - i));
239 m_pChildList = (NetObj **)realloc(m_pChildList, sizeof(NetObj *) * m_dwChildCount);
240 }
241 else
242 {
243 free(m_pChildList);
244 m_pChildList = NULL;
245 }
246 Modify();
247 Unlock();
248 }
249
250
251 //
252 // Delete reference to parent object
253 //
254
255 void NetObj::DeleteParent(NetObj *pObject)
256 {
257 DWORD i;
258
259 Lock();
260 for(i = 0; i < m_dwParentCount; i++)
261 if (m_pParentList[i] == pObject)
262 break;
263 if (i == m_dwParentCount) // No such object
264 {
265 Unlock();
266 return;
267 }
268 m_dwParentCount--;
269 if (m_dwParentCount > 0)
270 {
271 memmove(&m_pParentList[i], &m_pParentList[i + 1], sizeof(NetObj *) * (m_dwParentCount - i));
272 m_pParentList = (NetObj **)realloc(m_pParentList, sizeof(NetObj *) * m_dwParentCount);
273 }
274 else
275 {
276 free(m_pParentList);
277 m_pParentList = NULL;
278 }
279 Modify();
280 Unlock();
281 }
282
283
284 //
285 // Prepare object for deletion - remove all references, etc.
286 // bIndexLocked should be TRUE if object index by ID is already locked
287 // by current thread
288 //
289
290 void NetObj::Delete(BOOL bIndexLocked)
291 {
292 DWORD i;
293
294 DbgPrintf(AF_DEBUG_OBJECTS, "Deleting object %d [%s]", m_dwId, m_szName);
295
296 Lock();
297
298 // Remove references to this object from parent objects
299 DbgPrintf(AF_DEBUG_OBJECTS, "NetObj::Delete(): clearing parent list for object %d", m_dwId);
300 for(i = 0; i < m_dwParentCount; i++)
301 {
302 m_pParentList[i]->DeleteChild(this);
303 m_pParentList[i]->CalculateCompoundStatus();
304 }
305 free(m_pParentList);
306 m_pParentList = NULL;
307 m_dwParentCount = 0;
308
309 // Delete references to this object from child objects
310 DbgPrintf(AF_DEBUG_OBJECTS, "NetObj::Delete(): clearing child list for object %d", m_dwId);
311 for(i = 0; i < m_dwChildCount; i++)
312 {
313 m_pChildList[i]->DeleteParent(this);
314 if (m_pChildList[i]->IsOrphaned())
315 m_pChildList[i]->Delete(bIndexLocked);
316 }
317 free(m_pChildList);
318 m_pChildList = NULL;
319 m_dwChildCount = 0;
320
321 m_bIsDeleted = TRUE;
322 Modify();
323 Unlock();
324
325 DbgPrintf(AF_DEBUG_OBJECTS, "NetObj::Delete(): deleting object %d from indexes", m_dwId);
326 NetObjDeleteFromIndexes(this);
327
328 // Notify all other objects about object deletion
329 DbgPrintf(AF_DEBUG_OBJECTS, "NetObj::Delete(): calling OnObjectDelete(%d)", m_dwId);
330 if (!bIndexLocked)
331 RWLockReadLock(g_rwlockIdIndex, INFINITE);
332 for(i = 0; i < g_dwIdIndexSize; i++)
333 {
334 if (g_pIndexById[i].dwKey != m_dwId)
335 g_pIndexById[i].pObject->OnObjectDelete(m_dwId);
336 }
337 if (!bIndexLocked)
338 RWLockUnlock(g_rwlockIdIndex);
339
340 DbgPrintf(AF_DEBUG_OBJECTS, "Object %d successfully deleted", m_dwId);
341 }
342
343
344 //
345 // Default handler for object deletion notification
346 //
347
348 void NetObj::OnObjectDelete(DWORD dwObjectId)
349 {
350 }
351
352
353 //
354 // Print childs IDs
355 //
356
357 const char *NetObj::ChildList(char *szBuffer)
358 {
359 DWORD i;
360 char *pBuf = szBuffer;
361
362 *pBuf = 0;
363 Lock();
364 for(i = 0, pBuf = szBuffer; i < m_dwChildCount; i++)
365 {
366 sprintf(pBuf, "%d ", m_pChildList[i]->Id());
367 while(*pBuf)
368 pBuf++;
369 }
370 Unlock();
371 if (pBuf != szBuffer)
372 *(pBuf - 1) = 0;
373 return szBuffer;
374 }
375
376
377 //
378 // Print parents IDs
379 //
380
381 const char *NetObj::ParentList(char *szBuffer)
382 {
383 DWORD i;
384 char *pBuf = szBuffer;
385
386 *pBuf = 0;
387 Lock();
388 for(i = 0; i < m_dwParentCount; i++)
389 {
390 sprintf(pBuf, "%d ", m_pParentList[i]->Id());
391 while(*pBuf)
392 pBuf++;
393 }
394 Unlock();
395 if (pBuf != szBuffer)
396 *(pBuf - 1) = 0;
397 return szBuffer;
398 }
399
400
401 //
402 // Calculate status for compound object based on childs' status
403 //
404
405 void NetObj::CalculateCompoundStatus(void)
406 {
407 DWORD i;
408 int iSum, iCount, iOldStatus = m_iStatus;
409
410 if (m_iStatus != STATUS_UNMANAGED)
411 {
412 Lock();
413 /* TODO: probably status calculation algorithm should be changed */
414 for(i = 0, iSum = 0, iCount = 0; i < m_dwChildCount; i++)
415 if (m_pChildList[i]->Status() < STATUS_UNKNOWN)
416 {
417 iSum += m_pChildList[i]->Status();
418 iCount++;
419 }
420 if (iCount > 0)
421 m_iStatus = iSum / iCount;
422 else
423 m_iStatus = STATUS_UNKNOWN;
424 Unlock();
425
426 // Cause parent object(s) to recalculate it's status
427 if (iOldStatus != m_iStatus)
428 {
429 Lock();
430 for(i = 0; i < m_dwParentCount; i++)
431 m_pParentList[i]->CalculateCompoundStatus();
432 Unlock();
433 Modify();
434 }
435 }
436 }
437
438
439 //
440 // Load ACL from database
441 //
442
443 BOOL NetObj::LoadACLFromDB(void)
444 {
445 char szQuery[256];
446 DB_RESULT hResult;
447 BOOL bSuccess = FALSE;
448
449 // Load access list
450 sprintf(szQuery, "SELECT user_id,access_rights FROM acl WHERE object_id=%d", m_dwId);
451 hResult = DBSelect(g_hCoreDB, szQuery);
452 if (hResult != NULL)
453 {
454 int i, iNumRows;
455
456 iNumRows = DBGetNumRows(hResult);
457 for(i = 0; i < iNumRows; i++)
458 m_pAccessList->AddElement(DBGetFieldULong(hResult, i, 0),
459 DBGetFieldULong(hResult, i, 1));
460 DBFreeResult(hResult);
461 bSuccess = TRUE;
462 }
463
464 return bSuccess;
465 }
466
467
468 //
469 // Handler for ACL elements enumeration
470 //
471
472 static void EnumerationHandler(DWORD dwUserId, DWORD dwAccessRights, void *pArg)
473 {
474 char szQuery[256];
475
476 sprintf(szQuery, "INSERT INTO acl (object_id,user_id,access_rights) VALUES (%d,%d,%d)",
477 (DWORD)pArg, dwUserId, dwAccessRights);
478 DBQuery(g_hCoreDB, szQuery);
479 }
480
481
482 //
483 // Save ACL to database
484 //
485
486 BOOL NetObj::SaveACLToDB(void)
487 {
488 char szQuery[256];
489 BOOL bSuccess = FALSE;
490
491 // Save access list
492 sprintf(szQuery, "DELETE FROM acl WHERE object_id=%d", m_dwId);
493 if (DBQuery(g_hCoreDB, szQuery))
494 {
495 m_pAccessList->EnumerateElements(EnumerationHandler, (void *)m_dwId);
496 bSuccess = TRUE;
497 }
498 return bSuccess;
499 }
500
501
502 //
503 // Create CSCP message with object's data
504 //
505
506 void NetObj::CreateMessage(CSCPMessage *pMsg)
507 {
508 DWORD i, dwId;
509
510 pMsg->SetVariable(VID_OBJECT_CLASS, (WORD)Type());
511 pMsg->SetVariable(VID_OBJECT_ID, m_dwId);
512 pMsg->SetVariable(VID_OBJECT_NAME, m_szName);
513 pMsg->SetVariable(VID_OBJECT_STATUS, (WORD)m_iStatus);
514 pMsg->SetVariable(VID_IP_ADDRESS, m_dwIpAddr);
515 pMsg->SetVariable(VID_PARENT_CNT, m_dwParentCount);
516 pMsg->SetVariable(VID_CHILD_CNT, m_dwChildCount);
517 pMsg->SetVariable(VID_IS_DELETED, (WORD)m_bIsDeleted);
518 for(i = 0, dwId = VID_PARENT_ID_BASE; i < m_dwParentCount; i++, dwId++)
519 pMsg->SetVariable(dwId, m_pParentList[i]->Id());
520 for(i = 0, dwId = VID_CHILD_ID_BASE; i < m_dwChildCount; i++, dwId++)
521 pMsg->SetVariable(dwId, m_pChildList[i]->Id());
522 pMsg->SetVariable(VID_INHERIT_RIGHTS, (WORD)m_bInheritAccessRights);
523 pMsg->SetVariable(VID_IMAGE_ID, m_dwImageId);
524 m_pAccessList->CreateMessage(pMsg);
525 }
526
527
528 //
529 // Handler for EnumerateSessions()
530 //
531
532 static void BroadcastObjectChange(ClientSession *pSession, void *pArg)
533 {
534 if (pSession->IsAuthenticated())
535 pSession->OnObjectChange((NetObj *)pArg);
536 }
537
538
539 //
540 // Mark object as modified and put on client's notification queue
541 // We assume that object is locked at the time of function call
542 //
543
544 void NetObj::Modify(void)
545 {
546 if (g_bModificationsLocked)
547 return;
548
549 m_bIsModified = TRUE;
550 m_dwTimeStamp = time(NULL);
551
552 // Send event to all connected clients
553 EnumerateClientSessions(BroadcastObjectChange, this);
554 }
555
556
557 //
558 // Modify object from message
559 //
560
561 DWORD NetObj::ModifyFromMessage(CSCPMessage *pRequest, BOOL bAlreadyLocked)
562 {
563 if (!bAlreadyLocked)
564 Lock();
565
566 // Change object's name
567 if (pRequest->IsVariableExist(VID_OBJECT_NAME))
568 pRequest->GetVariableStr(VID_OBJECT_NAME, m_szName, MAX_OBJECT_NAME);
569
570 // Change object's image (icon)
571 if (pRequest->IsVariableExist(VID_IMAGE_ID))
572 m_dwImageId = pRequest->GetVariableLong(VID_IMAGE_ID);
573
574 // Change object's ACL
575 if (pRequest->IsVariableExist(VID_ACL_SIZE))
576 {
577 DWORD i, dwNumElements;
578
579 dwNumElements = pRequest->GetVariableLong(VID_ACL_SIZE);
580 m_bInheritAccessRights = pRequest->GetVariableShort(VID_INHERIT_RIGHTS);
581 m_pAccessList->DeleteAll();
582 for(i = 0; i < dwNumElements; i++)
583 m_pAccessList->AddElement(pRequest->GetVariableLong(VID_ACL_USER_BASE + i),
584 pRequest->GetVariableLong(VID_ACL_RIGHTS_BASE +i));
585 }
586
587 Unlock();
588 Modify();
589
590 return RCC_SUCCESS;
591 }
592
593
594 //
595 // Get rights to object for specific user
596 //
597
598 DWORD NetObj::GetUserRights(DWORD dwUserId)
599 {
600 DWORD dwRights;
601
602 // Admin always has all rights to any object
603 if (dwUserId == 0)
604 return 0xFFFFFFFF;
605
606 Lock();
607
608 // Check if have direct right assignment
609 if (!m_pAccessList->GetUserRights(dwUserId, &dwRights))
610 {
611 // We don't. If this object inherit rights from parents, get them
612 if (m_bInheritAccessRights)
613 {
614 DWORD i;
615
616 for(i = 0, dwRights = 0; i < m_dwParentCount; i++)
617 dwRights |= m_pParentList[i]->GetUserRights(dwUserId);
618 }
619 }
620
621 Unlock();
622 return dwRights;
623 }
624
625
626 //
627 // Check if given user has specific rights on this object
628 //
629
630 BOOL NetObj::CheckAccessRights(DWORD dwUserId, DWORD dwRequiredRights)
631 {
632 DWORD dwRights = GetUserRights(dwUserId);
633 return (dwRights & dwRequiredRights) == dwRequiredRights;
634 }
635
636
637 //
638 // Drop all user privileges on current object
639 //
640
641 void NetObj::DropUserAccess(DWORD dwUserId)
642 {
643 if (m_pAccessList->DeleteElement(dwUserId))
644 Modify();
645 }
646
647
648 //
649 // Set object's management status
650 //
651
652 void NetObj::SetMgmtStatus(BOOL bIsManaged)
653 {
654 DWORD i;
655 int iOldStatus;
656
657 Lock();
658
659 if ((bIsManaged && (m_iStatus != STATUS_UNMANAGED)) ||
660 ((!bIsManaged) && (m_iStatus == STATUS_UNMANAGED)))
661 {
662 Unlock();
663 return; // Status is already correct
664 }
665
666 iOldStatus = m_iStatus;
667 m_iStatus = (bIsManaged ? STATUS_UNKNOWN : STATUS_UNMANAGED);
668
669 // Generate event if current object is a node
670 if (Type() == OBJECT_NODE)
671 PostEvent(bIsManaged ? EVENT_NODE_UNKNOWN : EVENT_NODE_UNMANAGED, m_dwId, "d", iOldStatus);
672
673 // Change status for child objects also
674 for(i = 0; i < m_dwChildCount; i++)
675 m_pChildList[i]->SetMgmtStatus(bIsManaged);
676
677 // Cause parent object(s) to recalculate it's status
678 for(i = 0; i < m_dwParentCount; i++)
679 m_pParentList[i]->CalculateCompoundStatus();
680
681 Modify();
682 Unlock();
683 }
684
685
686 //
687 // Check if given object is an our child (possibly indirect, i.e child of child)
688 //
689
690 BOOL NetObj::IsChild(DWORD dwObjectId)
691 {
692 DWORD i;
693 BOOL bResult = FALSE;
694
695 Lock();
696
697 // Check for our own ID
698 if (m_dwId == dwObjectId)
699 bResult = TRUE;
700
701 // First, walk through our own child list
702 if (!bResult)
703 {
704 for(i = 0; i < m_dwChildCount; i++)
705 if (m_pChildList[i]->Id() == dwObjectId)
706 {
707 bResult = TRUE;
708 break;
709 }
710 }
711
712 // If given object is not in child list, check if it is indirect child
713 if (!bResult)
714 {
715 for(i = 0; i < m_dwChildCount; i++)
716 if (m_pChildList[i]->IsChild(dwObjectId))
717 {
718 bResult = TRUE;
719 break;
720 }
721 }
722
723 Unlock();
724 return bResult;
725 }
726
727
728 //
729 // Send message to client, who requests poll, if any
730 // This method is used by Node and Interface class objects
731 //
732
733 void NetObj::SendPollerMsg(DWORD dwRqId, TCHAR *pszFormat, ...)
734 {
735 if (m_pPollRequestor != NULL)
736 {
737 va_list args;
738 TCHAR szBuffer[1024];
739
740 va_start(args, pszFormat);
741 _vsntprintf(szBuffer, 1024, pszFormat, args);
742 va_end(args);
743 m_pPollRequestor->SendPollerMsg(dwRqId, szBuffer);
744 }
745 }
746
747
748 //
749 // Add child node objects (direct and indirect childs) to list
750 //
751
752 void NetObj::AddChildNodesToList(DWORD *pdwNumNodes, Node ***pppNodeList, DWORD dwUserId)
753 {
754 DWORD i, j;
755
756 Lock();
757
758 // Walk through our own child list
759 for(i = 0; i < m_dwChildCount; i++)
760 {
761 if (m_pChildList[i]->Type() == OBJECT_NODE)
762 {
763 // Check if this node already in the list
764 for(j = 0; j < *pdwNumNodes; j++)
765 if ((*pppNodeList)[j]->Id() == m_pChildList[i]->Id())
766 break;
767 if (j == *pdwNumNodes)
768 {
769 m_pChildList[i]->IncRefCount();
770 *pppNodeList = (Node **)realloc(*pppNodeList, sizeof(Node *) * (*pdwNumNodes + 1));
771 (*pppNodeList)[*pdwNumNodes] = (Node *)m_pChildList[i];
772 (*pdwNumNodes)++;
773 }
774 }
775 else
776 {
777 if (m_pChildList[i]->CheckAccessRights(dwUserId, OBJECT_ACCESS_READ))
778 m_pChildList[i]->AddChildNodesToList(pdwNumNodes, pppNodeList, dwUserId);
779 }
780 }
781
782 Unlock();
783 }