package org.waveprotocol.box.server.persistence.mongodb;

import org.waveprotocol.box.common.comms.WaveClientRpc.WaveletSnapshot;
import org.waveprotocol.box.server.common.SnapshotSerializer;
import org.waveprotocol.box.server.persistence.PersistenceException;
import org.waveprotocol.box.server.waveserver.DeltaStore;
import org.waveprotocol.wave.model.id.InvalidIdException;
import org.waveprotocol.wave.model.id.ModernIdSerialiser;
import org.waveprotocol.wave.model.id.WaveletName;
import org.waveprotocol.wave.model.operation.OperationException;
import org.waveprotocol.wave.model.util.Preconditions;
import org.waveprotocol.wave.model.wave.InvalidParticipantAddress;
import org.waveprotocol.wave.model.wave.data.ReadableWaveletData;
import org.waveprotocol.wave.model.wave.data.WaveletData;
import org.waveprotocol.wave.util.logging.Log;

import com.google.protobuf.InvalidProtocolBufferException;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.MongoException;
import com.mongodb.WriteConcern;

/**
 * A MongoDB-backed store of Wavelet snapshots.
 * The aim is to avoid the whole processing of deltas when a
 * wavelet is loaded into server's memory for the first time.
 * 
 * This class is not thread-safe.
 * 
 * @author pablojan@gmail.com (Pablo Ojanguren)
 *
 */
public class MongoDBSnapshotStore {
  
  private static final Log LOG = Log.get(MongoDBSnapshotStore.class);
  
  protected static final String WAVE_ID_FIELD  = "waveid";
  protected static final String WAVELET_ID_FIELD = "waveletid";
  protected static final String VERSION_FIELD = "version";
  protected static final String VERSION_HASH_FIELD = "versionhash";
  protected static final String LASTMOD_FIELD = "lastmod";
  
  protected static final String SNAPSHOT_DATA = "data";

  
  /** Name of the MongoDB collection to store Deltas */
  protected static final String SNAPSHOT_COLLECTION = "snapshots";


  private final DBCollection collection;

  /**
   * Get a reference to the snapshots store.
   * 
   * @param database
   * @return
   */
  public static MongoDBSnapshotStore create(DB database) {
      Preconditions.checkArgument(database != null, "Unable to get reference to mongoDB snapshots collection");
      DBCollection collection = database.getCollection(SNAPSHOT_COLLECTION);
      return new MongoDBSnapshotStore(collection);
  }
  
  /**
   * Construct a new snapshots store.
   *
   * @param database the database connection object
   */
  protected MongoDBSnapshotStore(DBCollection collection) {
    this.collection = collection;
  }  
  
  protected void deleteSnapshot(WaveletName waveletName) throws PersistenceException {
    
    DBObject criteria = new BasicDBObject();
    criteria.put(WAVE_ID_FIELD, ModernIdSerialiser.INSTANCE.serialiseWaveId(waveletName.waveId));
    criteria.put(WAVELET_ID_FIELD, ModernIdSerialiser.INSTANCE.serialiseWaveletId(waveletName.waveletId));

    try {
      // Using Journaled Write Concern
      // (http://docs.mongodb.org/manual/core/write-concern/#journaled)
      collection.remove(criteria, WriteConcern.JOURNALED);
    } catch (MongoException e) {
      throw new PersistenceException(e);
    }
  }
  
  /**
   * Store a snapshot
   * 
   * @param waveletData
   * @param hashedVersion
   * @throws PersistenceException 
   */
  public void store(ReadableWaveletData waveletData) throws PersistenceException {
    
    String waveId = ModernIdSerialiser.INSTANCE.serialiseWaveId(waveletData.getWaveId());
    String waveletId = ModernIdSerialiser.INSTANCE.serialiseWaveletId(waveletData.getWaveletId());
    
  
    // store new snapshot    
    BasicDBObject dbo = new BasicDBObject();
    dbo.put(WAVE_ID_FIELD, ModernIdSerialiser.INSTANCE.serialiseWaveId(waveletData.getWaveId()));
    dbo.put(WAVELET_ID_FIELD, ModernIdSerialiser.INSTANCE.serialiseWaveletId(waveletData.getWaveletId()));
    
    dbo.put(VERSION_FIELD, waveletData.getHashedVersion().getVersion());
    dbo.put(VERSION_HASH_FIELD, waveletData.getHashedVersion().getHistoryHash());
    dbo.put(LASTMOD_FIELD,waveletData.getLastModifiedTime());
    

    WaveletSnapshot snapshot = SnapshotSerializer.serializeWavelet(waveletData, waveletData.getHashedVersion());    
    dbo.put(SNAPSHOT_DATA,snapshot.toByteArray()); 
    
    try {
      collection.insert(dbo);
    } catch (MongoException e) {
      LOG.warning("Error storing wavelet snapshot for "+waveId+"/"+waveletId, e);
      throw new PersistenceException(e);
    }    
    
    
    // Delete previous snapshots of this wavelet
    DBObject query = new BasicDBObject();
    query.put(WAVE_ID_FIELD, waveId);
    query.put(WAVELET_ID_FIELD, waveletId);
    query.put(VERSION_FIELD, new BasicDBObject("$lt", waveletData.getHashedVersion().getVersion()));
    
    try {
        collection.remove(query);
    } catch (MongoException e) {
      LOG.warning("Error deleting outdated wavelet snapshots for "+waveId+"/"+waveletId, e);
      throw new PersistenceException(e);
    }
    
    
    
    LOG.info("Stored snaphost for "+waveId+"/"+waveletId+" version "+ waveletData.getHashedVersion().getVersion());
  }
  
  
  public DeltaStore.Snapshot load(WaveletName waveletName) throws PersistenceException {
        
    String waveId = ModernIdSerialiser.INSTANCE.serialiseWaveId(waveletName.waveId);
    String waveletId = ModernIdSerialiser.INSTANCE.serialiseWaveletId(waveletName.waveletId);
    
    // find last snapshot stored
    DBObject query = new BasicDBObject();
    query.put(WAVE_ID_FIELD, waveId);
    query.put(WAVELET_ID_FIELD, waveletId);
    
    DBObject snapshotDBObject = null;

    try {
      snapshotDBObject = collection.findOne(query);
    } catch (MongoException e) {
      LOG.warning("Error querying wavelet snapshots for "+waveId+"/"+waveletId, e);
      throw new PersistenceException(e);
    }
    
    if (snapshotDBObject == null)
      return null;
    
    
    WaveletSnapshot snapshot = null;
    try {
      snapshot = WaveletSnapshot.parseFrom((byte[]) snapshotDBObject.get(SNAPSHOT_DATA));
    } catch (InvalidProtocolBufferException e) {
      throw new PersistenceException(e);
    }
    
    try {
    
      final WaveletData waveletData = SnapshotSerializer.deserializeWavelet(snapshot, waveletName.waveId);
      
      return new DeltaStore.Snapshot() {           
  
        @Override
        public WaveletData getWaveletData() {
         
          return waveletData;
        }

      };
    
    } catch (OperationException e) {
      throw new PersistenceException(e);
    } catch (InvalidParticipantAddress e) {
      throw new PersistenceException(e);
    } catch (InvalidIdException e) {
      throw new PersistenceException(e);
    }
  }
}

@@ -5062,12 +5062,12 @@
 LOG.
+f
 in
-fo
+e
 (%22St

Set snapshot store log messages to fine level