docs: ModeShapeLock lock( JcrSession session,
                          CachedNode node,
                          boolean isDeep,
                          boolean isSessionScoped,
                          long timeoutHint,
                          String ownerInfo ) throws LockException, RepositoryException {
          assert session != null;
          assert node != null;
  
          final ExecutionContext context = session.context();
          final String owner = ownerInfo != null ? ownerInfo : session.getUserID();
  
          final DateTimeFactory dateFactory = context.getValueFactories().getDateFactory();
          long expirationTimeInMillis = this.defaultLockAgeMillis;
          if (timeoutHint > 0 && timeoutHint < Long.MAX_VALUE) {
              expirationTimeInMillis = TimeUnit.MILLISECONDS.convert(timeoutHint, TimeUnit.SECONDS);
          }
          DateTime expirationDate = dateFactory.create().plus(Duration.ofMillis(expirationTimeInMillis));
  
          // Create a new lock ...
          SessionCache systemSession = repository.createSystemSession(context, false);
          SystemContent system = new SystemContent(systemSession);
          NodeKey nodeKey = node.getKey();
          NodeKey lockKey = generateLockKey(system.locksKey(), nodeKey);
          String token = generateLockToken();
          ModeShapeLock lock = new ModeShapeLock(nodeKey, lockKey, session.workspaceName(), owner, token, isDeep, isSessionScoped,
                                                 session.sessionId(), expirationDate);
  
          if (isDeep) {
              NodeCache cache = session.cache();
              CachedNode locked = findLockedNodeAtOrBelow(node, cache);
              if (locked != null) {
                  String nodePath = session.stringFactory().create(node.getPath(cache));
                  String descendantPath = session.stringFactory().create(locked.getPath(cache));
                  throw new LockException(JcrI18n.descendantAlreadyLocked.text(nodePath, descendantPath));
              }
          }
  
          ModeShapeLock existing = locksByNodeKey.putIfAbsent(nodeKey, lock);
          if (existing != null) {
              if (!existing.isExpired()) {
                  throwAlreadyLocked(session, existing);
              }
              // there's an existing lock which has expired, so we have to unlock first
              unlock(session, existing.lockedNodeKey);
              // try adding the new lock
              existing = locksByNodeKey.putIfAbsent(nodeKey, lock);
              if (existing != null) {
                  // some other thread has already replaced the old value
                  throwAlreadyLocked(session, existing);
              }
          }
  
          try {
              // Store the lock within the system area ...
              system.storeLock(lock);
  
              // Update the persistent node ...
              SessionCache lockingSession = session.spawnSessionCache(false);
              MutableCachedNode lockedNode = lockingSession.mutable(nodeKey);
  
              PropertyFactory propertyFactory = session.propertyFactory();
              lockedNode.setProperty(lockingSession, propertyFactory.create(JcrLexicon.LOCK_OWNER, owner));
              lockedNode.setProperty(lockingSession, propertyFactory.create(JcrLexicon.LOCK_IS_DEEP, isDeep));
              lockedNode.lock(isSessionScoped);
  
              // Now save both sessions. This will fail with a LockFailureException if the locking failed ...
              // save the system session first so that the system change is reflected first in the ws caches
              systemSession.save(lockingSession, null);
          } catch (LockFailureException | DocumentAlreadyExistsException e) {
              // Someone must have snuck in and locked the node, and we just didn't receive notification of it yet ...
              // note that the latter exception is a rare case which can occur only in a cluster under precise timing, when 2 
              // or more writers attempt to create the same lock the first time
              String location = nodeKey.toString();
              try {
                  location = session.node(nodeKey, null).getPath();
              } catch (Throwable t) {
                  // couldn't come up with the path, so just use the key
              }
              locksByNodeKey.remove(nodeKey);
              throw new LockException(JcrI18n.alreadyLocked.text(location));
          } catch (RuntimeException e) {
              locksByNodeKey.remove(nodeKey);
              throw new RepositoryException(e);
          }
          return lock;
      }