/**
* 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 cc.kune.kunecli.cmds;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import org.waveprotocol.box.common.ExceptionalIterator;
import org.waveprotocol.box.server.persistence.PersistenceException;
import org.waveprotocol.box.server.waveserver.DeltaStore;
import org.waveprotocol.box.server.waveserver.DeltaStore.DeltasAccess;
import org.waveprotocol.box.server.waveserver.WaveletDeltaRecord;
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.version.HashedVersion;
import org.waveprotocol.wave.util.logging.Log;
import com.google.common.collect.ImmutableSet;
import com.sun.xml.internal.rngom.parse.compact.EOFException;
/**
*
* An utility class to copy all deltas between storages. Already existing Waves
* in the target store wont be changed.
*
* It is NOT an incremental process.
*
* @author pablojan@gmail.com (Pablo Ojanguren)
*
*/
public class CustomDeltaMigrator {
private static final Log LOG = Log.get(CustomDeltaMigrator.class);
private Progressbar bar;
int countError = 0;
int countMigrated = 0;
int countMigratedBecausePartial = 0;
int countSkipped = 0;
protected DeltaStore sourceStore = null;
protected DeltaStore targetStore = null;
public CustomDeltaMigrator(final DeltaStore sourceStore, final DeltaStore targetStore) {
this.sourceStore = sourceStore;
this.targetStore = targetStore;
}
private String getStats() {
return "migrated/skipped/partial/error: " + countMigrated + "/" + countSkipped + "/"
+ countMigratedBecausePartial + "/" + countError;
}
private void printStats() {
LOG.info("Total " + getStats());
}
private void processCorruptedWave(final WaveId waveId, final Exception e) {
countError++;
LOG.info("Waveid with errors: " + waveId.toString());
// throw new RuntimeException(e);
}
public void run() {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
printStats();
}
});
LOG.info("Starting Wave migration from " + sourceStore.getClass().getSimpleName() + " to "
+ targetStore.getClass().getSimpleName());
final long startTime = System.currentTimeMillis();
try {
// We need the total number of waves to make a progress bar
int waveCount = 0;
final ExceptionalIterator<WaveId, PersistenceException> countIterator = sourceStore.getWaveIdIterator();
while (countIterator.hasNext()) {
waveCount++;
countIterator.next();
}
final ExceptionalIterator<WaveId, PersistenceException> srcItr = sourceStore.getWaveIdIterator();
final File stopFile = new File(
System.getProperty("java.io.tmpdir") + "/wave-mongo-migration-stop");
bar = new Progressbar(waveCount, "Migrating " + waveCount + " Waves");
int currentWave = 0;
// Waves
while (srcItr.hasNext()) {
currentWave++;
if (stopFile.exists()) {
LOG.info("Stopping Wave migration as requested. Partial migration.");
break;
}
bar.setVal(currentWave);
final WaveId waveId = srcItr.next();
final ImmutableSet<WaveletId> sourceLookup = sourceStore.lookup(waveId);
final ImmutableSet<WaveletId> targetLookup = targetStore.lookup(waveId);
final ImmutableSet<WaveletId> waveletIds = sourceLookup;
boolean wavesAreNotTheSame = false;
if (!targetLookup.isEmpty()) {
if (targetLookup.size() != sourceLookup.size()) {
wavesAreNotTheSame = true;
} else {
// Comparing source and target deltas
for (final WaveletId waveletId : waveletIds) {
setStatus("comparing");
bar.setVal(currentWave);
final DeltasAccess sourceDeltas = sourceStore.open(WaveletName.of(waveId, waveletId));
final DeltasAccess targetDeltas = targetStore.open(WaveletName.of(waveId, waveletId));
final HashedVersion deltaResultingVersion = sourceDeltas.getEndVersion();
final HashedVersion deltaResultingTargetVersion = targetDeltas.getEndVersion();
if (!deltaResultingVersion.equals(deltaResultingTargetVersion)) {
wavesAreNotTheSame = true;
break;
}
}
if (wavesAreNotTheSame) {
for (final WaveletId targetWaveletId : targetLookup) {
// LOG.info(
// "Deleting and appending Wave in target because it's found and
// has not the same size: "
// + waveId.toString());
targetStore.delete(WaveletName.of(waveId, targetWaveletId));
}
// Continue migrating that wave
} else {
setStatus("skipping already migrated");
bar.setVal(currentWave);
countSkipped++;
continue;
}
}
}
if (wavesAreNotTheSame) {
setStatus("migrating partial wave");
countMigratedBecausePartial++;
} else {
setStatus("migrating");
countMigrated++;
}
setStatus(wavesAreNotTheSame ? "migrating partial wave" : "migrating");
// LOG.info("--- Migrating Wave " + waveId.toString() + " with " +
// waveletIds.size() + " wavelets");
// final long waveStartTime = System.currentTimeMillis();
//
// Wavelets
try {
for (final WaveletId waveletId : waveletIds) {
bar.setVal(currentWave);
// LOG.info(
// "Migrating wavelet " + waveletsCount + "/" + waveletsTotal + " :
// "
// + waveletId.toString());
final DeltasAccess sourceDeltas = sourceStore.open(WaveletName.of(waveId, waveletId));
final DeltasAccess targetDeltas = targetStore.open(WaveletName.of(waveId, waveletId));
// Get all deltas from last version to initial version (0): reverse
// order
// int deltasCount = 0;
final ArrayList<WaveletDeltaRecord> deltas = new ArrayList<WaveletDeltaRecord>();
HashedVersion deltaResultingVersion = sourceDeltas.getEndVersion();
// Deltas
while (deltaResultingVersion != null && deltaResultingVersion.getVersion() != 0) {
// deltasCount++;
final WaveletDeltaRecord deltaRecord = sourceDeltas.getDeltaByEndVersion(
deltaResultingVersion.getVersion());
deltas.add(deltaRecord);
// get the previous delta, this is the appliedAt
deltaResultingVersion = deltaRecord.getAppliedAtVersion();
}
// LOG.info("Appending " + deltasCount + " deltas to target");
targetDeltas.append(deltas);
}
} catch (final EOFException e) {
processCorruptedWave(waveId, e);
} catch (final PersistenceException e) {
processCorruptedWave(waveId, e);
} catch (final IOException e) {
processCorruptedWave(waveId, e);
}
} // While Waves
bar.finish();
final long endTime = System.currentTimeMillis();
LOG.info("Migration completed. Total time = " + (endTime - startTime) + "ms");
printStats();
} catch (final PersistenceException e) {
LOG.warning("Error iterating over waves");
throw new RuntimeException(e);
}
}
private void setStatus(final String status) {
bar.setStatus(status, getStats());
}
}