blob: 7fd025032089119b0489d6106d40461e0a448f6d [file] [log] [blame]
Scott Baker2c1c4822019-10-16 11:02:41 -07001/*
2 * Copyright 2018-present Open Networking Foundation
3
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7
8 * http://www.apache.org/licenses/LICENSE-2.0
9
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package model
18
19// TODO: proper error handling
20// TODO: proper logging
21
22import (
23 "context"
24 "fmt"
25 "github.com/golang/protobuf/proto"
Scott Bakere73f91e2019-10-17 12:58:11 -070026 "github.com/opencord/voltha-lib-go/pkg/log"
Scott Baker2c1c4822019-10-16 11:02:41 -070027 "reflect"
28 "strings"
29 "sync"
30 "time"
31)
32
33// When a branch has no transaction id, everything gets stored in NONE
34const (
35 NONE string = "none"
36)
37
38// Node interface is an abstraction of the node data structure
39type Node interface {
40 MakeLatest(branch *Branch, revision Revision, changeAnnouncement []ChangeTuple)
41
42 // CRUD functions
43 Add(ctx context.Context, path string, data interface{}, txid string, makeBranch MakeBranchFunction) Revision
44 Get(ctx context.Context, path string, hash string, depth int, deep bool, txid string) interface{}
45 List(ctx context.Context, path string, hash string, depth int, deep bool, txid string) interface{}
46 Update(ctx context.Context, path string, data interface{}, strict bool, txid string, makeBranch MakeBranchFunction) Revision
47 Remove(ctx context.Context, path string, txid string, makeBranch MakeBranchFunction) Revision
48 CreateProxy(ctx context.Context, path string, exclusive bool) *Proxy
49
50 GetProxy() *Proxy
51
52 MakeBranch(txid string) *Branch
53 DeleteBranch(txid string)
54 MergeBranch(txid string, dryRun bool) (Revision, error)
55
56 MakeTxBranch() string
57 DeleteTxBranch(txid string)
58 FoldTxBranch(txid string)
59}
60
61type node struct {
62 mutex sync.RWMutex
63 Root *root
64 Type interface{}
65 Branches map[string]*Branch
66 Tags map[string]Revision
67 Proxy *Proxy
68 EventBus *EventBus
69 AutoPrune bool
70}
71
72// ChangeTuple holds details of modifications made to a revision
73type ChangeTuple struct {
74 Type CallbackType
75 PreviousData interface{}
76 LatestData interface{}
77}
78
79// NewNode creates a new instance of the node data structure
80func NewNode(root *root, initialData interface{}, autoPrune bool, txid string) *node {
81 n := &node{}
82
83 n.Root = root
84 n.Branches = make(map[string]*Branch)
85 n.Tags = make(map[string]Revision)
86 n.Proxy = nil
87 n.EventBus = nil
88 n.AutoPrune = autoPrune
89
90 if IsProtoMessage(initialData) {
91 n.Type = reflect.ValueOf(initialData).Interface()
92 dataCopy := proto.Clone(initialData.(proto.Message))
93 n.initialize(dataCopy, txid)
94 } else if reflect.ValueOf(initialData).IsValid() {
95 // FIXME: this block does not reflect the original implementation
96 // it should be checking if the provided initial_data is already a type!??!
97 // it should be checked before IsProtoMessage
98 n.Type = reflect.ValueOf(initialData).Interface()
99 } else {
100 // not implemented error
101 log.Errorf("cannot process initial data - %+v", initialData)
102 }
103
104 return n
105}
106
107// MakeNode creates a new node in the tree
108func (n *node) MakeNode(data interface{}, txid string) *node {
109 return NewNode(n.Root, data, true, txid)
110}
111
112// MakeRevision create a new revision of the node in the tree
113func (n *node) MakeRevision(branch *Branch, data interface{}, children map[string][]Revision) Revision {
114 return n.GetRoot().MakeRevision(branch, data, children)
115}
116
117// makeLatest will mark the revision of a node as being the latest
118func (n *node) makeLatest(branch *Branch, revision Revision, changeAnnouncement []ChangeTuple) {
119 // Keep a reference to the current revision
120 var previous string
121 if branch.GetLatest() != nil {
122 previous = branch.GetLatest().GetHash()
123 }
124
125 branch.AddRevision(revision)
126
127 // If anything is new, then set the revision as the latest
128 if branch.GetLatest() == nil || revision.GetHash() != branch.GetLatest().GetHash() {
129 if revision.GetName() != "" {
130 log.Debugw("saving-latest-data", log.Fields{"hash": revision.GetHash(), "data": revision.GetData()})
131 // Tag a timestamp to that revision
132 revision.SetLastUpdate()
133 GetRevCache().Set(revision.GetName(), revision)
134 }
135 branch.SetLatest(revision)
136 }
137
138 // Delete the previous revision if anything has changed
139 if previous != "" && previous != branch.GetLatest().GetHash() {
140 branch.DeleteRevision(previous)
141 }
142
143 if changeAnnouncement != nil && branch.Txid == "" {
144 if n.Proxy != nil {
145 for _, change := range changeAnnouncement {
146 log.Debugw("adding-callback",
147 log.Fields{
148 "callbacks": n.GetProxy().getCallbacks(change.Type),
149 "type": change.Type,
150 "previousData": change.PreviousData,
151 "latestData": change.LatestData,
152 })
153 n.Root.AddCallback(
154 n.GetProxy().InvokeCallbacks,
155 change.Type,
156 true,
157 change.PreviousData,
158 change.LatestData)
159 }
160 }
161 }
162}
163
164// Latest returns the latest revision of node with or without the transaction id
165func (n *node) Latest(txid ...string) Revision {
166 var branch *Branch
167
168 if len(txid) > 0 && txid[0] != "" {
169 if branch = n.GetBranch(txid[0]); branch != nil {
170 return branch.GetLatest()
171 }
172 } else if branch = n.GetBranch(NONE); branch != nil {
173 return branch.GetLatest()
174 }
175 return nil
176}
177
178// initialize prepares the content of a node along with its possible ramifications
179func (n *node) initialize(data interface{}, txid string) {
180 children := make(map[string][]Revision)
181 for fieldName, field := range ChildrenFields(n.Type) {
182 _, fieldValue := GetAttributeValue(data, fieldName, 0)
183
184 if fieldValue.IsValid() {
185 if field.IsContainer {
186 if field.Key != "" {
187 for i := 0; i < fieldValue.Len(); i++ {
188 v := fieldValue.Index(i)
189
190 if rev := n.MakeNode(v.Interface(), txid).Latest(txid); rev != nil {
191 children[fieldName] = append(children[fieldName], rev)
192 }
193
194 // TODO: The following logic was ported from v1.0. Need to verify if it is required
195 //var keysSeen []string
196 //_, key := GetAttributeValue(v.Interface(), field.Key, 0)
197 //for _, k := range keysSeen {
198 // if k == key.String() {
199 // //log.Errorf("duplicate key - %s", k)
200 // }
201 //}
202 //keysSeen = append(keysSeen, key.String())
203 }
204
205 } else {
206 for i := 0; i < fieldValue.Len(); i++ {
207 v := fieldValue.Index(i)
208 if newNodeRev := n.MakeNode(v.Interface(), txid).Latest(); newNodeRev != nil {
209 children[fieldName] = append(children[fieldName], newNodeRev)
210 }
211 }
212 }
213 } else {
214 if newNodeRev := n.MakeNode(fieldValue.Interface(), txid).Latest(); newNodeRev != nil {
215 children[fieldName] = append(children[fieldName], newNodeRev)
216 }
217 }
218 } else {
219 log.Errorf("field is invalid - %+v", fieldValue)
220 }
221 }
222
223 branch := NewBranch(n, "", nil, n.AutoPrune)
224 rev := n.MakeRevision(branch, data, children)
225 n.makeLatest(branch, rev, nil)
226
227 if txid == "" {
228 n.SetBranch(NONE, branch)
229 } else {
230 n.SetBranch(txid, branch)
231 }
232}
233
234// findRevByKey retrieves a specific revision from a node tree
235func (n *node) findRevByKey(revs []Revision, keyName string, value interface{}) (int, Revision) {
236 for i, rev := range revs {
237 dataValue := reflect.ValueOf(rev.GetData())
238 dataStruct := GetAttributeStructure(rev.GetData(), keyName, 0)
239
240 fieldValue := dataValue.Elem().FieldByName(dataStruct.Name)
241
242 a := fmt.Sprintf("%s", fieldValue.Interface())
243 b := fmt.Sprintf("%s", value)
244 if a == b {
245 return i, revs[i]
246 }
247 }
248
249 return -1, nil
250}
251
252// Get retrieves the data from a node tree that resides at the specified path
253func (n *node) List(ctx context.Context, path string, hash string, depth int, deep bool, txid string) interface{} {
254 n.mutex.Lock()
255 defer n.mutex.Unlock()
256
257 log.Debugw("node-list-request", log.Fields{"path": path, "hash": hash, "depth": depth, "deep": deep, "txid": txid})
258 if deep {
259 depth = -1
260 }
261
262 for strings.HasPrefix(path, "/") {
263 path = path[1:]
264 }
265
266 var branch *Branch
267 var rev Revision
268
269 if branch = n.GetBranch(txid); txid == "" || branch == nil {
270 branch = n.GetBranch(NONE)
271 }
272
273 if hash != "" {
274 rev = branch.GetRevision(hash)
275 } else {
276 rev = branch.GetLatest()
277 }
278
279 var result interface{}
280 var prList []interface{}
281 if pr := rev.LoadFromPersistence(ctx, path, txid, nil); pr != nil {
282 for _, revEntry := range pr {
283 prList = append(prList, revEntry.GetData())
284 }
285 result = prList
286 }
287
288 return result
289}
290
291// Get retrieves the data from a node tree that resides at the specified path
292func (n *node) Get(ctx context.Context, path string, hash string, depth int, reconcile bool, txid string) interface{} {
293 n.mutex.Lock()
294 defer n.mutex.Unlock()
295
296 log.Debugw("node-get-request", log.Fields{"path": path, "hash": hash, "depth": depth, "reconcile": reconcile, "txid": txid})
297
298 for strings.HasPrefix(path, "/") {
299 path = path[1:]
300 }
301
302 var branch *Branch
303 var rev Revision
304
305 if branch = n.GetBranch(txid); txid == "" || branch == nil {
306 branch = n.GetBranch(NONE)
307 }
308
309 if hash != "" {
310 rev = branch.GetRevision(hash)
311 } else {
312 rev = branch.GetLatest()
313 }
314
315 var result interface{}
316
317 // If there is no request to reconcile, try to get it from memory
318 if !reconcile {
319 // Try to find an entry matching the path value from one of these sources
320 // 1. Start with the cache which stores revisions by watch names
321 // 2. Then look in the revision tree, especially if it's a sub-path such as /devices/1234/flows
322 // 3. Move on to the KV store if that path cannot be found or if the entry has expired
323 if entry, exists := GetRevCache().Get(path); exists && entry.(Revision) != nil {
324 entryAge := time.Now().Sub(entry.(Revision).GetLastUpdate()).Nanoseconds() / int64(time.Millisecond)
325 if entryAge < DataRefreshPeriod {
326 log.Debugw("using-cache-entry", log.Fields{
327 "path": path,
328 "hash": hash,
329 "age": entryAge,
330 })
331 return proto.Clone(entry.(Revision).GetData().(proto.Message))
332 } else {
333 log.Debugw("cache-entry-expired", log.Fields{"path": path, "hash": hash, "age": entryAge})
334 }
335 } else if result = n.getPath(ctx, rev.GetBranch().GetLatest(), path, depth); result != nil && reflect.ValueOf(result).IsValid() && !reflect.ValueOf(result).IsNil() {
336 log.Debugw("using-rev-tree-entry", log.Fields{"path": path, "hash": hash, "depth": depth, "reconcile": reconcile, "txid": txid})
337 return result
338 } else {
339 log.Debugw("not-using-cache-entry", log.Fields{
340 "path": path,
341 "hash": hash, "depth": depth,
342 "reconcile": reconcile,
343 "txid": txid,
344 })
345 }
346 } else {
347 log.Debugw("reconcile-requested", log.Fields{
348 "path": path,
349 "hash": hash,
350 "reconcile": reconcile,
351 })
352 }
353
354 // If we got to this point, we are either trying to reconcile with the db
355 // or we simply failed at getting information from memory
356 if n.Root.KvStore != nil {
357 if pr := rev.LoadFromPersistence(ctx, path, txid, nil); pr != nil && len(pr) > 0 {
358 // Did we receive a single or multiple revisions?
359 if len(pr) > 1 {
360 var revs []interface{}
361 for _, revEntry := range pr {
362 revs = append(revs, revEntry.GetData())
363 }
364 result = revs
365 } else {
366 result = pr[0].GetData()
367 }
368 }
369 }
370
371 return result
372}
373
374//getPath traverses the specified path and retrieves the data associated to it
375func (n *node) getPath(ctx context.Context, rev Revision, path string, depth int) interface{} {
376 if path == "" {
377 return n.getData(rev, depth)
378 }
379
380 partition := strings.SplitN(path, "/", 2)
381 name := partition[0]
382
383 if len(partition) < 2 {
384 path = ""
385 } else {
386 path = partition[1]
387 }
388
389 names := ChildrenFields(n.Type)
390 field := names[name]
391
392 if field != nil && field.IsContainer {
393 children := make([]Revision, len(rev.GetChildren(name)))
394 copy(children, rev.GetChildren(name))
395
396 if field.Key != "" {
397 if path != "" {
398 partition = strings.SplitN(path, "/", 2)
399 key := partition[0]
400 path = ""
401 keyValue := field.KeyFromStr(key)
402 if _, childRev := n.findRevByKey(children, field.Key, keyValue); childRev == nil {
403 return nil
404 } else {
405 childNode := childRev.GetNode()
406 return childNode.getPath(ctx, childRev, path, depth)
407 }
408 } else {
409 var response []interface{}
410 for _, childRev := range children {
411 childNode := childRev.GetNode()
412 value := childNode.getData(childRev, depth)
413 response = append(response, value)
414 }
415 return response
416 }
417 } else {
418 var response []interface{}
419 if path != "" {
420 // TODO: raise error
421 return response
422 }
423 for _, childRev := range children {
424 childNode := childRev.GetNode()
425 value := childNode.getData(childRev, depth)
426 response = append(response, value)
427 }
428 return response
429 }
430 } else if children := rev.GetChildren(name); children != nil && len(children) > 0 {
431 childRev := children[0]
432 childNode := childRev.GetNode()
433 return childNode.getPath(ctx, childRev, path, depth)
434 }
435
436 return nil
437}
438
439// getData retrieves the data from a node revision
440func (n *node) getData(rev Revision, depth int) interface{} {
441 msg := rev.GetBranch().GetLatest().Get(depth)
442 var modifiedMsg interface{}
443
444 if n.GetProxy() != nil {
445 log.Debugw("invoking-get-callbacks", log.Fields{"data": msg})
446 if modifiedMsg = n.GetProxy().InvokeCallbacks(GET, false, msg); modifiedMsg != nil {
447 msg = modifiedMsg
448 }
449
450 }
451
452 return msg
453}
454
455// Update changes the content of a node at the specified path with the provided data
456func (n *node) Update(ctx context.Context, path string, data interface{}, strict bool, txid string, makeBranch MakeBranchFunction) Revision {
457 n.mutex.Lock()
458 defer n.mutex.Unlock()
459
460 log.Debugw("node-update-request", log.Fields{"path": path, "strict": strict, "txid": txid})
461
462 for strings.HasPrefix(path, "/") {
463 path = path[1:]
464 }
465
466 var branch *Branch
467 if txid == "" {
468 branch = n.GetBranch(NONE)
469 } else if branch = n.GetBranch(txid); branch == nil {
470 branch = makeBranch(n)
471 }
472
473 if branch.GetLatest() != nil {
474 log.Debugf("Branch data : %+v, Passed data: %+v", branch.GetLatest().GetData(), data)
475 }
476 if path == "" {
477 return n.doUpdate(ctx, branch, data, strict)
478 }
479
480 rev := branch.GetLatest()
481
482 partition := strings.SplitN(path, "/", 2)
483 name := partition[0]
484
485 if len(partition) < 2 {
486 path = ""
487 } else {
488 path = partition[1]
489 }
490
491 field := ChildrenFields(n.Type)[name]
492 var children []Revision
493
494 if field == nil {
495 return n.doUpdate(ctx, branch, data, strict)
496 }
497
498 if field.IsContainer {
499 if path == "" {
500 log.Errorf("cannot update a list")
501 } else if field.Key != "" {
502 partition := strings.SplitN(path, "/", 2)
503 key := partition[0]
504 if len(partition) < 2 {
505 path = ""
506 } else {
507 path = partition[1]
508 }
509 keyValue := field.KeyFromStr(key)
510
511 children = make([]Revision, len(rev.GetChildren(name)))
512 copy(children, rev.GetChildren(name))
513
514 idx, childRev := n.findRevByKey(children, field.Key, keyValue)
515
516 if childRev == nil {
517 log.Debugw("child-revision-is-nil", log.Fields{"key": keyValue})
518 return branch.GetLatest()
519 }
520
521 childNode := childRev.GetNode()
522
523 // Save proxy in child node to ensure callbacks are called later on
524 // only assign in cases of non sub-folder proxies, i.e. "/"
525 if childNode.Proxy == nil && n.Proxy != nil && n.GetProxy().getFullPath() == "" {
526 childNode.Proxy = n.Proxy
527 }
528
529 newChildRev := childNode.Update(ctx, path, data, strict, txid, makeBranch)
530
531 if newChildRev.GetHash() == childRev.GetHash() {
532 if newChildRev != childRev {
533 log.Debug("clear-hash - %s %+v", newChildRev.GetHash(), newChildRev)
534 newChildRev.ClearHash()
535 }
536 log.Debugw("child-revisions-have-matching-hash", log.Fields{"hash": childRev.GetHash(), "key": keyValue})
537 return branch.GetLatest()
538 }
539
540 _, newKey := GetAttributeValue(newChildRev.GetData(), field.Key, 0)
541
542 _newKeyType := fmt.Sprintf("%s", newKey)
543 _keyValueType := fmt.Sprintf("%s", keyValue)
544
545 if _newKeyType != _keyValueType {
546 log.Errorf("cannot change key field")
547 }
548
549 // Prefix the hash value with the data type (e.g. devices, logical_devices, adapters)
550 newChildRev.SetName(name + "/" + _keyValueType)
551
552 branch.LatestLock.Lock()
553 defer branch.LatestLock.Unlock()
554
555 if idx >= 0 {
556 children[idx] = newChildRev
557 } else {
558 children = append(children, newChildRev)
559 }
560
561 updatedRev := rev.UpdateChildren(ctx, name, children, branch)
562
563 n.makeLatest(branch, updatedRev, nil)
564 updatedRev.ChildDrop(name, childRev.GetHash())
565
566 return newChildRev
567
568 } else {
569 log.Errorf("cannot index into container with no keys")
570 }
571 } else {
572 childRev := rev.GetChildren(name)[0]
573 childNode := childRev.GetNode()
574 newChildRev := childNode.Update(ctx, path, data, strict, txid, makeBranch)
575
576 branch.LatestLock.Lock()
577 defer branch.LatestLock.Unlock()
578
579 updatedRev := rev.UpdateChildren(ctx, name, []Revision{newChildRev}, branch)
580 n.makeLatest(branch, updatedRev, nil)
581
582 updatedRev.ChildDrop(name, childRev.GetHash())
583
584 return newChildRev
585 }
586
587 return nil
588}
589
590func (n *node) doUpdate(ctx context.Context, branch *Branch, data interface{}, strict bool) Revision {
591 log.Debugw("comparing-types", log.Fields{"expected": reflect.ValueOf(n.Type).Type(), "actual": reflect.TypeOf(data)})
592
593 if reflect.TypeOf(data) != reflect.ValueOf(n.Type).Type() {
594 // TODO raise error
595 log.Errorw("types-do-not-match: %+v", log.Fields{"actual": reflect.TypeOf(data), "expected": n.Type})
596 return nil
597 }
598
599 // TODO: validate that this actually works
600 //if n.hasChildren(data) {
601 // return nil
602 //}
603
604 if n.GetProxy() != nil {
605 log.Debug("invoking proxy PRE_UPDATE Callbacks")
606 n.GetProxy().InvokeCallbacks(PRE_UPDATE, false, branch.GetLatest(), data)
607 }
608
609 if branch.GetLatest().GetData().(proto.Message).String() != data.(proto.Message).String() {
610 if strict {
611 // TODO: checkAccessViolations(data, Branch.GetLatest.data)
612 log.Debugf("checking access violations")
613 }
614
615 rev := branch.GetLatest().UpdateData(ctx, data, branch)
616 changes := []ChangeTuple{{POST_UPDATE, branch.GetLatest().GetData(), rev.GetData()}}
617 n.makeLatest(branch, rev, changes)
618
619 return rev
620 }
621 return branch.GetLatest()
622}
623
624// Add inserts a new node at the specified path with the provided data
625func (n *node) Add(ctx context.Context, path string, data interface{}, txid string, makeBranch MakeBranchFunction) Revision {
626 n.mutex.Lock()
627 defer n.mutex.Unlock()
628
629 log.Debugw("node-add-request", log.Fields{"path": path, "txid": txid})
630
631 for strings.HasPrefix(path, "/") {
632 path = path[1:]
633 }
634 if path == "" {
635 // TODO raise error
636 log.Errorf("cannot add for non-container mode")
637 return nil
638 }
639
640 var branch *Branch
641 if txid == "" {
642 branch = n.GetBranch(NONE)
643 } else if branch = n.GetBranch(txid); branch == nil {
644 branch = makeBranch(n)
645 }
646
647 rev := branch.GetLatest()
648
649 partition := strings.SplitN(path, "/", 2)
650 name := partition[0]
651
652 if len(partition) < 2 {
653 path = ""
654 } else {
655 path = partition[1]
656 }
657
658 field := ChildrenFields(n.Type)[name]
659
660 var children []Revision
661
662 if field.IsContainer {
663 if path == "" {
664 if field.Key != "" {
665 if n.GetProxy() != nil {
666 log.Debug("invoking proxy PRE_ADD Callbacks")
667 n.GetProxy().InvokeCallbacks(PRE_ADD, false, data)
668 }
669
670 children = make([]Revision, len(rev.GetChildren(name)))
671 copy(children, rev.GetChildren(name))
672
673 _, key := GetAttributeValue(data, field.Key, 0)
674
675 if _, exists := n.findRevByKey(children, field.Key, key.String()); exists != nil {
676 // TODO raise error
677 log.Warnw("duplicate-key-found", log.Fields{"key": key.String()})
678 return exists
679 }
680 childRev := n.MakeNode(data, "").Latest()
681
682 // Prefix the hash with the data type (e.g. devices, logical_devices, adapters)
683 childRev.SetName(name + "/" + key.String())
684
685 branch.LatestLock.Lock()
686 defer branch.LatestLock.Unlock()
687
688 children = append(children, childRev)
689
690 updatedRev := rev.UpdateChildren(ctx, name, children, branch)
691 changes := []ChangeTuple{{POST_ADD, nil, childRev.GetData()}}
692 childRev.SetupWatch(childRev.GetName())
693
694 n.makeLatest(branch, updatedRev, changes)
695
696 return childRev
697 }
698 log.Errorf("cannot add to non-keyed container")
699
700 } else if field.Key != "" {
701 partition := strings.SplitN(path, "/", 2)
702 key := partition[0]
703 if len(partition) < 2 {
704 path = ""
705 } else {
706 path = partition[1]
707 }
708 keyValue := field.KeyFromStr(key)
709
710 children = make([]Revision, len(rev.GetChildren(name)))
711 copy(children, rev.GetChildren(name))
712
713 idx, childRev := n.findRevByKey(children, field.Key, keyValue)
714
715 if childRev == nil {
716 return branch.GetLatest()
717 }
718
719 childNode := childRev.GetNode()
720 newChildRev := childNode.Add(ctx, path, data, txid, makeBranch)
721
722 // Prefix the hash with the data type (e.g. devices, logical_devices, adapters)
723 newChildRev.SetName(name + "/" + keyValue.(string))
724
725 branch.LatestLock.Lock()
726 defer branch.LatestLock.Unlock()
727
728 if idx >= 0 {
729 children[idx] = newChildRev
730 } else {
731 children = append(children, newChildRev)
732 }
733
734 updatedRev := rev.UpdateChildren(ctx, name, children, branch)
735 n.makeLatest(branch, updatedRev, nil)
736
737 updatedRev.ChildDrop(name, childRev.GetHash())
738
739 return newChildRev
740 } else {
741 log.Errorf("cannot add to non-keyed container")
742 }
743 } else {
744 log.Errorf("cannot add to non-container field")
745 }
746
747 return nil
748}
749
750// Remove eliminates a node at the specified path
751func (n *node) Remove(ctx context.Context, path string, txid string, makeBranch MakeBranchFunction) Revision {
752 n.mutex.Lock()
753 defer n.mutex.Unlock()
754
755 log.Debugw("node-remove-request", log.Fields{"path": path, "txid": txid, "makeBranch": makeBranch})
756
757 for strings.HasPrefix(path, "/") {
758 path = path[1:]
759 }
760 if path == "" {
761 // TODO raise error
762 log.Errorf("cannot remove for non-container mode")
763 }
764 var branch *Branch
765 if txid == "" {
766 branch = n.GetBranch(NONE)
767 } else if branch = n.GetBranch(txid); branch == nil {
768 branch = makeBranch(n)
769 }
770
771 rev := branch.GetLatest()
772
773 partition := strings.SplitN(path, "/", 2)
774 name := partition[0]
775 if len(partition) < 2 {
776 path = ""
777 } else {
778 path = partition[1]
779 }
780
781 field := ChildrenFields(n.Type)[name]
782 var children []Revision
783 postAnnouncement := []ChangeTuple{}
784
785 if field.IsContainer {
786 if path == "" {
787 log.Errorw("cannot-remove-without-key", log.Fields{"name": name, "key": path})
788 } else if field.Key != "" {
789 partition := strings.SplitN(path, "/", 2)
790 key := partition[0]
791 if len(partition) < 2 {
792 path = ""
793 } else {
794 path = partition[1]
795 }
796
797 keyValue := field.KeyFromStr(key)
798 children = make([]Revision, len(rev.GetChildren(name)))
799 copy(children, rev.GetChildren(name))
800
801 if path != "" {
802 if idx, childRev := n.findRevByKey(children, field.Key, keyValue); childRev != nil {
803 childNode := childRev.GetNode()
804 if childNode.Proxy == nil {
805 childNode.Proxy = n.Proxy
806 }
807 newChildRev := childNode.Remove(ctx, path, txid, makeBranch)
808
809 branch.LatestLock.Lock()
810 defer branch.LatestLock.Unlock()
811
812 if idx >= 0 {
813 children[idx] = newChildRev
814 } else {
815 children = append(children, newChildRev)
816 }
817
818 rev.SetChildren(name, children)
819 branch.GetLatest().Drop(txid, false)
820 n.makeLatest(branch, rev, nil)
821 }
822 return branch.GetLatest()
823 }
824
825 if idx, childRev := n.findRevByKey(children, field.Key, keyValue); childRev != nil && idx >= 0 {
826 if n.GetProxy() != nil {
827 data := childRev.GetData()
828 n.GetProxy().InvokeCallbacks(PRE_REMOVE, false, data)
829 postAnnouncement = append(postAnnouncement, ChangeTuple{POST_REMOVE, data, nil})
830 } else {
831 postAnnouncement = append(postAnnouncement, ChangeTuple{POST_REMOVE, childRev.GetData(), nil})
832 }
833
834 childRev.StorageDrop(txid, true)
835 GetRevCache().Delete(childRev.GetName())
836
837 branch.LatestLock.Lock()
838 defer branch.LatestLock.Unlock()
839
840 children = append(children[:idx], children[idx+1:]...)
841 rev.SetChildren(name, children)
842
843 branch.GetLatest().Drop(txid, false)
844 n.makeLatest(branch, rev, postAnnouncement)
845
846 return rev
847 } else {
848 log.Errorw("failed-to-find-revision", log.Fields{"name": name, "key": keyValue.(string)})
849 }
850 }
851 log.Errorw("cannot-add-to-non-keyed-container", log.Fields{"name": name, "path": path, "fieldKey": field.Key})
852
853 } else {
854 log.Errorw("cannot-add-to-non-container-field", log.Fields{"name": name, "path": path})
855 }
856
857 return nil
858}
859
860// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Branching ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
861
862// MakeBranchFunction is a type for function references intented to create a branch
863type MakeBranchFunction func(*node) *Branch
864
865// MakeBranch creates a new branch for the provided transaction id
866func (n *node) MakeBranch(txid string) *Branch {
867 branchPoint := n.GetBranch(NONE).GetLatest()
868 branch := NewBranch(n, txid, branchPoint, true)
869 n.SetBranch(txid, branch)
870 return branch
871}
872
873// DeleteBranch removes a branch with the specified id
874func (n *node) DeleteBranch(txid string) {
875 delete(n.Branches, txid)
876}
877
878func (n *node) mergeChild(txid string, dryRun bool) func(Revision) Revision {
879 f := func(rev Revision) Revision {
880 childBranch := rev.GetBranch()
881
882 if childBranch.Txid == txid {
883 rev, _ = childBranch.Node.MergeBranch(txid, dryRun)
884 }
885
886 return rev
887 }
888 return f
889}
890
891// MergeBranch will integrate the contents of a transaction branch within the latest branch of a given node
892func (n *node) MergeBranch(txid string, dryRun bool) (Revision, error) {
893 srcBranch := n.GetBranch(txid)
894 dstBranch := n.GetBranch(NONE)
895
896 forkRev := srcBranch.Origin
897 srcRev := srcBranch.GetLatest()
898 dstRev := dstBranch.GetLatest()
899
900 rev, changes := Merge3Way(forkRev, srcRev, dstRev, n.mergeChild(txid, dryRun), dryRun)
901
902 if !dryRun {
903 if rev != nil {
904 rev.SetName(dstRev.GetName())
905 n.makeLatest(dstBranch, rev, changes)
906 }
907 n.DeleteBranch(txid)
908 }
909
910 // TODO: return proper error when one occurs
911 return rev, nil
912}
913
914// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Diff utility ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
915
916//func (n *node) diff(hash1, hash2, txid string) {
917// branch := n.Branches[txid]
918// rev1 := branch.GetHash(hash1)
919// rev2 := branch.GetHash(hash2)
920//
921// if rev1.GetHash() == rev2.GetHash() {
922// // empty patch
923// } else {
924// // translate data to json and generate patch
925// patch, err := jsonpatch.MakePatch(rev1.GetData(), rev2.GetData())
926// patch.
927// }
928//}
929
930// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tag utility ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
931
932// TODO: is tag mgmt used in the python implementation? Need to validate
933
934// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Internals ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
935
936func (n *node) hasChildren(data interface{}) bool {
937 for fieldName, field := range ChildrenFields(n.Type) {
938 _, fieldValue := GetAttributeValue(data, fieldName, 0)
939
940 if (field.IsContainer && fieldValue.Len() > 0) || !fieldValue.IsNil() {
941 log.Error("cannot update external children")
942 return true
943 }
944 }
945
946 return false
947}
948
949// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ node Proxy ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
950
951// CreateProxy returns a reference to a sub-tree of the data model
952func (n *node) CreateProxy(ctx context.Context, path string, exclusive bool) *Proxy {
953 return n.createProxy(ctx, path, path, n, exclusive)
954}
955
956func (n *node) createProxy(ctx context.Context, path string, fullPath string, parentNode *node, exclusive bool) *Proxy {
957 log.Debugw("node-create-proxy", log.Fields{
958 "node-type": reflect.ValueOf(n.Type).Type(),
959 "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
960 "path": path,
961 "fullPath": fullPath,
962 })
963
964 for strings.HasPrefix(path, "/") {
965 path = path[1:]
966 }
967 if path == "" {
968 return n.makeProxy(path, fullPath, parentNode, exclusive)
969 }
970
971 rev := n.GetBranch(NONE).GetLatest()
972 partition := strings.SplitN(path, "/", 2)
973 name := partition[0]
974 var nodeType interface{}
975 if len(partition) < 2 {
976 path = ""
977 nodeType = n.Type
978 } else {
979 path = partition[1]
980 nodeType = parentNode.Type
981 }
982
983 field := ChildrenFields(nodeType)[name]
984
985 if field != nil {
986 if field.IsContainer {
987 log.Debugw("container-field", log.Fields{
988 "node-type": reflect.ValueOf(n.Type).Type(),
989 "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
990 "path": path,
991 "name": name,
992 })
993 if path == "" {
994 log.Debugw("folder-proxy", log.Fields{
995 "node-type": reflect.ValueOf(n.Type).Type(),
996 "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
997 "fullPath": fullPath,
998 "name": name,
999 })
1000 newNode := n.MakeNode(reflect.New(field.ClassType.Elem()).Interface(), "")
1001 return newNode.makeProxy(path, fullPath, parentNode, exclusive)
1002 } else if field.Key != "" {
1003 log.Debugw("key-proxy", log.Fields{
1004 "node-type": reflect.ValueOf(n.Type).Type(),
1005 "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
1006 "fullPath": fullPath,
1007 "name": name,
1008 })
1009 partition := strings.SplitN(path, "/", 2)
1010 key := partition[0]
1011 if len(partition) < 2 {
1012 path = ""
1013 } else {
1014 path = partition[1]
1015 }
1016 keyValue := field.KeyFromStr(key)
1017 var children []Revision
1018 children = make([]Revision, len(rev.GetChildren(name)))
1019 copy(children, rev.GetChildren(name))
1020
1021 var childRev Revision
1022 if _, childRev = n.findRevByKey(children, field.Key, keyValue); childRev != nil {
1023 log.Debugw("found-revision-matching-key-in-memory", log.Fields{
1024 "node-type": reflect.ValueOf(n.Type).Type(),
1025 "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
1026 "fullPath": fullPath,
1027 "name": name,
1028 })
1029 } else if revs := n.GetBranch(NONE).GetLatest().LoadFromPersistence(ctx, fullPath, "", nil); revs != nil && len(revs) > 0 {
1030 log.Debugw("found-revision-matching-key-in-db", log.Fields{
1031 "node-type": reflect.ValueOf(n.Type).Type(),
1032 "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
1033 "fullPath": fullPath,
1034 "name": name,
1035 })
1036 childRev = revs[0]
1037 } else {
1038 log.Debugw("no-revision-matching-key", log.Fields{
1039 "node-type": reflect.ValueOf(n.Type).Type(),
1040 "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
1041 "fullPath": fullPath,
1042 "name": name,
1043 })
1044 }
1045 if childRev != nil {
1046 childNode := childRev.GetNode()
1047 return childNode.createProxy(ctx, path, fullPath, n, exclusive)
1048 }
1049 } else {
1050 log.Errorw("cannot-access-index-of-empty-container", log.Fields{
1051 "node-type": reflect.ValueOf(n.Type).Type(),
1052 "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
1053 "path": path,
1054 "name": name,
1055 })
1056 }
1057 } else {
1058 log.Debugw("non-container-field", log.Fields{
1059 "node-type": reflect.ValueOf(n.Type).Type(),
1060 "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
1061 "path": path,
1062 "name": name,
1063 })
1064 childRev := rev.GetChildren(name)[0]
1065 childNode := childRev.GetNode()
1066 return childNode.createProxy(ctx, path, fullPath, n, exclusive)
1067 }
1068 } else {
1069 log.Debugw("field-object-is-nil", log.Fields{
1070 "node-type": reflect.ValueOf(n.Type).Type(),
1071 "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
1072 "fullPath": fullPath,
1073 "name": name,
1074 })
1075 }
1076
1077 log.Warnw("cannot-create-proxy", log.Fields{
1078 "node-type": reflect.ValueOf(n.Type).Type(),
1079 "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
1080 "path": path,
1081 "fullPath": fullPath,
1082 "latest-rev": rev.GetHash(),
1083 })
1084 return nil
1085}
1086
1087func (n *node) makeProxy(path string, fullPath string, parentNode *node, exclusive bool) *Proxy {
1088 log.Debugw("node-make-proxy", log.Fields{
1089 "node-type": reflect.ValueOf(n.Type).Type(),
1090 "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
1091 "path": path,
1092 "fullPath": fullPath,
1093 })
1094
1095 r := &root{
1096 node: n,
1097 Callbacks: n.Root.GetCallbacks(),
1098 NotificationCallbacks: n.Root.GetNotificationCallbacks(),
1099 DirtyNodes: n.Root.DirtyNodes,
1100 KvStore: n.Root.KvStore,
1101 Loading: n.Root.Loading,
1102 RevisionClass: n.Root.RevisionClass,
1103 }
1104
1105 if n.Proxy == nil {
1106 log.Debugw("constructing-new-proxy", log.Fields{
1107 "node-type": reflect.ValueOf(n.Type).Type(),
1108 "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
1109 "path": path,
1110 "fullPath": fullPath,
1111 })
1112 n.Proxy = NewProxy(r, n, parentNode, path, fullPath, exclusive)
1113 } else {
1114 log.Debugw("node-has-existing-proxy", log.Fields{
1115 "node-type": reflect.ValueOf(n.GetProxy().Node.Type).Type(),
1116 "parent-node-type": reflect.ValueOf(n.GetProxy().ParentNode.Type).Type(),
1117 "path": n.GetProxy().Path,
1118 "fullPath": n.GetProxy().FullPath,
1119 })
1120 if n.GetProxy().Exclusive {
1121 log.Error("node is already owned exclusively")
1122 }
1123 }
1124
1125 return n.Proxy
1126}
1127
1128func (n *node) makeEventBus() *EventBus {
1129 if n.EventBus == nil {
1130 n.EventBus = NewEventBus()
1131 }
1132 return n.EventBus
1133}
1134
1135func (n *node) SetProxy(proxy *Proxy) {
1136 n.Proxy = proxy
1137}
1138
1139func (n *node) GetProxy() *Proxy {
1140 return n.Proxy
1141}
1142
1143func (n *node) GetBranch(key string) *Branch {
1144 if n.Branches != nil {
1145 if branch, exists := n.Branches[key]; exists {
1146 return branch
1147 }
1148 }
1149 return nil
1150}
1151
1152func (n *node) SetBranch(key string, branch *Branch) {
1153 n.Branches[key] = branch
1154}
1155
1156func (n *node) GetRoot() *root {
1157 return n.Root
1158}
1159func (n *node) SetRoot(root *root) {
1160 n.Root = root
1161}