001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.configuration2.tree; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027import java.util.concurrent.atomic.AtomicReference; 028 029import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; 030import org.apache.commons.lang3.mutable.Mutable; 031import org.apache.commons.lang3.mutable.MutableObject; 032 033/** 034 * <p> 035 * A specialized node model implementation which operates on 036 * {@link ImmutableNode} structures. 037 * </p> 038 * <p> 039 * This {@code NodeModel} implementation keeps all its data as a tree of 040 * {@link ImmutableNode} objects in memory. The managed structure can be 041 * manipulated in a thread-safe, non-blocking way. This is achieved by using 042 * atomic variables: The root of the tree is stored in an atomic reference 043 * variable. Each update operation causes a new structure to be constructed 044 * (which reuses as much from the original structure as possible). The old root 045 * node is then replaced by the new one using an atomic compare-and-set 046 * operation. If this fails, the manipulation has to be done anew on the updated 047 * structure. 048 * </p> 049 * 050 * @since 2.0 051 */ 052public class InMemoryNodeModel implements NodeModel<ImmutableNode> 053{ 054 /** 055 * A dummy node handler instance used in operations which require only a 056 * limited functionality. 057 */ 058 private static final NodeHandler<ImmutableNode> DUMMY_HANDLER = 059 new TreeData(null, 060 Collections.<ImmutableNode, ImmutableNode> emptyMap(), 061 Collections.<ImmutableNode, ImmutableNode> emptyMap(), null, new ReferenceTracker()); 062 063 /** Stores information about the current nodes structure. */ 064 private final AtomicReference<TreeData> structure; 065 066 /** 067 * Creates a new instance of {@code InMemoryNodeModel} which is initialized 068 * with an empty root node. 069 */ 070 public InMemoryNodeModel() 071 { 072 this(null); 073 } 074 075 /** 076 * Creates a new instance of {@code InMemoryNodeModel} and initializes it 077 * from the given root node. If the passed in node is <b>null</b>, a new, 078 * empty root node is created. 079 * 080 * @param root the new root node for this model 081 */ 082 public InMemoryNodeModel(final ImmutableNode root) 083 { 084 structure = 085 new AtomicReference<>( 086 createTreeData(initialRootNode(root), null)); 087 } 088 089 /** 090 * Returns the root node of this mode. Note: This method should be used with 091 * care. The model may be updated concurrently which causes the root node to 092 * be replaced. If the root node is to be processed further (e.g. by 093 * executing queries on it), the model should be asked for its 094 * {@code NodeHandler}, and the root node should be obtained from there. The 095 * connection between a node handler and its root node remain constant 096 * because an update of the model causes the whole node handler to be 097 * replaced. 098 * 099 * @return the current root node 100 */ 101 public ImmutableNode getRootNode() 102 { 103 return getTreeData().getRootNode(); 104 } 105 106 /** 107 * {@inheritDoc} {@code InMemoryNodeModel} implements the 108 * {@code NodeHandler} interface itself. So this implementation just returns 109 * the <strong>this</strong> reference. 110 */ 111 @Override 112 public NodeHandler<ImmutableNode> getNodeHandler() 113 { 114 return getReferenceNodeHandler(); 115 } 116 117 @Override 118 public void addProperty(final String key, final Iterable<?> values, 119 final NodeKeyResolver<ImmutableNode> resolver) 120 { 121 addProperty(key, null, values, resolver); 122 } 123 124 /** 125 * Adds new property values using a tracked node as root node. This method 126 * works like the normal {@code addProperty()} method, but the origin of the 127 * operation (also for the interpretation of the passed in key) is a tracked 128 * node identified by the passed in {@code NodeSelector}. The selector can 129 * be <b>null</b>, then the root node is assumed. 130 * 131 * @param key the key 132 * @param selector the {@code NodeSelector} defining the root node (or 133 * <b>null</b>) 134 * @param values the values to be added 135 * @param resolver the {@code NodeKeyResolver} 136 * @throws ConfigurationRuntimeException if the selector cannot be resolved 137 */ 138 public void addProperty(final String key, final NodeSelector selector, 139 final Iterable<?> values, 140 final NodeKeyResolver<ImmutableNode> resolver) 141 { 142 if (valuesNotEmpty(values)) 143 { 144 updateModel(new TransactionInitializer() 145 { 146 @Override 147 public boolean initTransaction(final ModelTransaction tx) 148 { 149 initializeAddTransaction(tx, key, values, resolver); 150 return true; 151 } 152 }, selector, resolver); 153 } 154 } 155 156 @Override 157 public void addNodes(final String key, final Collection<? extends ImmutableNode> nodes, 158 final NodeKeyResolver<ImmutableNode> resolver) 159 { 160 addNodes(key, null, nodes, resolver); 161 } 162 163 /** 164 * Adds new nodes using a tracked node as root node. This method works like 165 * the normal {@code addNodes()} method, but the origin of the operation 166 * (also for the interpretation of the passed in key) is a tracked node 167 * identified by the passed in {@code NodeSelector}. The selector can be 168 * <b>null</b>, then the root node is assumed. 169 * 170 * @param key the key 171 * @param selector the {@code NodeSelector} defining the root node (or 172 * <b>null</b>) 173 * @param nodes the collection of new nodes to be added 174 * @param resolver the {@code NodeKeyResolver} 175 * @throws ConfigurationRuntimeException if the selector cannot be resolved 176 */ 177 public void addNodes(final String key, final NodeSelector selector, 178 final Collection<? extends ImmutableNode> nodes, 179 final NodeKeyResolver<ImmutableNode> resolver) 180 { 181 if (nodes != null && !nodes.isEmpty()) 182 { 183 updateModel(new TransactionInitializer() 184 { 185 @Override 186 public boolean initTransaction(final ModelTransaction tx) 187 { 188 final List<QueryResult<ImmutableNode>> results = 189 resolver.resolveKey(tx.getQueryRoot(), key, 190 tx.getCurrentData()); 191 if (results.size() == 1) 192 { 193 if (results.get(0).isAttributeResult()) 194 { 195 throw attributeKeyException(key); 196 } 197 tx.addAddNodesOperation(results.get(0).getNode(), nodes); 198 } 199 else 200 { 201 final NodeAddData<ImmutableNode> addData = 202 resolver.resolveAddKey(tx.getQueryRoot(), key, 203 tx.getCurrentData()); 204 if (addData.isAttribute()) 205 { 206 throw attributeKeyException(key); 207 } 208 final ImmutableNode newNode = 209 new ImmutableNode.Builder(nodes.size()) 210 .name(addData.getNewNodeName()) 211 .addChildren(nodes).create(); 212 addNodesByAddData(tx, addData, 213 Collections.singleton(newNode)); 214 } 215 return true; 216 } 217 }, selector, resolver); 218 } 219 } 220 221 @Override 222 public void setProperty(final String key, final Object value, 223 final NodeKeyResolver<ImmutableNode> resolver) 224 { 225 setProperty(key, null, value, resolver); 226 } 227 228 /** 229 * Sets the value of a property using a tracked node as root node. This 230 * method works like the normal {@code setProperty()} method, but the origin 231 * of the operation (also for the interpretation of the passed in key) is a 232 * tracked node identified by the passed in {@code NodeSelector}. The 233 * selector can be <b>null</b>, then the root node is assumed. 234 * 235 * @param key the key 236 * @param selector the {@code NodeSelector} defining the root node (or 237 * <b>null</b>) 238 * @param value the new value for this property 239 * @param resolver the {@code NodeKeyResolver} 240 * @throws ConfigurationRuntimeException if the selector cannot be resolved 241 */ 242 public void setProperty(final String key, final NodeSelector selector, 243 final Object value, final NodeKeyResolver<ImmutableNode> resolver) 244 { 245 updateModel(new TransactionInitializer() 246 { 247 @Override 248 public boolean initTransaction(final ModelTransaction tx) 249 { 250 boolean added = false; 251 final NodeUpdateData<ImmutableNode> updateData = 252 resolver.resolveUpdateKey(tx.getQueryRoot(), key, 253 value, tx.getCurrentData()); 254 if (!updateData.getNewValues().isEmpty()) 255 { 256 initializeAddTransaction(tx, key, 257 updateData.getNewValues(), resolver); 258 added = true; 259 } 260 final boolean cleared = 261 initializeClearTransaction(tx, 262 updateData.getRemovedNodes()); 263 final boolean updated = 264 initializeUpdateTransaction(tx, 265 updateData.getChangedValues()); 266 return added || cleared || updated; 267 } 268 }, selector, resolver); 269 } 270 271 /** 272 * {@inheritDoc} This implementation checks whether nodes become undefined 273 * after subtrees have been removed. If this is the case, such nodes are 274 * removed, too. Return value is a collection with {@code QueryResult} 275 * objects for the elements to be removed from the model. 276 */ 277 @Override 278 public List<QueryResult<ImmutableNode>> clearTree(final String key, 279 final NodeKeyResolver<ImmutableNode> resolver) 280 { 281 return clearTree(key, null, resolver); 282 } 283 284 /** 285 * Clears a whole sub tree using a tracked node as root node. This method 286 * works like the normal {@code clearTree()} method, but the origin of the 287 * operation (also for the interpretation of the passed in key) is a tracked 288 * node identified by the passed in {@code NodeSelector}. The selector can 289 * be <b>null</b>, then the root node is assumed. 290 * 291 * @param key the key 292 * @param selector the {@code NodeSelector} defining the root node (or 293 * <b>null</b>) 294 * @param resolver the {@code NodeKeyResolver} 295 * @return a list with the results to be removed 296 * @throws ConfigurationRuntimeException if the selector cannot be resolved 297 */ 298 public List<QueryResult<ImmutableNode>> clearTree(final String key, 299 final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) 300 { 301 final List<QueryResult<ImmutableNode>> removedElements = 302 new LinkedList<>(); 303 updateModel(new TransactionInitializer() 304 { 305 @Override 306 public boolean initTransaction(final ModelTransaction tx) 307 { 308 boolean changes = false; 309 final TreeData currentStructure = tx.getCurrentData(); 310 final List<QueryResult<ImmutableNode>> results = resolver.resolveKey( 311 tx.getQueryRoot(), key, currentStructure); 312 removedElements.clear(); 313 removedElements.addAll(results); 314 for (final QueryResult<ImmutableNode> result : results) 315 { 316 if (result.isAttributeResult()) 317 { 318 tx.addRemoveAttributeOperation(result.getNode(), 319 result.getAttributeName()); 320 } 321 else 322 { 323 if (result.getNode() == currentStructure.getRootNode()) 324 { 325 // the whole model is to be cleared 326 clear(resolver); 327 return false; 328 } 329 tx.addRemoveNodeOperation( 330 currentStructure.getParent(result.getNode()), 331 result.getNode()); 332 } 333 changes = true; 334 } 335 return changes; 336 } 337 }, selector, resolver); 338 339 return removedElements; 340 } 341 342 /** 343 * {@inheritDoc} If this operation leaves an affected node in an undefined 344 * state, it is removed from the model. 345 */ 346 @Override 347 public void clearProperty(final String key, 348 final NodeKeyResolver<ImmutableNode> resolver) 349 { 350 clearProperty(key, null, resolver); 351 } 352 353 /** 354 * Clears a property using a tracked node as root node. This method works 355 * like the normal {@code clearProperty()} method, but the origin of the 356 * operation (also for the interpretation of the passed in key) is a tracked 357 * node identified by the passed in {@code NodeSelector}. The selector can 358 * be <b>null</b>, then the root node is assumed. 359 * 360 * @param key the key 361 * @param selector the {@code NodeSelector} defining the root node (or 362 * <b>null</b>) 363 * @param resolver the {@code NodeKeyResolver} 364 * @throws ConfigurationRuntimeException if the selector cannot be resolved 365 */ 366 public void clearProperty(final String key, final NodeSelector selector, 367 final NodeKeyResolver<ImmutableNode> resolver) 368 { 369 updateModel(new TransactionInitializer() 370 { 371 @Override 372 public boolean initTransaction(final ModelTransaction tx) 373 { 374 final List<QueryResult<ImmutableNode>> results = 375 resolver.resolveKey(tx.getQueryRoot(), key, 376 tx.getCurrentData()); 377 return initializeClearTransaction(tx, results); 378 } 379 }, selector, resolver); 380 } 381 382 /** 383 * {@inheritDoc} A new empty root node is created with the same name as the 384 * current root node. Implementation note: Because this is a hard reset the 385 * usual dance for dealing with concurrent updates is not required here. 386 * 387 * @param resolver the {@code NodeKeyResolver} 388 */ 389 @Override 390 public void clear(final NodeKeyResolver<ImmutableNode> resolver) 391 { 392 final ImmutableNode newRoot = 393 new ImmutableNode.Builder().name(getRootNode().getNodeName()) 394 .create(); 395 setRootNode(newRoot); 396 } 397 398 /** 399 * {@inheritDoc} This implementation simply returns the current root node of this 400 * model. 401 */ 402 @Override 403 public ImmutableNode getInMemoryRepresentation() 404 { 405 return getTreeData().getRootNode(); 406 } 407 408 /** 409 * {@inheritDoc} All tracked nodes and reference objects managed by this 410 * model are cleared.Care has to be taken when this method is used and the 411 * model is accessed by multiple threads. It is not deterministic which 412 * concurrent operations see the old root and which see the new root node. 413 * 414 * @param newRoot the new root node to be set (can be <b>null</b>, then an 415 * empty root node is set) 416 */ 417 @Override 418 public void setRootNode(final ImmutableNode newRoot) 419 { 420 structure.set(createTreeData(initialRootNode(newRoot), structure.get())); 421 } 422 423 /** 424 * Replaces the root node of this model. This method is similar to 425 * {@link #setRootNode(ImmutableNode)}; however, tracked nodes will not get 426 * lost. The model applies the selectors of all tracked nodes on the new 427 * nodes hierarchy, so that corresponding nodes are selected (this may cause 428 * nodes to become detached if a select operation fails). This operation is 429 * useful if the new nodes hierarchy to be set is known to be similar to the 430 * old one. Note that reference objects are lost; there is no way to 431 * automatically match nodes between the old and the new nodes hierarchy. 432 * 433 * @param newRoot the new root node to be set (must not be <b>null</b>) 434 * @param resolver the {@code NodeKeyResolver} 435 * @throws IllegalArgumentException if the new root node is <b>null</b> 436 */ 437 public void replaceRoot(final ImmutableNode newRoot, 438 final NodeKeyResolver<ImmutableNode> resolver) 439 { 440 if (newRoot == null) 441 { 442 throw new IllegalArgumentException( 443 "Replaced root node must not be null!"); 444 } 445 446 final TreeData current = structure.get(); 447 // this step is needed to get a valid NodeHandler 448 final TreeData temp = 449 createTreeDataForRootAndTracker(newRoot, 450 current.getNodeTracker()); 451 structure.set(temp.updateNodeTracker(temp.getNodeTracker().update( 452 newRoot, null, resolver, temp))); 453 } 454 455 /** 456 * Merges the root node of this model with the specified node. This method 457 * is typically caused by configuration implementations when a configuration 458 * source is loaded, and its data has to be added to the model. It is 459 * possible to define the new name of the root node and to pass in a map 460 * with reference objects. 461 * 462 * @param node the node to be merged with the root node 463 * @param rootName the new name of the root node; can be <b>null</b>, then 464 * the name of the root node is not changed unless it is <b>null</b> 465 * @param references an optional map with reference objects 466 * @param rootRef an optional reference object for the new root node 467 * @param resolver the {@code NodeKeyResolver} 468 */ 469 public void mergeRoot(final ImmutableNode node, final String rootName, 470 final Map<ImmutableNode, ?> references, final Object rootRef, 471 final NodeKeyResolver<ImmutableNode> resolver) 472 { 473 updateModel(new TransactionInitializer() 474 { 475 @Override 476 public boolean initTransaction(final ModelTransaction tx) 477 { 478 final TreeData current = tx.getCurrentData(); 479 final String newRootName = 480 determineRootName(current.getRootNode(), node, rootName); 481 if (newRootName != null) 482 { 483 tx.addChangeNodeNameOperation(current.getRootNode(), 484 newRootName); 485 } 486 tx.addAddNodesOperation(current.getRootNode(), 487 node.getChildren()); 488 tx.addAttributesOperation(current.getRootNode(), 489 node.getAttributes()); 490 if (node.getValue() != null) 491 { 492 tx.addChangeNodeValueOperation(current.getRootNode(), 493 node.getValue()); 494 } 495 if (references != null) 496 { 497 tx.addNewReferences(references); 498 } 499 if (rootRef != null) 500 { 501 tx.addNewReference(current.getRootNode(), rootRef); 502 } 503 return true; 504 } 505 }, null, resolver); 506 } 507 508 /** 509 * Adds a node to be tracked. After this method has been called with a 510 * specific {@code NodeSelector}, the node associated with this key can be 511 * always obtained using {@link #getTrackedNode(NodeSelector)} with the same 512 * selector. This is useful because during updates of a model parts of the 513 * structure are replaced. Therefore, it is not a good idea to simply hold a 514 * reference to a node; this might become outdated soon. Rather, the node 515 * should be tracked. This mechanism ensures that always the correct node 516 * reference can be obtained. 517 * 518 * @param selector the {@code NodeSelector} defining the desired node 519 * @param resolver the {@code NodeKeyResolver} 520 * @throws ConfigurationRuntimeException if the selector does not select a 521 * single node 522 */ 523 public void trackNode(final NodeSelector selector, 524 final NodeKeyResolver<ImmutableNode> resolver) 525 { 526 boolean done; 527 do 528 { 529 final TreeData current = structure.get(); 530 final NodeTracker newTracker = 531 current.getNodeTracker().trackNode(current.getRootNode(), 532 selector, resolver, current); 533 done = 534 structure.compareAndSet(current, 535 current.updateNodeTracker(newTracker)); 536 } while (!done); 537 } 538 539 /** 540 * Allows tracking all nodes selected by a key. This method evaluates the 541 * specified key on the current nodes structure. For all selected nodes 542 * corresponding {@code NodeSelector} objects are created, and they are 543 * tracked. The returned collection of {@code NodeSelector} objects can be 544 * used for interacting with the selected nodes. 545 * 546 * @param key the key for selecting the nodes to track 547 * @param resolver the {@code NodeKeyResolver} 548 * @return a collection with the {@code NodeSelector} objects for the new 549 * tracked nodes 550 */ 551 public Collection<NodeSelector> selectAndTrackNodes(final String key, 552 final NodeKeyResolver<ImmutableNode> resolver) 553 { 554 final Mutable<Collection<NodeSelector>> refSelectors = 555 new MutableObject<>(); 556 boolean done; 557 do 558 { 559 final TreeData current = structure.get(); 560 final List<ImmutableNode> nodes = 561 resolver.resolveNodeKey(current.getRootNode(), key, current); 562 if (nodes.isEmpty()) 563 { 564 return Collections.emptyList(); 565 } 566 done = 567 structure.compareAndSet( 568 current, 569 createSelectorsForTrackedNodes(refSelectors, nodes, 570 current, resolver)); 571 } while (!done); 572 return refSelectors.getValue(); 573 } 574 575 /** 576 * Tracks all nodes which are children of the node selected by the passed in 577 * key. If the key selects exactly one node, for all children of this node 578 * {@code NodeSelector} objects are created, and they become tracked nodes. 579 * The returned collection of {@code NodeSelector} objects can be used for 580 * interacting with the selected nodes. 581 * 582 * @param key the key for selecting the parent node whose children are to be 583 * tracked 584 * @param resolver the {@code NodeKeyResolver} 585 * @return a collection with the {@code NodeSelector} objects for the new 586 * tracked nodes 587 */ 588 public Collection<NodeSelector> trackChildNodes(final String key, 589 final NodeKeyResolver<ImmutableNode> resolver) 590 { 591 final Mutable<Collection<NodeSelector>> refSelectors = 592 new MutableObject<>(); 593 boolean done; 594 do 595 { 596 refSelectors.setValue(Collections.<NodeSelector> emptyList()); 597 final TreeData current = structure.get(); 598 final List<ImmutableNode> nodes = 599 resolver.resolveNodeKey(current.getRootNode(), key, current); 600 if (nodes.size() == 1) 601 { 602 final ImmutableNode node = nodes.get(0); 603 done = 604 node.getChildren().isEmpty() 605 || structure.compareAndSet( 606 current, 607 createSelectorsForTrackedNodes( 608 refSelectors, 609 node.getChildren(), current, 610 resolver)); 611 } 612 else 613 { 614 done = true; 615 } 616 } while (!done); 617 return refSelectors.getValue(); 618 } 619 620 /** 621 * Tracks a node which is a child of another node selected by the passed in 622 * key. If the selected node has a child node with this name, it is tracked 623 * and its selector is returned. Otherwise, a new child node with this name 624 * is created first. 625 * 626 * @param key the key for selecting the parent node 627 * @param childName the name of the child node 628 * @param resolver the {@code NodeKeyResolver} 629 * @return the {@code NodeSelector} for the tracked child node 630 * @throws ConfigurationRuntimeException if the passed in key does not 631 * select a single node 632 */ 633 public NodeSelector trackChildNodeWithCreation(final String key, 634 final String childName, final NodeKeyResolver<ImmutableNode> resolver) 635 { 636 final MutableObject<NodeSelector> refSelector = 637 new MutableObject<>(); 638 boolean done; 639 640 do 641 { 642 final TreeData current = structure.get(); 643 final List<ImmutableNode> nodes = 644 resolver.resolveNodeKey(current.getRootNode(), key, current); 645 if (nodes.size() != 1) 646 { 647 throw new ConfigurationRuntimeException( 648 "Key does not select a single node: " + key); 649 } 650 651 final ImmutableNode parent = nodes.get(0); 652 final TreeData newData = 653 createDataWithTrackedChildNode(current, parent, childName, 654 resolver, refSelector); 655 656 done = structure.compareAndSet(current, newData); 657 } while (!done); 658 659 return refSelector.getValue(); 660 } 661 662 /** 663 * Returns the current {@code ImmutableNode} instance associated with the 664 * given {@code NodeSelector}. The node must be a tracked node, i.e. 665 * {@link #trackNode(NodeSelector, NodeKeyResolver)} must have been called 666 * before with the given selector. 667 * 668 * @param selector the {@code NodeSelector} defining the desired node 669 * @return the current {@code ImmutableNode} associated with this selector 670 * @throws ConfigurationRuntimeException if the selector is unknown 671 */ 672 public ImmutableNode getTrackedNode(final NodeSelector selector) 673 { 674 return structure.get().getNodeTracker().getTrackedNode(selector); 675 } 676 677 /** 678 * Replaces a tracked node by another node. If the tracked node is not yet 679 * detached, it becomes now detached. The passed in node (which must not be 680 * <b>null</b>) becomes the new root node of an independent model for this 681 * tracked node. Further updates of this model do not affect the tracked 682 * node's model and vice versa. 683 * 684 * @param selector the {@code NodeSelector} defining the tracked node 685 * @param newNode the node replacing the tracked node (must not be 686 * <b>null</b>) 687 * @throws ConfigurationRuntimeException if the selector cannot be resolved 688 * @throws IllegalArgumentException if the replacement node is <b>null</b> 689 */ 690 public void replaceTrackedNode(final NodeSelector selector, final ImmutableNode newNode) 691 { 692 if (newNode == null) 693 { 694 throw new IllegalArgumentException( 695 "Replacement node must not be null!"); 696 } 697 698 boolean done; 699 do 700 { 701 final TreeData currentData = structure.get(); 702 done = 703 replaceDetachedTrackedNode(currentData, selector, newNode) 704 || replaceActiveTrackedNode(currentData, selector, 705 newNode); 706 } while (!done); 707 } 708 709 /** 710 * Returns a {@code NodeHandler} for a tracked node. Such a handler may be 711 * required for operations on a sub tree of the model. The handler to be 712 * returned depends on the current state of the tracked node. If it is still 713 * active, a handler is used which shares some data (especially the parent 714 * mapping) with this model. Detached track nodes in contrast have their own 715 * separate model; in this case a handler associated with this model is 716 * returned. 717 * 718 * @param selector the {@code NodeSelector} defining the tracked node 719 * @return a {@code NodeHandler} for this tracked node 720 * @throws ConfigurationRuntimeException if the selector is unknown 721 */ 722 public NodeHandler<ImmutableNode> getTrackedNodeHandler( 723 final NodeSelector selector) 724 { 725 final TreeData currentData = structure.get(); 726 final InMemoryNodeModel detachedNodeModel = 727 currentData.getNodeTracker().getDetachedNodeModel(selector); 728 return (detachedNodeModel != null) ? detachedNodeModel.getNodeHandler() 729 : new TrackedNodeHandler(currentData.getNodeTracker() 730 .getTrackedNode(selector), currentData); 731 } 732 733 /** 734 * Returns a flag whether the specified tracked node is detached. As long as 735 * the {@code NodeSelector} associated with that node returns a single 736 * instance, the tracked node is said to be <em>life</em>. If now an update 737 * of the model happens which invalidates the selector (maybe the target 738 * node was removed), the tracked node becomes detached. It is still 739 * possible to query the node; here the latest valid instance is returned. 740 * But further changes on the node model are no longer tracked for this 741 * node. So even if there are further changes which would make the 742 * {@code NodeSelector} valid again, the tracked node stays in detached 743 * state. 744 * 745 * @param selector the {@code NodeSelector} defining the desired node 746 * @return a flag whether this tracked node is in detached state 747 * @throws ConfigurationRuntimeException if the selector is unknown 748 */ 749 public boolean isTrackedNodeDetached(final NodeSelector selector) 750 { 751 return structure.get().getNodeTracker().isTrackedNodeDetached(selector); 752 } 753 754 /** 755 * Removes a tracked node. This method is the opposite of 756 * {@code trackNode()}. It has to be called if there is no longer the need 757 * to track a specific node. Note that for each call of {@code trackNode()} 758 * there has to be a corresponding {@code untrackNode()} call. This ensures 759 * that multiple observers can track the same node. 760 * 761 * @param selector the {@code NodeSelector} defining the desired node 762 * @throws ConfigurationRuntimeException if the specified node is not 763 * tracked 764 */ 765 public void untrackNode(final NodeSelector selector) 766 { 767 boolean done; 768 do 769 { 770 final TreeData current = structure.get(); 771 final NodeTracker newTracker = 772 current.getNodeTracker().untrackNode(selector); 773 done = 774 structure.compareAndSet(current, 775 current.updateNodeTracker(newTracker)); 776 } while (!done); 777 } 778 779 /** 780 * Returns a {@code ReferenceNodeHandler} object for this model. This 781 * extended node handler can be used to query references objects stored for 782 * this model. 783 * 784 * @return the {@code ReferenceNodeHandler} 785 */ 786 public ReferenceNodeHandler getReferenceNodeHandler() 787 { 788 return getTreeData(); 789 } 790 791 /** 792 * Returns the current {@code TreeData} object. This object contains all 793 * information about the current node structure. 794 * 795 * @return the current {@code TreeData} object 796 */ 797 TreeData getTreeData() 798 { 799 return structure.get(); 800 } 801 802 /** 803 * Updates the mapping from nodes to their parents for the passed in 804 * hierarchy of nodes. This method traverses all children and grand-children 805 * of the passed in root node. For each node in the subtree the parent 806 * relation is added to the map. 807 * 808 * @param parents the map with parent nodes 809 * @param root the root node of the current tree 810 */ 811 static void updateParentMapping(final Map<ImmutableNode, ImmutableNode> parents, 812 final ImmutableNode root) 813 { 814 NodeTreeWalker.INSTANCE.walkBFS(root, 815 new ConfigurationNodeVisitorAdapter<ImmutableNode>() 816 { 817 @Override 818 public void visitBeforeChildren(final ImmutableNode node, 819 final NodeHandler<ImmutableNode> handler) 820 { 821 for (final ImmutableNode c : node.getChildren()) 822 { 823 parents.put(c, node); 824 } 825 } 826 }, DUMMY_HANDLER); 827 } 828 829 /** 830 * Checks if the passed in node is defined. Result is <b>true</b> if the 831 * node contains any data. 832 * 833 * @param node the node in question 834 * @return <b>true</b> if the node is defined, <b>false</b> otherwise 835 */ 836 static boolean checkIfNodeDefined(final ImmutableNode node) 837 { 838 return node.getValue() != null || !node.getChildren().isEmpty() 839 || !node.getAttributes().isEmpty(); 840 } 841 842 /** 843 * Initializes a transaction for an add operation. 844 * 845 * @param tx the transaction to be initialized 846 * @param key the key 847 * @param values the collection with node values 848 * @param resolver the {@code NodeKeyResolver} 849 */ 850 private void initializeAddTransaction(final ModelTransaction tx, final String key, 851 final Iterable<?> values, final NodeKeyResolver<ImmutableNode> resolver) 852 { 853 final NodeAddData<ImmutableNode> addData = 854 resolver.resolveAddKey(tx.getQueryRoot(), key, 855 tx.getCurrentData()); 856 if (addData.isAttribute()) 857 { 858 addAttributeProperty(tx, addData, values); 859 } 860 else 861 { 862 addNodeProperty(tx, addData, values); 863 } 864 } 865 866 /** 867 * Creates a {@code TreeData} object for the specified root node. 868 * 869 * @param root the root node of the current tree 870 * @param current the current {@code TreeData} object (may be <b>null</b>) 871 * @return the {@code TreeData} describing the current tree 872 */ 873 private TreeData createTreeData(final ImmutableNode root, final TreeData current) 874 { 875 final NodeTracker newTracker = 876 (current != null) ? current.getNodeTracker() 877 .detachAllTrackedNodes() : new NodeTracker(); 878 return createTreeDataForRootAndTracker(root, newTracker); 879 } 880 881 /** 882 * Creates a {@code TreeData} object for the specified root node and 883 * {@code NodeTracker}. Other parameters are set to default values. 884 * 885 * @param root the new root node for this model 886 * @param newTracker the new {@code NodeTracker} 887 * @return the new {@code TreeData} object 888 */ 889 private TreeData createTreeDataForRootAndTracker(final ImmutableNode root, 890 final NodeTracker newTracker) 891 { 892 return new TreeData(root, createParentMapping(root), 893 Collections.<ImmutableNode, ImmutableNode> emptyMap(), 894 newTracker, new ReferenceTracker()); 895 } 896 897 /** 898 * Handles an add property operation if the property to be added is a node. 899 * 900 * @param tx the transaction 901 * @param addData the {@code NodeAddData} 902 * @param values the collection with node values 903 */ 904 private static void addNodeProperty(final ModelTransaction tx, 905 final NodeAddData<ImmutableNode> addData, final Iterable<?> values) 906 { 907 final Collection<ImmutableNode> newNodes = 908 createNodesToAdd(addData.getNewNodeName(), values); 909 addNodesByAddData(tx, addData, newNodes); 910 } 911 912 /** 913 * Initializes a transaction to add a collection of nodes as described by a 914 * {@code NodeAddData} object. If necessary, new path nodes are created. 915 * Eventually, the new nodes are added as children to the specified target 916 * node. 917 * 918 * @param tx the transaction 919 * @param addData the {@code NodeAddData} 920 * @param newNodes the collection of new child nodes 921 */ 922 private static void addNodesByAddData(final ModelTransaction tx, 923 final NodeAddData<ImmutableNode> addData, 924 final Collection<ImmutableNode> newNodes) 925 { 926 if (addData.getPathNodes().isEmpty()) 927 { 928 tx.addAddNodesOperation(addData.getParent(), newNodes); 929 } 930 else 931 { 932 final ImmutableNode newChild = createNodeToAddWithPath(addData, newNodes); 933 tx.addAddNodeOperation(addData.getParent(), newChild); 934 } 935 } 936 937 /** 938 * Handles an add property operation if the property to be added is an 939 * attribute. 940 * 941 * @param tx the transaction 942 * @param addData the {@code NodeAddData} 943 * @param values the collection with node values 944 */ 945 private static void addAttributeProperty(final ModelTransaction tx, 946 final NodeAddData<ImmutableNode> addData, final Iterable<?> values) 947 { 948 if (addData.getPathNodes().isEmpty()) 949 { 950 tx.addAttributeOperation(addData.getParent(), 951 addData.getNewNodeName(), values.iterator().next()); 952 } 953 else 954 { 955 final int pathNodeCount = addData.getPathNodes().size(); 956 final ImmutableNode childWithAttribute = 957 new ImmutableNode.Builder() 958 .name(addData.getPathNodes().get(pathNodeCount - 1)) 959 .addAttribute(addData.getNewNodeName(), 960 values.iterator().next()).create(); 961 final ImmutableNode newChild = 962 (pathNodeCount > 1) ? createNodeOnPath(addData 963 .getPathNodes().subList(0, pathNodeCount - 1) 964 .iterator(), 965 Collections.singleton(childWithAttribute)) 966 : childWithAttribute; 967 tx.addAddNodeOperation(addData.getParent(), newChild); 968 } 969 } 970 971 /** 972 * Creates a collection with new nodes with a given name and a value from a 973 * given collection. 974 * 975 * @param newNodeName the name of the new nodes 976 * @param values the collection with node values 977 * @return the newly created collection 978 */ 979 private static Collection<ImmutableNode> createNodesToAdd( 980 final String newNodeName, final Iterable<?> values) 981 { 982 final Collection<ImmutableNode> nodes = new LinkedList<>(); 983 for (final Object value : values) 984 { 985 nodes.add(new ImmutableNode.Builder().name(newNodeName) 986 .value(value).create()); 987 } 988 return nodes; 989 } 990 991 /** 992 * Creates a node structure consisting of the path nodes defined by the 993 * passed in {@code NodeAddData} instance and all new child nodes. 994 * 995 * @param addData the {@code NodeAddData} 996 * @param newNodes the collection of new child nodes 997 * @return the parent node of the newly created hierarchy 998 */ 999 private static ImmutableNode createNodeToAddWithPath( 1000 final NodeAddData<ImmutableNode> addData, 1001 final Collection<ImmutableNode> newNodes) 1002 { 1003 return createNodeOnPath(addData.getPathNodes().iterator(), newNodes); 1004 } 1005 1006 /** 1007 * Recursive helper method for creating a path node for an add operation. 1008 * All path nodes except for the last have a single child. The last path 1009 * node has the new nodes as children. 1010 * 1011 * @param it the iterator over the names of the path nodes 1012 * @param newNodes the collection of new child nodes 1013 * @return the newly created path node 1014 */ 1015 private static ImmutableNode createNodeOnPath(final Iterator<String> it, 1016 final Collection<ImmutableNode> newNodes) 1017 { 1018 final String nodeName = it.next(); 1019 ImmutableNode.Builder builder; 1020 if (it.hasNext()) 1021 { 1022 builder = new ImmutableNode.Builder(1); 1023 builder.addChild(createNodeOnPath(it, newNodes)); 1024 } 1025 else 1026 { 1027 builder = new ImmutableNode.Builder(newNodes.size()); 1028 builder.addChildren(newNodes); 1029 } 1030 return builder.name(nodeName).create(); 1031 } 1032 1033 /** 1034 * Initializes a transaction to clear the values of a property based on the 1035 * passed in collection of affected results. 1036 * 1037 * @param tx the transaction to be initialized 1038 * @param results a collection with results pointing to the nodes to be 1039 * cleared 1040 * @return a flag whether there are elements to be cleared 1041 */ 1042 private static boolean initializeClearTransaction(final ModelTransaction tx, 1043 final Collection<QueryResult<ImmutableNode>> results) 1044 { 1045 for (final QueryResult<ImmutableNode> result : results) 1046 { 1047 if (result.isAttributeResult()) 1048 { 1049 tx.addRemoveAttributeOperation(result.getNode(), 1050 result.getAttributeName()); 1051 } 1052 else 1053 { 1054 tx.addClearNodeValueOperation(result.getNode()); 1055 } 1056 } 1057 1058 return !results.isEmpty(); 1059 } 1060 1061 /** 1062 * Initializes a transaction to change the values of some query results 1063 * based on the passed in map. 1064 * 1065 * @param tx the transaction to be initialized 1066 * @param changedValues the map defining the elements to be changed 1067 * @return a flag whether there are elements to be updated 1068 */ 1069 private static boolean initializeUpdateTransaction(final ModelTransaction tx, 1070 final Map<QueryResult<ImmutableNode>, Object> changedValues) 1071 { 1072 for (final Map.Entry<QueryResult<ImmutableNode>, Object> e : changedValues 1073 .entrySet()) 1074 { 1075 if (e.getKey().isAttributeResult()) 1076 { 1077 tx.addAttributeOperation(e.getKey().getNode(), e.getKey() 1078 .getAttributeName(), e.getValue()); 1079 } 1080 else 1081 { 1082 tx.addChangeNodeValueOperation(e.getKey().getNode(), 1083 e.getValue()); 1084 } 1085 } 1086 1087 return !changedValues.isEmpty(); 1088 } 1089 1090 /** 1091 * Determines the initial root node of this model. If a root node has been 1092 * provided, it is used. Otherwise, an empty dummy root node is created. 1093 * 1094 * @param providedRoot the passed in root node 1095 * @return the root node to be used 1096 */ 1097 private static ImmutableNode initialRootNode(final ImmutableNode providedRoot) 1098 { 1099 return (providedRoot != null) ? providedRoot 1100 : new ImmutableNode.Builder().create(); 1101 } 1102 1103 /** 1104 * Determines the name of the root node for a merge operation. If a root 1105 * name is provided, it is used. Otherwise, if the current root node has no 1106 * name, the name of the node to be merged is used. A result of <b>null</b> 1107 * means that no node name has to be set. 1108 * 1109 * @param rootNode the current root node 1110 * @param node the node to be merged with the root node 1111 * @param rootName the name of the resulting node 1112 * @return the new name of the root node 1113 */ 1114 private static String determineRootName(final ImmutableNode rootNode, 1115 final ImmutableNode node, final String rootName) 1116 { 1117 if (rootName != null) 1118 { 1119 return rootName; 1120 } 1121 if (rootNode.getNodeName() == null) 1122 { 1123 return node.getNodeName(); 1124 } 1125 return null; 1126 } 1127 1128 /** 1129 * Creates the mapping to parent nodes for the nodes structured represented 1130 * by the passed in root node. Each node is assigned its parent node. Here 1131 * an iterative algorithm is used rather than a recursive one to avoid stack 1132 * overflow for huge structures. 1133 * 1134 * @param root the root node of the structure 1135 * @return the parent node mapping 1136 */ 1137 private Map<ImmutableNode, ImmutableNode> createParentMapping( 1138 final ImmutableNode root) 1139 { 1140 final Map<ImmutableNode, ImmutableNode> parents = 1141 new HashMap<>(); 1142 updateParentMapping(parents, root); 1143 return parents; 1144 } 1145 1146 /** 1147 * Performs a non-blocking, thread-safe update of this model based on a 1148 * transaction initialized by the passed in initializer. This method uses 1149 * the atomic reference for the model's current data to ensure that an 1150 * update was successful even if the model is concurrently accessed. 1151 * 1152 * @param txInit the {@code TransactionInitializer} 1153 * @param selector an optional {@code NodeSelector} defining the target node 1154 * of the transaction 1155 * @param resolver the {@code NodeKeyResolver} 1156 */ 1157 private void updateModel(final TransactionInitializer txInit, 1158 final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) 1159 { 1160 boolean done; 1161 1162 do 1163 { 1164 final TreeData currentData = getTreeData(); 1165 done = 1166 executeTransactionOnDetachedTrackedNode(txInit, selector, 1167 currentData, resolver) 1168 || executeTransactionOnCurrentStructure(txInit, 1169 selector, currentData, resolver); 1170 } while (!done); 1171 } 1172 1173 /** 1174 * Executes a transaction on the current data of this model. This method is 1175 * called if an operation is to be executed on the model's root node or a 1176 * tracked node which is not yet detached. 1177 * 1178 * @param txInit the {@code TransactionInitializer} 1179 * @param selector an optional {@code NodeSelector} defining the target node 1180 * @param currentData the current data of the model 1181 * @param resolver the {@code NodeKeyResolver} 1182 * @return a flag whether the operation has been completed successfully 1183 */ 1184 private boolean executeTransactionOnCurrentStructure( 1185 final TransactionInitializer txInit, final NodeSelector selector, 1186 final TreeData currentData, final NodeKeyResolver<ImmutableNode> resolver) 1187 { 1188 boolean done; 1189 final ModelTransaction tx = 1190 new ModelTransaction(currentData, selector, resolver); 1191 if (!txInit.initTransaction(tx)) 1192 { 1193 done = true; 1194 } 1195 else 1196 { 1197 final TreeData newData = tx.execute(); 1198 done = structure.compareAndSet(tx.getCurrentData(), newData); 1199 } 1200 return done; 1201 } 1202 1203 /** 1204 * Tries to execute a transaction on the model of a detached tracked node. 1205 * This method checks whether the target node of the transaction is a 1206 * tracked node and if this node is already detached. If this is the case, 1207 * the update operation is independent on this model and has to be executed 1208 * on the specific model for the detached node. 1209 * 1210 * @param txInit the {@code TransactionInitializer} 1211 * @param selector an optional {@code NodeSelector} defining the target node 1212 * @param currentData the current data of the model 1213 * @param resolver the {@code NodeKeyResolver} @return a flag whether the 1214 * transaction could be executed 1215 * @throws ConfigurationRuntimeException if the selector cannot be resolved 1216 */ 1217 private boolean executeTransactionOnDetachedTrackedNode( 1218 final TransactionInitializer txInit, final NodeSelector selector, 1219 final TreeData currentData, final NodeKeyResolver<ImmutableNode> resolver) 1220 { 1221 if (selector != null) 1222 { 1223 final InMemoryNodeModel detachedNodeModel = 1224 currentData.getNodeTracker().getDetachedNodeModel(selector); 1225 if (detachedNodeModel != null) 1226 { 1227 detachedNodeModel.updateModel(txInit, null, resolver); 1228 return true; 1229 } 1230 } 1231 1232 return false; 1233 } 1234 1235 /** 1236 * Replaces a tracked node if it is already detached. 1237 * 1238 * @param currentData the current data of the model 1239 * @param selector the {@code NodeSelector} defining the tracked node 1240 * @param newNode the node replacing the tracked node 1241 * @return a flag whether the operation was successful 1242 */ 1243 private boolean replaceDetachedTrackedNode(final TreeData currentData, 1244 final NodeSelector selector, final ImmutableNode newNode) 1245 { 1246 final InMemoryNodeModel detachedNodeModel = 1247 currentData.getNodeTracker().getDetachedNodeModel(selector); 1248 if (detachedNodeModel != null) 1249 { 1250 detachedNodeModel.setRootNode(newNode); 1251 return true; 1252 } 1253 1254 return false; 1255 } 1256 1257 /** 1258 * Replaces an active tracked node. The node then becomes detached. 1259 * 1260 * @param currentData the current data of the model 1261 * @param selector the {@code NodeSelector} defining the tracked node 1262 * @param newNode the node replacing the tracked node 1263 * @return a flag whether the operation was successful 1264 */ 1265 private boolean replaceActiveTrackedNode(final TreeData currentData, 1266 final NodeSelector selector, final ImmutableNode newNode) 1267 { 1268 final NodeTracker newTracker = 1269 currentData.getNodeTracker().replaceAndDetachTrackedNode( 1270 selector, newNode); 1271 return structure.compareAndSet(currentData, 1272 currentData.updateNodeTracker(newTracker)); 1273 } 1274 1275 /** 1276 * Creates tracked node entries for the specified nodes and creates the 1277 * corresponding selectors. 1278 * 1279 * @param refSelectors the reference where to store the selectors 1280 * @param nodes the nodes to be tracked 1281 * @param current the current {@code TreeData} object 1282 * @param resolver the {@code NodeKeyResolver} 1283 * @return the updated {@code TreeData} object 1284 */ 1285 private static TreeData createSelectorsForTrackedNodes( 1286 final Mutable<Collection<NodeSelector>> refSelectors, 1287 final List<ImmutableNode> nodes, final TreeData current, 1288 final NodeKeyResolver<ImmutableNode> resolver) 1289 { 1290 final List<NodeSelector> selectors = 1291 new ArrayList<>(nodes.size()); 1292 final Map<ImmutableNode, String> cache = new HashMap<>(); 1293 for (final ImmutableNode node : nodes) 1294 { 1295 selectors.add(new NodeSelector(resolver.nodeKey(node, cache, 1296 current))); 1297 } 1298 refSelectors.setValue(selectors); 1299 final NodeTracker newTracker = 1300 current.getNodeTracker().trackNodes(selectors, nodes); 1301 return current.updateNodeTracker(newTracker); 1302 } 1303 1304 /** 1305 * Adds a tracked node that has already been resolved to the specified data 1306 * object. 1307 * 1308 * @param current the current {@code TreeData} object 1309 * @param node the node in question 1310 * @param resolver the {@code NodeKeyResolver} 1311 * @param refSelector here the newly created {@code NodeSelector} is 1312 * returned 1313 * @return the new {@code TreeData} instance 1314 */ 1315 private static TreeData updateDataWithNewTrackedNode(final TreeData current, 1316 final ImmutableNode node, final NodeKeyResolver<ImmutableNode> resolver, 1317 final MutableObject<NodeSelector> refSelector) 1318 { 1319 final NodeSelector selector = 1320 new NodeSelector(resolver.nodeKey(node, 1321 new HashMap<ImmutableNode, String>(), current)); 1322 refSelector.setValue(selector); 1323 final NodeTracker newTracker = 1324 current.getNodeTracker().trackNodes( 1325 Collections.singleton(selector), 1326 Collections.singleton(node)); 1327 return current.updateNodeTracker(newTracker); 1328 } 1329 1330 /** 1331 * Creates a new data object with a tracked child node of the given parent 1332 * node. If such a child node already exists, it is used. Otherwise, a new 1333 * one is created. 1334 * 1335 * @param current the current {@code TreeData} object 1336 * @param parent the parent node 1337 * @param childName the name of the child node 1338 * @param resolver the {@code NodeKeyResolver} 1339 * @param refSelector here the newly created {@code NodeSelector} is 1340 * returned 1341 * @return the new {@code TreeData} instance 1342 */ 1343 private static TreeData createDataWithTrackedChildNode(final TreeData current, 1344 final ImmutableNode parent, final String childName, 1345 final NodeKeyResolver<ImmutableNode> resolver, 1346 final MutableObject<NodeSelector> refSelector) 1347 { 1348 TreeData newData; 1349 final List<ImmutableNode> namedChildren = 1350 current.getChildren(parent, childName); 1351 if (!namedChildren.isEmpty()) 1352 { 1353 newData = 1354 updateDataWithNewTrackedNode(current, namedChildren.get(0), 1355 resolver, refSelector); 1356 } 1357 else 1358 { 1359 final ImmutableNode child = 1360 new ImmutableNode.Builder().name(childName).create(); 1361 final ModelTransaction tx = new ModelTransaction(current, null, resolver); 1362 tx.addAddNodeOperation(parent, child); 1363 newData = 1364 updateDataWithNewTrackedNode(tx.execute(), child, resolver, 1365 refSelector); 1366 } 1367 return newData; 1368 } 1369 1370 /** 1371 * Checks whether the specified collection with values is not empty. 1372 * 1373 * @param values the collection with node values 1374 * @return <b>true</b> if values are provided, <b>false</b> otherwise 1375 */ 1376 private static boolean valuesNotEmpty(final Iterable<?> values) 1377 { 1378 return values.iterator().hasNext(); 1379 } 1380 1381 /** 1382 * Creates an exception referring to an invalid key for adding properties. 1383 * Such an exception is thrown when an operation tries to add something to 1384 * an attribute. 1385 * 1386 * @param key the invalid key causing this exception 1387 * @return the exception 1388 */ 1389 private static RuntimeException attributeKeyException(final String key) 1390 { 1391 return new IllegalArgumentException( 1392 "New nodes cannot be added to an attribute key: " + key); 1393 } 1394 1395 /** 1396 * An interface used internally for handling concurrent updates. An 1397 * implementation has to populate the passed in {@code ModelTransaction}. 1398 * The transaction is then executed, and an atomic update of the model's 1399 * {@code TreeData} is attempted. If this fails - because another update 1400 * came across -, the whole operation has to be tried anew. 1401 */ 1402 private interface TransactionInitializer 1403 { 1404 /** 1405 * Initializes the specified transaction for an update operation. The 1406 * return value indicates whether the transaction should be executed. A 1407 * result of <b>false</b> means that the update is to be aborted (maybe 1408 * another update method was called). 1409 * 1410 * @param tx the transaction to be initialized 1411 * @return a flag whether the update should continue 1412 */ 1413 boolean initTransaction(ModelTransaction tx); 1414 } 1415}