docs: JcrNodeDefinition validateChildNodeDefinition( Name childName,
                                                     Name childPrimaryNodeTypeName,
                                                     boolean skipProtected )
          throws ItemNotFoundException, InvalidItemStateException, ItemExistsException, ConstraintViolationException,
          NoSuchNodeTypeException {
  
          final SessionCache cache = sessionCache();
          final CachedNode node = node();
          Name primaryTypeName = node.getPrimaryType(cache);
          Set<Name> mixins = node.getMixinTypes(cache);
          NodeTypes nodeTypes = session().nodeTypes();
          final SiblingCounter siblingCounter = SiblingCounter.create(node, cache);
  
          if (childPrimaryNodeTypeName != null) {
              if (INTERNAL_NODE_TYPE_NAMES.contains(childPrimaryNodeTypeName)) {
                  int numExistingSns = siblingCounter.countSiblingsNamed(childName);
                  String workspaceName = workspaceName();
                  String childPath = readable(session.pathFactory().create(path(), childName, numExistingSns + 1));
                  String msg = JcrI18n.unableToCreateNodeWithInternalPrimaryType.text(childPrimaryNodeTypeName, childPath,
                                                                                      workspaceName);
                  throw new ConstraintViolationException(msg);
              }
              JcrNodeType primaryType = nodeTypes.getNodeType(childPrimaryNodeTypeName);
              if (primaryType == null) {
                  int numExistingSns = siblingCounter.countSiblingsNamed(childName);
                  Path pathForChild = session.pathFactory().create(path(), childName, numExistingSns + 1);
                  I18n msg = JcrI18n.unableToCreateNodeWithPrimaryTypeThatDoesNotExist;
                  throw new NoSuchNodeTypeException(msg.text(childPrimaryNodeTypeName, pathForChild, workspaceName()));
              }
  
              if (primaryType.isMixin()) {
                  I18n msg = JcrI18n.cannotUseMixinTypeAsPrimaryType;
                  throw new ConstraintViolationException(msg.text(primaryType.getName()));
              }
  
              if (primaryType.isAbstract()) {
                  I18n msg = JcrI18n.primaryTypeCannotBeAbstract;
                  throw new ConstraintViolationException(msg.text(primaryType.getName()));
              }
          }
  
          NodeDefinitionSet childDefns = nodeTypes.findChildNodeDefinitions(primaryTypeName, mixins);
          JcrNodeDefinition childDefn = childDefns.findBestDefinitionForChild(childName, childPrimaryNodeTypeName, skipProtected,
                                                                              siblingCounter);
          if (childDefn == null) {
              // Failed to find an appropriate child node definition. Throw an exception with the appropriate message...
              String repoName = session.repository().repositoryName();
              String workspaceName = workspaceName();
              childDefns.determineReasonForMismatch(childName, childPrimaryNodeTypeName, skipProtected, siblingCounter,
                                                    primaryTypeName, mixins, path(), workspaceName, repoName, context());
          }
          assert childDefn != null;
  
          if (childPrimaryNodeTypeName == null && childDefn.getDefaultPrimaryType() == null) {
              // There is no default primary type ...
              int numExistingSns = siblingCounter.countSiblingsNamed(childName);
              String childPath = readable(session.pathFactory().create(path(), childName, numExistingSns + 1));
              I18n msg = JcrI18n.unableToCreateNodeWithNoDefaultPrimaryTypeOnChildNodeDefinition;
              String nodeTypeName = childDefn.getDeclaringNodeType().getName();
              throw new ConstraintViolationException(msg.text(childDefn.getName(), nodeTypeName, childPath, workspaceName()));
          }
          return childDefn;
      }