/**
* Copyright 2010 Google Inc.
*
* Licensed 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.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.google.protobuf.ByteString;
import org.waveprotocol.box.common.DeltaSequence;
import org.waveprotocol.box.server.common.CoreWaveletOperationSerializer;
import org.waveprotocol.box.server.util.WaveletDataUtil;
import org.waveprotocol.wave.federation.FederationHostBridge;
import org.waveprotocol.wave.federation.WaveletFederationListener;
import org.waveprotocol.wave.federation.FederationErrorProto.FederationError;
import org.waveprotocol.wave.federation.Proto.ProtocolHashedVersion;
import org.waveprotocol.wave.model.id.WaveletName;
import org.waveprotocol.wave.model.operation.wave.TransformedWaveletDelta;
import org.waveprotocol.wave.model.version.HashedVersion;
import org.waveprotocol.wave.model.wave.data.ReadableWaveletData;
import org.waveprotocol.wave.util.logging.Log;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Forwards wave notifications to wave bus subscribers and remote wave servers.
*
* Swallows any runtime exception from a wave bus subscriber and removes that
* subscriber. The wave server used to do this swallowing but really things are
* in bad shape if a subscriber throws a runtime exception.
* TODO(anorth): Remove this catch and let the server crash.
*
* @author soren@google.com (Soren Lassen)
*/
class WaveletNotificationDispatcher implements WaveBus, WaveletNotificationSubscriber {
private static final Log LOG = Log.get(WaveletNotificationDispatcher.class);
/** Picks out the transformed deltas from a list of delta records. */
private static ImmutableList<TransformedWaveletDelta> transformedDeltasOf(
Iterable<WaveletDeltaRecord> deltaRecords) {
ImmutableList.Builder<TransformedWaveletDelta> transformedDeltas = ImmutableList.builder();
for (WaveletDeltaRecord deltaRecord : deltaRecords) {
transformedDeltas.add(deltaRecord.getTransformedDelta());
}
return transformedDeltas.build();
}
/** Picks out the byte strings of the applied deltas from a list of delta records. */
private static ImmutableList<ByteString> serializedAppliedDeltasOf(
Iterable<WaveletDeltaRecord> deltaRecords) {
ImmutableList.Builder<ByteString> serializedAppliedDeltas = ImmutableList.builder();
for (WaveletDeltaRecord deltaRecord : deltaRecords) {
serializedAppliedDeltas.add(deltaRecord.getAppliedDelta().getByteString());
}
return serializedAppliedDeltas.build();
}
private final ImmutableSet<String> localDomains;
private final WaveletFederationListener.Factory federationHostFactory;
private final CopyOnWriteArraySet<WaveBus.Subscriber> subscribers =
new CopyOnWriteArraySet<WaveBus.Subscriber>();
/** Maps remote domains to wave server stubs for those domains. */
private final Map<String, WaveletFederationListener> federationHosts =
new MapMaker().makeComputingMap(
new Function<String, WaveletFederationListener>() {
@Override
public WaveletFederationListener apply(String domain) {
return federationHostFactory.listenerForDomain(domain);
}
});
/**
* Constructor.
*
* @param certificateManager knows what the local domains are
* @param federationHostFactory manufactures federation host instances for
* remote domains
*/
@Inject
public WaveletNotificationDispatcher(
CertificateManager certificateManager,
@FederationHostBridge WaveletFederationListener.Factory federationHostFactory) {
this.localDomains = certificateManager.getLocalDomains();
this.federationHostFactory = federationHostFactory;
}
@Override
public void subscribe(Subscriber s) {
subscribers.add(s);
}
@Override
public void unsubscribe(Subscriber s) {
subscribers.remove(s);
}
@Override
public void waveletUpdate(ReadableWaveletData wavelet, ImmutableList<WaveletDeltaRecord> deltas,
ImmutableSet<String> domainsToNotify) {
DeltaSequence sequence = DeltaSequence.of(transformedDeltasOf(deltas));
for (WaveBus.Subscriber s : subscribers) {
try {
s.waveletUpdate(wavelet, sequence);
} catch (RuntimeException e) {
LOG.severe("Runtime exception in update to wave bus subscriber " + s, e);
// Subscriber is now in an undefined state.
subscribers.remove(s);
}
}
Set<String> remoteDomainsToNotify = Sets.difference(domainsToNotify, localDomains);
if (!remoteDomainsToNotify.isEmpty()) {
ImmutableList<ByteString> serializedAppliedDeltas = serializedAppliedDeltasOf(deltas);
for (String domain : remoteDomainsToNotify) {
federationHosts.get(domain).waveletDeltaUpdate(WaveletDataUtil.waveletNameOf(wavelet),
serializedAppliedDeltas, federationCallback("delta update"));
}
}
}
@Override
public void waveletCommitted(WaveletName waveletName, HashedVersion version,
ImmutableSet<String> domainsToNotify) {
for (WaveBus.Subscriber s : subscribers) {
try {
s.waveletCommitted(waveletName, version);
} catch (RuntimeException e) {
LOG.severe("Runtime exception in commit to wave bus subscriber " + s, e);
// Subscriber is now in an undefined state.
subscribers.remove(s);
}
}
Set<String> remoteDomainsToNotify = Sets.difference(domainsToNotify, localDomains);
if (!remoteDomainsToNotify.isEmpty()) {
ProtocolHashedVersion serializedVersion = CoreWaveletOperationSerializer.serialize(version);
for (String domain : remoteDomainsToNotify) {
federationHosts.get(domain).waveletCommitUpdate(
waveletName, serializedVersion, federationCallback("commit notice"));
}
}
}
private WaveletFederationListener.WaveletUpdateCallback federationCallback(
final String description) {
return new WaveletFederationListener.WaveletUpdateCallback() {
@Override
public void onSuccess() {
LOG.info(description + " success");
}
@Override
public void onFailure(FederationError error) {
LOG.warning(description + " failure: " + error);
}
};
}
}