/**
* Copyright 2009 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.wave.examples.fedone.waveserver;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.protobuf.ByteString;
import org.waveprotocol.wave.crypto.WaveSigner;
import org.waveprotocol.wave.examples.fedone.waveserver.CertificateManager.SignatureResultListener;
import org.waveprotocol.wave.federation.Proto.ProtocolSignedDelta;
import org.waveprotocol.wave.federation.Proto.ProtocolWaveletDelta;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Signs deltas passed to it in addDelta.
* Obtain an instance of a Delta Signer using DeltaSignerFactory.
*
* Stores deltas in bundles. We will queue bundles that are in the process of being signed
*
* After a certain timeout, or after the bundle has reached
* a maximum size, sign all deltas and call the listeners with the signed deltas.
* A bundle is created on demand i.e. when a delta needs is to be added the first time,
* or when a previous one has already been signed.
*
* TODO(balfanz): replace/augment the deltasToSign map to use proper streamauth bundles.
*
* @author jochen@google.com (Jochen Bekmann)
*/
class BundlingDeltaSigner implements DeltaSigner {
private final int bundlingAccumulationDelayMs;
private final WaveSigner signer;
private final ScheduledExecutorService executorService;
private DeltaBundle currentBundle;
private final int maximumDeltaBundleSize;
/** Stores deltas in a bundle. Start a timer when the first delta is added. */
private class DeltaBundle {
AtomicBoolean acceptMoreDeltas;
Map<ByteStringMessage<ProtocolWaveletDelta>,
CertificateManager.SignatureResultListener> deltasToSign = Maps.newHashMap();
private ScheduledFuture<?> scheduledFuture;
private final Runnable signingTask;
DeltaBundle() {
scheduledFuture = null; // Will be null until a signingTask is scheduled.
acceptMoreDeltas = new AtomicBoolean(true);
signingTask = new Runnable() {
@Override public void run() {
signBundle();
}
};
}
boolean canAcceptMoreDeltas() {
return acceptMoreDeltas.get();
}
/** Sign bundle, notify every listener with a signedDelta result. */
synchronized private void signBundle() {
for (Entry<ByteStringMessage<ProtocolWaveletDelta>, SignatureResultListener> entry :
deltasToSign.entrySet()) {
ProtocolSignedDelta.Builder signedDelta = ProtocolSignedDelta.newBuilder();
ByteString deltaBytes = entry.getKey().getByteString();
signedDelta.setDelta(deltaBytes);
signedDelta.addAllSignature(ImmutableList.of(signer.sign(deltaBytes.toByteArray()).build()));
entry.getValue().signatureResult(signedDelta.build());
}
}
/**
* Add the delta to the bundle. This may trigger signing now or later. The
* resultListener will be called once the delta has been signed. May only
* be called when canAcceptMoreDeltas is true.
*/
synchronized void addDelta(ByteStringMessage<ProtocolWaveletDelta> delta,
CertificateManager.SignatureResultListener resultListener) {
Preconditions.checkState(acceptMoreDeltas.get());
deltasToSign.put(delta, resultListener);
if (deltasToSign.size() >= maximumDeltaBundleSize) {
acceptMoreDeltas.set(false);
// If there is a task scheduled, attempt to cancel, but do not interrupt if it's
// already executing. If cancel() returns false the task has already been run.
if (scheduledFuture == null || scheduledFuture.cancel(false)) {
executorService.execute(signingTask);
}
} else if (scheduledFuture == null){
scheduledFuture = executorService.schedule(signingTask, bundlingAccumulationDelayMs,
TimeUnit.MILLISECONDS);
}
}
}
/**
* Constructor.
* @param executorService a ScheduledExecutorService, may be null if maximumDeltaBundleSize = 1
* @param signer wave signer
* @param bundlingAccumulationDelayMs hold deltas at most for this long
* @param maximumDeltaBundleSize largest size for a bundle
*/
BundlingDeltaSigner(ScheduledExecutorService executorService,
WaveSigner signer,
int maximumDeltaBundleSize,
int bundlingAccumulationDelayMs) {
this.executorService = executorService;
this.signer = signer;
this.currentBundle = null;
Preconditions.checkArgument(maximumDeltaBundleSize > 1);
this.maximumDeltaBundleSize = maximumDeltaBundleSize;
Preconditions.checkArgument(bundlingAccumulationDelayMs > 0);
this.bundlingAccumulationDelayMs = bundlingAccumulationDelayMs;
}
@Override
synchronized public void sign(ByteStringMessage<ProtocolWaveletDelta> delta,
CertificateManager.SignatureResultListener resultListener) {
if (currentBundle == null || !currentBundle.canAcceptMoreDeltas()) {
// If the old bundle has a pending task it will only be garbage collected once it's
// signed all deltas because the scheduler has a handle to it.
currentBundle = new DeltaBundle();
}
currentBundle.addDelta(delta, resultListener);
}
}