package com.trechner.ksync4j;
import com.trechner.ksync4j.type.Syncable;
import com.trechner.ksync4j.type.SyncType;
import com.trechner.ksync4j.type.ChangeType;
import java.util.Map;
import java.util.HashMap;
import java.util.Collection;
/**
* Abstract sync server.
* <p/>
* Override it to adapt to your environment.
* <p/>
* Copyright (c) 2008 T-Rechner Ltd.
*
* @author Karl Tiller <karl@t-rechner.com>
* @see com.trechner.ksync4j.type.SyncType
*/
public abstract class AbstractSyncServer {
/**
* Sync changes between server and client.
*
* @param syncType Synchronization type.
* @throws Exception Throws exception.
* @see com.trechner.ksync4j.type.SyncType
*/
public void sync(int syncType) throws Exception {
Map<String, Syncable> clientChanges = new HashMap<String, Syncable>();
Collection<Syncable> syncables = receiveSyncablesFromClient();
for (Syncable syncable : syncables) {
clientChanges.put(syncable.getGuid(), syncable);
}
Map<String, Syncable> database = new HashMap<String, Syncable>();
syncables = getSyncablesFromDatabase();
for (Syncable syncable : syncables) {
database.put(syncable.getGuid(), syncable);
}
switch (syncType) {
case SyncType.TWO_WAY:
syncTwoWay(database, clientChanges);
break;
case SyncType.SLOW:
syncSlow(database, clientChanges);
break;
case SyncType.ONE_WAY_FROM_CLIENT:
syncOneWayFromClient(database, clientChanges);
break;
case SyncType.REFRESH_FROM_CLIENT:
syncRefreshFromClient(database, clientChanges);
break;
case SyncType.ONE_WAY_FROM_SERVER:
syncOneWayFromServer(database, clientChanges);
break;
case SyncType.REFRESH_FROM_SERVER:
syncRefreshFromServer(database, clientChanges);
break;
}
}
/**
* Must returns all syncables from database.
*
* @return Collection
*/
public abstract Collection<Syncable> getSyncablesFromDatabase();
/**
* Must clear all syncables from database.
*/
public abstract void clearDatabase();
/**
* Must save all syncables to a database.
*
* @param syncables Syncables
* @see com.trechner.ksync4j.type.Syncable
*/
public abstract void saveSyncablesToDatabase(Collection<Syncable> syncables);
/**
* Must return all syncables that were received from client.
*
* @return Collection
* @see com.trechner.ksync4j.type.Syncable
*/
public abstract Collection<Syncable> receiveSyncablesFromClient();
/**
* Must send all syncables to client.
*
* @param syncables Collection
* @see com.trechner.ksync4j.type.Syncable
*/
public abstract void sendSyncablesToClient(Collection<Syncable> syncables);
private void syncTwoWay(Map<String, Syncable> database, Map<String, Syncable> clientChanges) throws Exception {
Map<String, Syncable> resultingDatabase = new HashMap<String, Syncable>();
Map<String, Syncable> resultingClientChanges = new HashMap<String, Syncable>();
resolveConflicts(database, clientChanges, resultingDatabase, resultingClientChanges, false);
sendSyncablesToClient(resultingClientChanges.values());
saveSyncablesToDatabase(resultingDatabase.values());
}
private void syncSlow(Map<String, Syncable> database, Map<String, Syncable> clientChanges) throws Exception {
Map<String, Syncable> resultingDatabase = new HashMap<String, Syncable>();
Map<String, Syncable> resultingClientChanges = new HashMap<String, Syncable>();
resolveConflicts(database, clientChanges, resultingDatabase, resultingClientChanges, true);
sendSyncablesToClient(resultingClientChanges.values());
saveSyncablesToDatabase(resultingDatabase.values());
}
private void syncOneWayFromClient(Map<String, Syncable> database, Map<String, Syncable> clientChanges) throws Exception {
Map<String, Syncable> resultingDatabase = new HashMap<String, Syncable>();
Map<String, Syncable> resultingClientChanges = new HashMap<String, Syncable>();
resolveConflicts(database, clientChanges, resultingDatabase, resultingClientChanges, false);
saveSyncablesToDatabase(resultingDatabase.values());
}
private void syncRefreshFromClient(Map<String, Syncable> database, Map<String, Syncable> clientChanges) throws Exception {
Map<String, Syncable> resultingClientChanges = new HashMap<String, Syncable>();
for (Syncable syncable : clientChanges.values()) {
Syncable copy = syncable.getClass().newInstance();
copy.copy(syncable);
copy.setChangeType(ChangeType.NONE);
resultingClientChanges.put(copy.getGuid(), copy);
}
clearDatabase();
saveSyncablesToDatabase(resultingClientChanges.values());
}
private void syncOneWayFromServer(Map<String, Syncable> database, Map<String, Syncable> clientChanges) throws Exception {
Map<String, Syncable> resultingDatabase = new HashMap<String, Syncable>();
Map<String, Syncable> resultingClientChanges = new HashMap<String, Syncable>();
resolveConflicts(database, clientChanges, resultingDatabase, resultingClientChanges, false);
sendSyncablesToClient(resultingClientChanges.values());
}
private void syncRefreshFromServer(Map<String, Syncable> database, Map<String, Syncable> clientChanges) throws Exception {
Map<String, Syncable> resultingClientChanges = new HashMap<String, Syncable>();
for (Syncable syncable : database.values()) {
Syncable copy = syncable.getClass().newInstance();
copy.copy(syncable);
copy.setChangeType(ChangeType.ADDED);
resultingClientChanges.put(copy.getGuid(), copy);
}
sendSyncablesToClient(resultingClientChanges.values());
}
private void resolveConflicts(Map<String, Syncable> database, Map<String, Syncable> clientChanges, Map<String, Syncable> resultingDatabase, Map<String, Syncable> resultingClientChanges, boolean merge) throws Exception {
// Determine what to write to database.
for (Syncable syncable : database.values()) {
Syncable copy = syncable.getClass().newInstance();
copy.copy(syncable);
copy.setChangeType(ChangeType.NONE);
resultingDatabase.put(copy.getGuid(), copy);
}
for (String guid : clientChanges.keySet()) {
Syncable clientSyncable = clientChanges.get(guid);
Syncable databaseSyncable = database.get(guid);
switch (clientSyncable.getChangeType()) {
case ChangeType.NONE: {
if (merge) {
if (databaseSyncable == null) {
Syncable copy = clientSyncable.getClass().newInstance();
copy.copy(clientSyncable);
copy.setChangeType(ChangeType.NONE);
resultingDatabase.put(guid, copy);
}
}
break;
}
case ChangeType.ADDED: {
Syncable copy = clientSyncable.getClass().newInstance();
copy.copy(clientSyncable);
copy.setChangeType(ChangeType.NONE);
resultingDatabase.put(guid, copy);
break;
}
case ChangeType.EDITED: {
Syncable copy = clientSyncable.getClass().newInstance();
copy.copy(clientSyncable);
copy.setChangeType(ChangeType.NONE);
resultingDatabase.put(guid, copy);
break;
}
case ChangeType.DELETED: {
// Remove from database. Conflict. Client and server: DELETED/NONE, DELETED/DELETED (remove)
if (databaseSyncable == null || databaseSyncable.getChangeType() == ChangeType.NONE || databaseSyncable.getChangeType() == ChangeType.DELETED) {
resultingDatabase.remove(guid);
}
break;
}
default: {
break;
}
}
}
// Determine what to write to client.
for (String guid : database.keySet()) {
Syncable databaseSyncable = database.get(guid);
Syncable clientSyncable = clientChanges.get(guid);
switch (databaseSyncable.getChangeType()) {
case ChangeType.NONE: {
if (merge) {
if (clientSyncable == null) {
Syncable copy = databaseSyncable.getClass().newInstance();
copy.copy(databaseSyncable);
copy.setChangeType(ChangeType.ADDED);
resultingClientChanges.put(guid, copy);
}
}
break;
}
case ChangeType.ADDED: {
// Send editions to client. Conflict. Client and server: NONE/ADDED, DELETED/ADDED (send)
if (clientSyncable == null || clientSyncable.getChangeType() == ChangeType.NONE || clientSyncable.getChangeType() == ChangeType.DELETED) {
Syncable copy = databaseSyncable.getClass().newInstance();
copy.copy(databaseSyncable);
resultingClientChanges.put(guid, copy);
}
break;
}
case ChangeType.EDITED: {
// Send editions to client. Conflict. Client and server: NONE/EDITED, DELETED/EDITED (send)
if (clientSyncable == null || clientSyncable.getChangeType() == ChangeType.NONE || clientSyncable.getChangeType() == ChangeType.DELETED) {
Syncable copy = databaseSyncable.getClass().newInstance();
copy.copy(databaseSyncable);
resultingClientChanges.put(guid, copy);
}
break;
}
case ChangeType.DELETED: {
// Send editions to client. Conflict. Client and server: NONE/DELETED, DELETED/DELETED (send)
if (clientSyncable == null || clientSyncable.getChangeType() == ChangeType.NONE || clientSyncable.getChangeType() == ChangeType.DELETED) {
Syncable copy = databaseSyncable.getClass().newInstance();
copy.copy(databaseSyncable);
resultingClientChanges.put(guid, copy);
resultingDatabase.remove(guid);
}
break;
}
default: {
break;
}
}
}
}
}