Package org.waveprotocol.box.server.waveserver

Source Code of org.waveprotocol.box.server.waveserver.WaveServerImpl

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.waveprotocol.box.server.waveserver;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;

import org.waveprotocol.box.common.ExceptionalIterator;
import org.waveprotocol.box.server.common.CoreWaveletOperationSerializer;
import org.waveprotocol.box.server.frontend.CommittedWaveletSnapshot;
import org.waveprotocol.box.common.Receiver;
import org.waveprotocol.box.server.persistence.PersistenceException;
import org.waveprotocol.wave.crypto.SignatureException;
import org.waveprotocol.wave.crypto.SignerInfo;
import org.waveprotocol.wave.crypto.UnknownSignerException;
import org.waveprotocol.wave.federation.FederationErrorProto.FederationError;
import org.waveprotocol.wave.federation.FederationErrors;
import org.waveprotocol.wave.federation.FederationException;
import org.waveprotocol.wave.federation.FederationRemoteBridge;
import org.waveprotocol.wave.federation.Proto.ProtocolAppliedWaveletDelta;
import org.waveprotocol.wave.federation.Proto.ProtocolHashedVersion;
import org.waveprotocol.wave.federation.Proto.ProtocolSignature;
import org.waveprotocol.wave.federation.Proto.ProtocolSignedDelta;
import org.waveprotocol.wave.federation.Proto.ProtocolSignerInfo;
import org.waveprotocol.wave.federation.Proto.ProtocolWaveletDelta;
import org.waveprotocol.wave.federation.WaveletFederationListener;
import org.waveprotocol.wave.federation.WaveletFederationProvider;
import org.waveprotocol.wave.model.id.WaveId;
import org.waveprotocol.wave.model.id.WaveletId;
import org.waveprotocol.wave.model.id.WaveletName;
import org.waveprotocol.wave.model.operation.OperationException;
import org.waveprotocol.wave.model.operation.wave.TransformedWaveletDelta;
import org.waveprotocol.wave.model.version.HashedVersion;
import org.waveprotocol.wave.model.wave.InvalidParticipantAddress;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.wave.data.ReadableWaveletData;
import org.waveprotocol.wave.model.operation.TransformException;
import org.waveprotocol.wave.util.logging.Log;

import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import org.waveprotocol.box.server.executor.ExecutorAnnotations;
import org.waveprotocol.box.server.executor.ExecutorAnnotations.ListenerExecutor;

