docs: final AbstractJcrNode addChildNode( Name childName,
                                          Name childPrimaryNodeTypeName,
                                          NodeKey desiredKey,
                                          boolean skipVersioningValidation,
                                          boolean aclScope )
          throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException,
          RepositoryException {
          checkNodeTypeCanBeModified();
  
          if (!aclScope) {
              session.checkPermission(this, ModeShapePermissions.ADD_NODE);
          }
  
          if (isLockedByAnotherSession()) {
              throw new LockException(JcrI18n.lockTokenNotHeld.text(location()));
          }
  
          // Find the best child node definition to use for this new child ...
          NodeTypes nodeTypes = session.nodeTypes();
  
          // Determine the node type based upon this node's type information ...
          SessionCache cache = sessionCache();
          CachedNode node = node();
  
          // validate there is an appropriate child node definition
          JcrNodeDefinition childDefn = validateChildNodeDefinition(childName, childPrimaryNodeTypeName, !aclScope);
          if (childPrimaryNodeTypeName == null) {
              childPrimaryNodeTypeName = childDefn.getDefaultPrimaryType().getInternalName();
          }
  
          // See if this node is checked in. If so, then we can only create children if the child
          // node definition has an OPV of 'ignore'. See Section 15.2.2 of the JSR-283 spec for details ...
          if (!skipVersioningValidation && !isCheckedOut() && childDefn.getOnParentVersion() != OnParentVersionAction.IGNORE) {
              // The OPV is not 'ignore', so we can't create the new node ...
              Path parentPath = path();
              String parentPathStr = readable(parentPath);
              int numExistingSns = node.getChildReferences(cache).getChildCount(childName);
              int sns = numExistingSns + 1;
              String segment = readable(session.pathFactory().createSegment(childName, sns));
              String opv = OnParentVersionAction.nameFromValue(childDefn.getOnParentVersion());
              I18n msg = JcrI18n.cannotCreateChildOnCheckedInNodeSinceOpvOfChildDefinitionIsNotIgnore;
              throw new VersionException(msg.text(segment, readable(parentPathStr), childDefn.getName(), opv));
          }
  
          // If there isn't a desired key, check if the document store doesn't require a certain key format (this is especially used
          // by federation)
          if (desiredKey == null) {
              String documentStoreKey = null;
              try {
                  documentStoreKey = session().repository().documentStore()
                                              .newDocumentKey(key().toString(), childName, childPrimaryNodeTypeName);
              } catch (Exception e) {
                  throw new RepositoryException(e);
              }
              if (documentStoreKey != null) {
                  desiredKey = new NodeKey(documentStoreKey);
              } 
          }
  
          // We can create the child, so start by building the required properties ...
          PropertyFactory propFactory = session.propertyFactory();
          Property ptProp = propFactory.create(JcrLexicon.PRIMARY_TYPE, childPrimaryNodeTypeName);
  
          if (JcrNtLexicon.UNSTRUCTURED.equals(childPrimaryNodeTypeName)) {
              // This is very common, and we know they don't have auto-created properties or children ...
              MutableCachedNode newChild = mutable().createChild(cache, desiredKey, childName, ptProp);
  
              // And get or create the JCR node ...
              AbstractJcrNode jcrNode = session.node(newChild.getKey(), null, key());
  
              // Set the child node definition ...
              jcrNode.setNodeDefinitionId(childDefn.getId(), nodeTypes.getVersion());
              return jcrNode;
          }
  
          // Auto-create the properties ...
          NodeTypes capabilities = session.repository().nodeTypeManager().getNodeTypes();
          LinkedList<Property> props = autoCreatePropertiesFor(childName, childPrimaryNodeTypeName, propFactory, capabilities);
  
          // Then create the node ...
          MutableCachedNode newChild = null;
          if (props != null) {
              props.addFirst(ptProp);
              newChild = mutable().createChild(cache, desiredKey, childName, props);
          } else {
              newChild = mutable().createChild(cache, desiredKey, childName, ptProp);
          }
  
          // Check if the child node is referenceable
          if (capabilities.getNodeType(childPrimaryNodeTypeName).isNodeType(JcrMixLexicon.REFERENCEABLE)) {
              newChild.setProperty(cache, propFactory.create(JcrLexicon.UUID, session.nodeIdentifier(newChild.getKey())));
          }
          
          if (nodeTypes.isUnorderedCollection(childPrimaryNodeTypeName, null)) {
              // we're adding a new unordered collection so we need to set some internal (non JCR) information
              int bucketIdLength = nodeTypes.getBucketIdLengthForUnorderedCollection(childPrimaryNodeTypeName, null);
              newChild.addInternalProperty(DocumentConstants.BUCKET_ID_LENGTH, bucketIdLength);
          }
  
          // And get or create the JCR node ...
          AbstractJcrNode jcrNode = session.node(newChild.getKey(), null, key());
  
          // Set the child node definition ...
          jcrNode.setNodeDefinitionId(childDefn.getId(), nodeTypes.getVersion());
  
          // Create any mandatory properties or child nodes ...
          jcrNode.autoCreateChildren(childPrimaryNodeTypeName, capabilities);
  
          return jcrNode;
      }