@Override
  public void notify(ChangeSet changeSet) {
    if (changeSet == null) {
      return; // do nothing
    }
    if (!isOpen.get()) {
      // The channel is not open ...
      return;
    }
    if (!multipleAddressesInCluster.get()) {
      // We are in clustered mode, but there is only one participant in the cluster (us).
      // So short-circuit the cluster and just notify the local observers ...
      if (hasObservers()) {
        delegate.notify(changeSet);
        logReceivedOperation(changeSet);
      }
      return;
    }

    // There are multiple participants in the cluster, so send all changes out to JGroups,
    // letting JGroups do the ordering of messages...
    try {
      logSendOperation(changeSet);
      byte[] data = serialize(changeSet);
      Message message = new Message(null, null, data);
      channel.send(message);
    } catch (IllegalStateException e) {
      LOGGER.warn(
          BusI18n.unableToNotifyChanges,
          clusteringConfiguration.getClusterName(),
          changeSet.size(),
          changeSet.getWorkspaceName(),
          changeSet.getUserId(),
          changeSet.getProcessKey(),
          changeSet.getTimestamp());
    } catch (Exception e) {
      // Something went wrong here (this should not happen) ...
      String msg =
          BusI18n.errorSerializingChanges.text(
              clusteringConfiguration.getClusterName(),
              changeSet.size(),
              changeSet.getWorkspaceName(),
              changeSet.getUserId(),
              changeSet.getProcessKey(),
              changeSet.getTimestamp(),
              changeSet);
      throw new SystemFailureException(msg, e);
    }
  }
 protected final void logReceivedOperation(ChangeSet changeSet) {
   if (LOGGER.isTraceEnabled()) {
     LOGGER.trace(
         "Received on cluster '{0}' {1} changes on workspace {2} made by {3} from process '{4}' at {5}",
         clusteringConfiguration.getClusterName(),
         changeSet.size(),
         changeSet.getWorkspaceName(),
         changeSet.getUserId(),
         changeSet.getProcessKey(),
         changeSet.getTimestamp());
   }
 }
  @Override
  public void notify(ChangeSet changeSet) {
    if (!systemWorkspaceName.equals(changeSet.getWorkspaceName())) {
      // The change does not affect the 'system' workspace, so skip it ...
      return;
    }
    if (context.getProcessId().equals(changeSet.getProcessKey())) {
      // We generated these changes, so skip them ...
      return;
    }

    // Now process the changes ...
    Set<Name> nodeTypesToRefresh = new HashSet<Name>();
    Set<Name> nodeTypesToDelete = new HashSet<Name>();
    for (Change change : changeSet) {
      if (change instanceof NodeAdded) {
        NodeAdded added = (NodeAdded) change;
        Path addedPath = added.getPath();
        if (nodeTypesPath.isAncestorOf(addedPath)) {
          // Get the name of the node type ...
          Name nodeTypeName = addedPath.getSegment(2).getName();
          nodeTypesToRefresh.add(nodeTypeName);
        }
      } else if (change instanceof NodeRemoved) {
        NodeRemoved removed = (NodeRemoved) change;
        Path removedPath = removed.getPath();
        if (nodeTypesPath.isAncestorOf(removedPath)) {
          // Get the name of the node type ...
          Name nodeTypeName = removedPath.getSegment(2).getName();
          if (removedPath.size() == 3) {
            nodeTypesToDelete.add(nodeTypeName);
          } else {
            // It's a child defn or property defn ...
            if (!nodeTypesToDelete.contains(nodeTypeName)) {
              // The child defn or property defn is being removed but the node type is not ...
              nodeTypesToRefresh.add(nodeTypeName);
            }
          }
        }
      } else if (change instanceof PropertyChanged) {
        PropertyChanged propChanged = (PropertyChanged) change;
        Path changedPath = propChanged.getPathToNode();
        if (nodeTypesPath.isAncestorOf(changedPath)) {
          // Get the name of the node type ...
          Name nodeTypeName = changedPath.getSegment(2).getName();
          nodeTypesToRefresh.add(nodeTypeName);
        }
      } // we don't care about node moves (don't happen) or property added/removed (handled by node
        // add/remove)
    }

    if (nodeTypesToRefresh.isEmpty() && nodeTypesToDelete.isEmpty()) {
      // No changes
      return;
    }

    // There were at least some changes ...
    this.nodeTypesLock.writeLock().lock();
    try {
      // Re-register the node types that were changed or added ...
      SessionCache systemCache = repository.createSystemSession(context, false);
      SystemContent system = new SystemContent(systemCache);
      Collection<NodeTypeDefinition> nodeTypes = system.readNodeTypes(nodeTypesToRefresh);
      registerNodeTypes(nodeTypes, false, false, false);

      // Unregister those that were removed ...
      unregisterNodeType(nodeTypesToDelete, false);
    } catch (Throwable e) {
      logger.error(e, JcrI18n.errorRefreshingNodeTypes, repository.name());
    } finally {
      this.nodeTypesLock.writeLock().unlock();
    }
  }