@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);
    }
  }
  @Override
  public synchronized void start() throws Exception {
    String clusterName = clusteringConfiguration.getClusterName();
    if (clusterName == null) {
      throw new IllegalStateException(BusI18n.clusterNameRequired.text());
    }
    if (channel != null) {
      // Disconnect from any previous channel ...
      channel.removeChannelListener(listener);
      channel.setReceiver(null);
    }
    // Create the new channel by calling the delegate method ...
    channel = newChannel();
    // Add a listener through which we'll know what's going on within the cluster ...
    channel.addChannelListener(listener);

    // Set the receiver through which we'll receive all of the changes ...
    channel.setReceiver(receiver);

    // Now connect to the cluster ...
    channel.connect(clusterName);

    // start the delegate
    delegate.start();
  }
  public ClusteredRepositoryChangeBus(
      RepositoryConfiguration.Clustering clusteringConfiguration, ChangeBus delegate) {
    CheckArg.isNotNull(clusteringConfiguration, "clusteringConfiguration");
    CheckArg.isNotNull(delegate, "delegate");

    this.clusteringConfiguration = clusteringConfiguration;
    assert clusteringConfiguration.isEnabled();
    this.delegate = delegate;
  }
  private Channel newChannel() throws Exception {
    // Try to get the channel directly from the configuration (and its environment) ...
    Channel channel = clusteringConfiguration.getChannel();
    if (channel != null) {
      return channel;
    }

    String lookupClassName = clusteringConfiguration.getChannelProviderClassName();
    assert lookupClassName != null;

    Class<?> lookupClass = Class.forName(lookupClassName);
    if (!ChannelProvider.class.isAssignableFrom(lookupClass)) {
      throw new IllegalArgumentException(
          "Invalid channel lookup class configured. Expected a subclass of org.modeshape.jcr.clustering.ChannelProvider. Actual class:"
              + lookupClass);
    }
    return ((ChannelProvider) lookupClass.newInstance()).getChannel(clusteringConfiguration);
  }
 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());
   }
 }