package org.mozilla.android.sync.test.helpers;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.mozilla.gecko.sync.Logger;
import org.mozilla.gecko.sync.repositories.InactiveSessionException;
import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
import org.mozilla.gecko.sync.repositories.RecordFilter;
import org.mozilla.gecko.sync.repositories.Repository;
import org.mozilla.gecko.sync.repositories.StoreTrackingRepositorySession;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
import org.mozilla.gecko.sync.repositories.domain.Record;
import android.content.Context;
public class WBORepository extends Repository {
public class WBORepositoryStats {
public long created = -1;
public long begun = -1;
public long fetchBegan = -1;
public long fetchCompleted = -1;
public long storeBegan = -1;
public long storeCompleted = -1;
public long finished = -1;
}
public static final String LOG_TAG = "WBORepository";
// Access to stats is not guarded.
public WBORepositoryStats stats;
// Whether or not to increment the timestamp of stored records.
public final boolean bumpTimestamps;
public class WBORepositorySession extends StoreTrackingRepositorySession {
protected WBORepository wboRepository;
protected ExecutorService delegateExecutor = Executors.newSingleThreadExecutor();
public ConcurrentHashMap<String, Record> wbos;
public WBORepositorySession(WBORepository repository) {
super(repository);
wboRepository = repository;
wbos = new ConcurrentHashMap<String, Record>();
stats = new WBORepositoryStats();
stats.created = now();
}
@Override
protected synchronized void trackGUID(String guid) {
if (wboRepository.shouldTrack()) {
super.trackGUID(guid);
}
}
@Override
public void guidsSince(long timestamp,
RepositorySessionGuidsSinceDelegate delegate) {
throw new RuntimeException("guidsSince not implemented.");
}
@Override
public void fetchSince(long timestamp,
RepositorySessionFetchRecordsDelegate delegate) {
long fetchBegan = now();
stats.fetchBegan = fetchBegan;
RecordFilter filter = storeTracker.getFilter();
for (Entry<String, Record> entry : wbos.entrySet()) {
Record record = entry.getValue();
if (record.lastModified >= timestamp) {
if (filter != null &&
filter.excludeRecord(record)) {
Logger.debug(LOG_TAG, "Excluding record " + record.guid);
continue;
}
delegate.deferredFetchDelegate(delegateExecutor).onFetchedRecord(record);
}
}
long fetchCompleted = now();
stats.fetchCompleted = fetchCompleted;
delegate.deferredFetchDelegate(delegateExecutor).onFetchCompleted(fetchCompleted);
}
@Override
public void fetch(final String[] guids,
final RepositorySessionFetchRecordsDelegate delegate) {
long fetchBegan = now();
stats.fetchBegan = fetchBegan;
for (String guid : guids) {
if (wbos.containsKey(guid)) {
delegate.deferredFetchDelegate(delegateExecutor).onFetchedRecord(wbos.get(guid));
}
}
long fetchCompleted = now();
stats.fetchCompleted = fetchCompleted;
delegate.deferredFetchDelegate(delegateExecutor).onFetchCompleted(fetchCompleted);
}
@Override
public void fetchAll(final RepositorySessionFetchRecordsDelegate delegate) {
long fetchBegan = now();
stats.fetchBegan = fetchBegan;
for (Entry<String, Record> entry : wbos.entrySet()) {
Record record = entry.getValue();
delegate.deferredFetchDelegate(delegateExecutor).onFetchedRecord(record);
}
long fetchCompleted = now();
stats.fetchCompleted = fetchCompleted;
delegate.deferredFetchDelegate(delegateExecutor).onFetchCompleted(fetchCompleted);
}
@Override
public void store(final Record record) throws NoStoreDelegateException {
if (delegate == null) {
throw new NoStoreDelegateException();
}
final long now = now();
if (stats.storeBegan < 0) {
stats.storeBegan = now;
}
Record existing = wbos.get(record.guid);
Logger.debug(LOG_TAG, "Existing record is " + (existing == null ? "<null>" : (existing.guid + ", " + existing)));
if (existing != null &&
existing.lastModified > record.lastModified) {
Logger.debug(LOG_TAG, "Local record is newer. Not storing.");
delegate.deferredStoreDelegate(delegateExecutor).onRecordStoreSucceeded(record.guid);
return;
}
if (existing != null) {
Logger.debug(LOG_TAG, "Replacing local record.");
}
// Store a copy of the record with an updated modified time.
Record toStore = record.copyWithIDs(record.guid, record.androidID);
if (bumpTimestamps) {
toStore.lastModified = now;
}
wbos.put(record.guid, toStore);
trackRecord(toStore);
delegate.deferredStoreDelegate(delegateExecutor).onRecordStoreSucceeded(record.guid);
}
@Override
public void wipe(final RepositorySessionWipeDelegate delegate) {
if (!isActive()) {
delegate.onWipeFailed(new InactiveSessionException(null));
return;
}
Logger.info(LOG_TAG, "Wiping WBORepositorySession.");
this.wbos = new ConcurrentHashMap<String, Record>();
// Wipe immediately for the convenience of test code.
wboRepository.wbos = new ConcurrentHashMap<String, Record>();
delegate.deferredWipeDelegate(delegateExecutor).onWipeSucceeded();
}
@Override
public void finish(RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
Logger.info(LOG_TAG, "Finishing WBORepositorySession: handing back " + this.wbos.size() + " WBOs.");
wboRepository.wbos = this.wbos;
stats.finished = now();
super.finish(delegate);
}
@Override
public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
this.wbos = wboRepository.cloneWBOs();
stats.begun = now();
super.begin(delegate);
}
@Override
public void storeDone(long end) {
// TODO: this is not guaranteed to be called after all of the record
// store callbacks have completed!
if (stats.storeBegan < 0) {
stats.storeBegan = end;
}
stats.storeCompleted = end;
delegate.deferredStoreDelegate(delegateExecutor).onStoreCompleted(end);
}
}
public ConcurrentHashMap<String, Record> wbos;
public WBORepository(boolean bumpTimestamps) {
super();
this.bumpTimestamps = bumpTimestamps;
this.wbos = new ConcurrentHashMap<String, Record>();
}
public WBORepository() {
this(false);
}
public synchronized boolean shouldTrack() {
return false;
}
@Override
public void createSession(RepositorySessionCreationDelegate delegate,
Context context) {
delegate.deferredCreationDelegate().onSessionCreated(new WBORepositorySession(this));
}
public ConcurrentHashMap<String, Record> cloneWBOs() {
ConcurrentHashMap<String, Record> out = new ConcurrentHashMap<String, Record>();
for (Entry<String, Record> entry : wbos.entrySet()) {
out.put(entry.getKey(), entry.getValue()); // Assume that records are
// immutable.
}
return out;
}
}