/**
* The main class that services the FederationHost, FederationRemote and ClientFrontend.
*/
@Singleton
public class WaveServerImpl implements WaveletProvider, ReadableWaveletDataProvider,
    WaveletFederationProvider, WaveletFederationListener.Factory {

  private static final Log LOG = Log.get(WaveServerImpl.class);

  private final Executor listenerExecutor;
  private final CertificateManager certificateManager;
  private final WaveletFederationProvider federationRemote;
  private final WaveMap waveMap;
  private boolean initialized = false;

  //
  // WaveletFederationListener.Factory implementation.
  //

  /**
   * Listener for notifications coming from the Federation Remote. For now we accept updates
   * for wavelets on any domain.
   */
  @Override
  public WaveletFederationListener listenerForDomain(final String domain) {
    return new WaveletFederationListener() {
      @Override
      public void waveletDeltaUpdate(final WaveletName waveletName,
          List<ByteString> deltas, final WaveletUpdateCallback callback) {
        Preconditions.checkArgument(!deltas.isEmpty());

        if (isLocalWavelet(waveletName)) {
          LOG.warning("Remote tried to update local wavelet " + waveletName);
          callback.onFailure(FederationErrors.badRequest("Received update to local wavelet"));
          return;
        }

        // Update wavelet container with the applied deltas
        final RemoteWaveletContainer remoteWavelet = getOrCreateRemoteWavelet(waveletName);

        // Update this remote wavelet with the immediately incoming delta,
        // providing a callback so that incoming historic deltas (as well as
        // this delta) can be provided to the wave bus.
        final ListenableFuture<Void> result =
            remoteWavelet.update(deltas, domain, federationRemote, certificateManager);
        result.addListener(
            new Runnable() {
              @Override
              public void run() {
                try {
                  FutureUtil.getResultOrPropagateException(result, FederationException.class);
                  callback.onSuccess();
                } catch (FederationException e) {
                  LOG.warning("Failed updating " + waveletName, e);
                  callback.onFailure(e.getError());
                } catch (InterruptedException e) {
                  Thread.currentThread().interrupt();
                  LOG.severe("Interrupted updating " + waveletName, e);
                  callback.onFailure(FederationErrors.internalServerError("Interrupted"));
                }
              }
            },
            listenerExecutor);
      }

      @Override
      public void waveletCommitUpdate(WaveletName waveletName,
          ProtocolHashedVersion committedVersion, WaveletUpdateCallback callback) {
        Preconditions.checkNotNull(committedVersion);

        if (isLocalWavelet(waveletName)) {
          LOG.warning("Got commit update for local wavelet " + waveletName);
          callback.onFailure(FederationErrors.badRequest("Received commit update to local wavelet"));
          return;
        }

        RemoteWaveletContainer wavelet;
        try {
          wavelet = getRemoteWavelet(waveletName);
        } catch (WaveletStateException e) {
          LOG.warning("Failed to access wavelet " + waveletName + " for commit update", e);
          callback.onFailure(FederationErrors.internalServerError("Storage access failure"));
          return;
        }
        if (wavelet != null) {
          wavelet.commit(CoreWaveletOperationSerializer.deserialize(committedVersion));
        } else {
          if(LOG.isInfoLoggable()) {
            LOG.info("Got commit update for missing wavelet " + waveletName);
          }
          createAndCommitRemoteWavelet(waveletName, committedVersion);
        }
        callback.onSuccess();
      }
    };
  }

  /**
   * Creates the non-existent remote wavelet container at this server and commits it.
   * Calling commit at this known version, forces the history to be fetched up to this point.
   * TODO (alown): Possible race condition here with update? (Though I don't think it would result in
   * anything more serious than repeated history fetches.)
   */
  private void createAndCommitRemoteWavelet(WaveletName waveletName, ProtocolHashedVersion committedVersion) {
    RemoteWaveletContainer wavelet = getOrCreateRemoteWavelet(waveletName);
    HashedVersion v = CoreWaveletOperationSerializer.deserialize(committedVersion);
    wavelet.commit(v);
    LOG.info("Passed commit message for version " + v.getVersion() + " to RemoteWavelet");
  }

  //
  // WaveletFederationProvider implementation.
  //

  @Override
  public void submitRequest(WaveletName waveletName, ProtocolSignedDelta signedDelta,
      SubmitResultListener listener) {
    if (!isLocalWavelet(waveletName)) {
      LOG.warning("Remote tried to submit to non-local wavelet " + waveletName);
      listener.onFailure(FederationErrors.badRequest("Non-local wavelet update"));
      return;
    }

    ProtocolWaveletDelta delta;
    try {
      delta = ByteStringMessage.parseProtocolWaveletDelta(signedDelta.getDelta()).getMessage();
    } catch (InvalidProtocolBufferException e) {
      LOG.warning("Submit request: Invalid delta protobuf. WaveletName: " + waveletName, e);
      listener.onFailure(FederationErrors.badRequest("Signed delta contains invalid delta"));
      return;
    }

    // Disallow creation of wavelets by remote users.
    if (delta.getHashedVersion().getVersion() == 0) {
      LOG.warning("Remote user tried to submit delta at version 0 - disallowed. " + signedDelta);
      listener.onFailure(FederationErrors.badRequest("Remote users may not create wavelets."));
      return;
    }

    try {
      certificateManager.verifyDelta(signedDelta);
      submitDelta(waveletName, delta, signedDelta, listener);
    } catch (SignatureException e) {
      LOG.warning("Submit request: Delta failed verification. WaveletName: " + waveletName +
          " delta: " + signedDelta, e);
      listener.onFailure(FederationErrors.badRequest("Remote verification failed"));
    } catch (UnknownSignerException e) {
      LOG.warning("Submit request: unknown signer.  WaveletName: " + waveletName +
          "delta: " + signedDelta, e);
      listener.onFailure(FederationErrors.internalServerError("Unknown signer"));
    }
  }

  @Override
  public void requestHistory(WaveletName waveletName, String domain,
      ProtocolHashedVersion startVersion, ProtocolHashedVersion endVersion,
      final long lengthLimit, HistoryResponseListener listener) {
    LocalWaveletContainer wavelet = loadLocalWavelet(waveletName, listener);
    if (wavelet != null) {
      final ImmutableList.Builder<ByteString> deltaHistoryBytes = ImmutableList.builder();
      final AtomicInteger length = new AtomicInteger(0);
      try {
        wavelet.requestHistory(
            CoreWaveletOperationSerializer.deserialize(startVersion),
            CoreWaveletOperationSerializer.deserialize(endVersion),
            new Receiver<ByteStringMessage<ProtocolAppliedWaveletDelta>>() {

          @Override
          public boolean put(ByteStringMessage<ProtocolAppliedWaveletDelta> delta) {
            ByteString bytes = delta.getByteString();
            deltaHistoryBytes.add(bytes);
            if (length.addAndGet(bytes.size()) >= lengthLimit) {
              return false;
            }
            return true;
          }
        });
      } catch (WaveServerException e) {
        LOG.severe("Error retrieving wavelet history: " + waveletName + " " + startVersion +
            " - " + endVersion);
        // TODO(soren): choose a better error code (depending on e)
        listener.onFailure(FederationErrors.badRequest(
            "Server error while retrieving wavelet history."));
        return;
      }

      // Now determine whether we received the entire requested wavelet history.
      LOG.info("Found deltaHistory between " + startVersion + " - " + endVersion
          + ", returning to requester domain " + domain);
      listener.onSuccess(deltaHistoryBytes.build(), endVersion, endVersion.getVersion());
    }
  }

  @Override
  public void getDeltaSignerInfo(ByteString signerId,
      WaveletName waveletName, ProtocolHashedVersion deltaEndVersion,
      DeltaSignerInfoResponseListener listener) {
    LocalWaveletContainer wavelet = loadLocalWavelet(waveletName, listener);
    if (wavelet != null) {
      HashedVersion endVersion = CoreWaveletOperationSerializer.deserialize(deltaEndVersion);
      if (wavelet.isDeltaSigner(endVersion, signerId)) {
        ProtocolSignerInfo signerInfo = certificateManager.retrieveSignerInfo(signerId);
        if (signerInfo == null) {
          // Oh no!  We are supposed to store it, and we already know they did sign this delta.
          LOG.severe("No stored signer info for valid getDeltaSignerInfo on " + waveletName);
          listener.onFailure(FederationErrors.badRequest("Unknown signer info"));
        } else {
          listener.onSuccess(signerInfo);
        }
      } else {
        LOG.info("getDeltaSignerInfo was not authrorised for wavelet " + waveletName
            + ", end version " + deltaEndVersion);
        listener.onFailure(FederationErrors.badRequest("Not authorised to get signer info"));
      }
    }
  }

  @Override
  public void postSignerInfo(String destinationDomain, ProtocolSignerInfo signerInfo,
      PostSignerInfoResponseListener listener) {
    try {
      certificateManager.storeSignerInfo(signerInfo);
    } catch (SignatureException e) {
      String error = "verification failure from domain " + signerInfo.getDomain();
      LOG.warning("incoming postSignerInfo: " + error, e);
      listener.onFailure(FederationErrors.badRequest(error));
      return;
    }
    listener.onSuccess();
  }

  //
  // WaveletProvider implementation.
  //

  @Override
  public void initialize() throws WaveServerException {
    Preconditions.checkState(!initialized, "Wave server already initialized");
    initialized = true;
  }

  @Override
  public void getHistory(WaveletName waveletName, HashedVersion startVersion, HashedVersion endVersion,
      Receiver<TransformedWaveletDelta> receiver) throws WaveServerException {
    Preconditions.checkState(initialized, "Wave server not yet initialized");
    WaveletContainer wavelet = getWavelet(waveletName);
    if (wavelet == null) {
      throw new AccessControlException(
          "Client request for history made for non-existent wavelet: " + waveletName);
    }
    wavelet.requestTransformedHistory(startVersion, endVersion, receiver);
  }

  @Override
  public
  ExceptionalIterator<WaveId, WaveServerException> getWaveIds() {
    Preconditions.checkState(initialized, "Wave server not yet initialized");
    return waveMap.getWaveIds();
  }

  @Override
  public
  ImmutableSet<WaveletId> getWaveletIds(WaveId waveId) throws WaveServerException {
    Preconditions.checkState(initialized, "Wave server not yet initialized");
    return waveMap.lookupWavelets(waveId);
  }

  @Override
  public CommittedWaveletSnapshot getSnapshot(WaveletName waveletName) throws WaveServerException {
    Preconditions.checkState(initialized, "Wave server not yet initialized");
    WaveletContainer wavelet = getWavelet(waveletName);
    if (wavelet == null) {
      LOG.info("client requested snapshot for non-existent wavelet: " + waveletName);
      return null;
    } else {
      return wavelet.getSnapshot();
    }
  }

  @Override
  public ReadableWaveletData getReadableWaveletData(WaveletName waveletName)
      throws WaveServerException {
    return getSnapshot(waveletName).snapshot;
  }

  @Override
  public void submitRequest(WaveletName waveletName, ProtocolWaveletDelta delta,
      final SubmitRequestListener listener) {
    Preconditions.checkState(initialized, "Wave server not yet initialized");
    if (delta.getOperationCount() == 0) {
      listener.onFailure("Empty delta at version " + delta.getHashedVersion().getVersion());
      return;
    }

    // The serialised version of this delta happens now.  This should be the only place, ever!
    ProtocolSignedDelta signedDelta =
        certificateManager.signDelta(ByteStringMessage.serializeMessage(delta));

    submitDelta(waveletName, delta, signedDelta, new SubmitResultListener() {
      @Override
      public void onFailure(FederationError errorMessage) {
        listener.onFailure(errorMessage.getErrorMessage());
      }

      @Override
      public void onSuccess(int operationsApplied,
          ProtocolHashedVersion hashedVersionAfterApplication, long applicationTimestamp) {
        listener.onSuccess(operationsApplied,
            CoreWaveletOperationSerializer.deserialize(hashedVersionAfterApplication),
            applicationTimestamp);
      }
    });
  }

  @Override
  public boolean checkAccessPermission(WaveletName waveletName, ParticipantId participantId)
      throws WaveServerException {
    Preconditions.checkState(initialized, "Wave server not yet initialized");
    WaveletContainer wavelet = getWavelet(waveletName);
    return wavelet != null && wavelet.checkAccessPermission(participantId);
  }

  /**
   * Constructor.
   *
   * @param listenerExecutor executes callback listeners
   * @param certificateManager provider of certificates; it also determines which
   *        domains this wave server regards as local wavelets.
   * @param federationRemote federation remote interface
   * @param waveMap records the waves and wavelets in memory
   */
  @Inject
  WaveServerImpl(@ListenerExecutor Executor listenerExecutor,
      CertificateManager certificateManager,
      @FederationRemoteBridge WaveletFederationProvider federationRemote, WaveMap waveMap) {
    this.listenerExecutor = listenerExecutor;
    this.certificateManager = certificateManager;
    this.federationRemote = federationRemote;
    this.waveMap = waveMap;

    LOG.info("Wave Server configured to host local domains: "
        + certificateManager.getLocalDomains());

    // Preemptively add our own signer info to the certificate manager
    SignerInfo signerInfo = certificateManager.getLocalSigner().getSignerInfo();
    if (signerInfo != null) {
      try {
        certificateManager.storeSignerInfo(signerInfo.toProtoBuf());
      } catch (SignatureException e) {
        LOG.severe("Failed to add our own signer info to the certificate store", e);
      }
    }
  }

  /**
   * Loads a local wavelet. If the request is invalid then the listener is
   * notified and null returned.
   *
   * @param waveletName wavelet to load
   * @param listener listener to notify on failure
   * @return the wavelet container, or null on failure
   */
  private LocalWaveletContainer loadLocalWavelet(WaveletName waveletName,
      FederationListener listener) {
    // TODO(soren): once we support federated groups, expand support to request remote wavelets too.
    if (!isLocalWavelet(waveletName)) {
      LOG.warning("Attempt to get delta signer info for remote wavelet " + waveletName);
      listener.onFailure(FederationErrors.badRequest("Wavelet not hosted here."));
      return null;
    }

    LocalWaveletContainer wavelet = null;
    try {
      wavelet = getLocalWavelet(waveletName);
    } catch (WaveletStateException e) {
      LOG.warning("Failed to access wavelet " + waveletName, e);
      listener.onFailure(FederationErrors.internalServerError("Storage access failure"));
      return null;
    }
    if (wavelet == null) {
      LOG.info("Non-existent wavelet " + waveletName);
      // TODO(soren): determine if it's ok to leak the fact that the wavelet doesn't exist,
      // or if we should hide this information and return the same error code as for wrong
      // version number or history hash
      listener.onFailure(FederationErrors.badRequest("Wavelet does not exist"));
    }
    return wavelet;
  }

  private boolean isLocalWavelet(WaveletName waveletName) {
    boolean isLocal = getLocalDomains().contains(waveletName.waveletId.getDomain());
    LOG.fine("" + waveletName + " is " + (isLocal? "" : "not") + " local");
    return isLocal;
  }

  private Set<String> getLocalDomains() {
    return certificateManager.getLocalDomains();
  }

  /**
   * Returns a container for a remote wavelet. If it doesn't exist, it will be created.
   * This method is only called in response to a Federation Remote doing an update
   * or commit on this wavelet.
   *
   * @param waveletName name of wavelet
   * @return an existing or new instance.
   * @throws IllegalArgumentException if the name refers to a local wavelet.
   */
  private RemoteWaveletContainer getOrCreateRemoteWavelet(WaveletName waveletName) {
    Preconditions.checkArgument(!isLocalWavelet(waveletName), "%s is not remote", waveletName);
    return waveMap.getOrCreateRemoteWavelet(waveletName);
  }

  /**
   * Returns a container for a local wavelet. If it doesn't exist, it will be created.
   *
   * @param waveletName name of wavelet
   * @return an existing or new instance.
   * @throws IllegalArgumentException if the name refers to a remote wavelet.
   */
  private LocalWaveletContainer getOrCreateLocalWavelet(WaveletName waveletName) {
    Preconditions.checkArgument(isLocalWavelet(waveletName), "%s is not local", waveletName);
    return waveMap.getOrCreateLocalWavelet(waveletName);
  }

  private RemoteWaveletContainer getRemoteWavelet(WaveletName waveletName)
      throws WaveletStateException {
    Preconditions.checkArgument(!isLocalWavelet(waveletName), "%s is not remote", waveletName);
    return waveMap.getRemoteWavelet(waveletName);
  }

  private LocalWaveletContainer getLocalWavelet(WaveletName waveletName)
      throws WaveletStateException {
    Preconditions.checkArgument(isLocalWavelet(waveletName), "%s is not local", waveletName);
    return waveMap.getLocalWavelet(waveletName);
  }

  /**
   * Returns a generic wavelet container, when the caller doesn't need to validate whether
   * its a local or remote wavelet.
   *
   * @param waveletName name of wavelet.
   * @return an wavelet container or null if it doesn't exist.
   * @throw WaveServerException if storage lookup fails
   */
  private WaveletContainer getWavelet(WaveletName waveletName) throws WaveServerException {
    return isLocalWavelet(waveletName) ?
        waveMap.getLocalWavelet(waveletName) : waveMap.getRemoteWavelet(waveletName);
  }

  /**
   * Callback interface for sending a list of certificates to a domain.
   */
  private interface PostSignerInfoCallback {
    public void done(int successCount);
  }

  /**
   * Submit the delta to local or remote wavelets, return results via listener.
   * Also broadcast updates to federationHosts and clientFrontend.
   *
   * @param waveletName the wavelet to apply the delta to
   * @param delta the {@link ProtocolWaveletDelta} inside {@code signedDelta}
   * @param signedDelta the signed delta
   * @param resultListener callback
   *
   * TODO: For now the WaveletFederationProvider will have to ensure this is a
   * local wavelet. Once we support federated groups, that test should be
   * removed.
   */
  private void submitDelta(final WaveletName waveletName, ProtocolWaveletDelta delta,
      final ProtocolSignedDelta signedDelta, final SubmitResultListener resultListener) {
    Preconditions.checkArgument(delta.getOperationCount() > 0, "empty delta");

    if (isLocalWavelet(waveletName)) {
      LOG.info("Submit to " + waveletName + " by " + delta.getAuthor() + " @ "
          + delta.getHashedVersion().getVersion() + " with " + delta.getOperationCount() + " ops");

      // TODO(arb): add v0 policer here.
      LocalWaveletContainer wavelet = getOrCreateLocalWavelet(waveletName);
      try {
        if (!wavelet.checkAccessPermission(ParticipantId.of(delta.getAuthor()))) {
          resultListener.onFailure(FederationErrors.badRequest(
              delta.getAuthor() + " is not a participant of " + waveletName));
          return;
        }
      } catch (InvalidParticipantAddress e) {
        resultListener.onFailure(FederationErrors.badRequest(
            "Invalid author address: " + e.getMessage()));
        return;
      } catch (WaveServerException e) {
        resultListener.onFailure(FederationErrors.internalServerError(e.getMessage()));
        return;
      }

      try {
        WaveletDeltaRecord submitResult = wavelet.submitRequest(waveletName, signedDelta);
        TransformedWaveletDelta transformedDelta = submitResult.getTransformedDelta();
        LOG.info("Submit result for " + waveletName + " by "
            + transformedDelta.getAuthor() + " applied "
            + transformedDelta.size() + " ops at v: "
            + transformedDelta.getAppliedAtVersion() + " t: "
            + transformedDelta.getApplicationTimestamp());
        resultListener.onSuccess(transformedDelta.size(),
            CoreWaveletOperationSerializer.serialize(transformedDelta.getResultingVersion()),
            transformedDelta.getApplicationTimestamp());
      } catch (OperationException e) {
        resultListener.onFailure(FederationErrors.badRequest(e.getMessage()));
      } catch (InvalidProtocolBufferException e) {
        resultListener.onFailure(FederationErrors.badRequest(e.getMessage()));
      } catch (InvalidHashException e) {
        resultListener.onFailure(FederationErrors.badRequest(e.getMessage()));
      } catch (PersistenceException e) {
        resultListener.onFailure(FederationErrors.internalServerError(e.getMessage()));
      } catch (WaveletStateException e) {
        resultListener.onFailure(FederationErrors.internalServerError(e.getMessage()));
      }
    } else {
      // For remote wavelets post required signatures to the authorative server then send delta
      postAllSignerInfo(signedDelta.getSignatureList(), waveletName.waveletId.getDomain(),
          new PostSignerInfoCallback() {
            @Override public void done(int successCount) {
              LOG.info("Remote: successfully sent " + successCount + " of "
                  + signedDelta.getSignatureCount() + " certs to "
                  + waveletName.waveletId.getDomain());
              federationRemote.submitRequest(waveletName, signedDelta, resultListener);
            }
          });
    }
  }

  /**
   * Post a list of certificates to a domain and run a callback when all are finished.  The
   * callback will run whether or not all posts succeed.
   *
   * @param sigs list of signatures to post signer info for
   * @param domain to post signature to
   * @param callback to run when all signatures have been posted, successfully or unsuccessfully
   */
  private void postAllSignerInfo(final List<ProtocolSignature> sigs, final String domain,
      final PostSignerInfoCallback callback) {

    // In the current implementation there should only be a single signer
    if (sigs.size() != 1) {
      LOG.warning(sigs.size() + " signatures to broadcast, expecting exactly 1");
    }

    final AtomicInteger resultCount = new AtomicInteger(sigs.size());
    final AtomicInteger successCount = new AtomicInteger(0);

    for (final ProtocolSignature sig : sigs) {
      final ProtocolSignerInfo psi = certificateManager.retrieveSignerInfo(sig.getSignerId());

      if (psi == null) {
        LOG.warning("Couldn't find signer info for " + sig);
        if (resultCount.decrementAndGet() == 0) {
          LOG.info("Finished signature broadcast with " + successCount.get()
              + " successful, running callback");
          callback.done(successCount.get());
        }
      } else {
        federationRemote.postSignerInfo(domain, psi, new PostSignerInfoResponseListener() {
          @Override
          public void onFailure(FederationError error) {
            LOG.warning("Failed to post " + sig + " to " + domain + ": " + error);
            countDown();
          }

          @Override
          public void onSuccess() {
            LOG.info("Successfully broadcasted " + sig + " to " + domain);
            successCount.incrementAndGet();
            countDown();
          }

          private void countDown() {
            if (resultCount.decrementAndGet() == 0) {
              LOG.info("Finished signature broadcast with " + successCount.get()
                  + " successful, running callback");
              callback.done(successCount.get());
            }
          }
        });
      }
    }
  }
}
TOP

Related Classes of org.waveprotocol.box.server.waveserver.WaveServerImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.