package de.swm.gwt.client.mobile.keystore.impl;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.storage.client.StorageEvent;
import com.google.gwt.user.client.rpc.AsyncCallback;
import de.swm.gwt.client.mobile.keystore.IStorage;
import de.swm.gwt.client.mobile.keystore.IStorageOperationCompleted;
import de.swm.gwt.client.mobile.keystore.ITransaction;
import java.util.*;
/**
* Wrapper um eine <code>IStroage</code> implementirung transkational zu behandeln.
*
* @author wiese.daniel
* <br>
* copyright (C) 2012, Stadtwerke München GmbH
*/
public class TransactionalStorage implements IStorage {
/**
* contains a set of new keys (entries). *
*/
private Map<String, String> storage = new HashMap<String, String>();
/**
* contains a set of all removed keys (entries). *
*/
private Map<String, String> removedItems = new HashMap<String, String>();
/**
* contains a set of all changed keys (entries). *
*/
private Set<String> changedKeys = new HashSet<String>();
public boolean clearWasCalled = false;
private final IStorage wrappedStorage;
private boolean isInTransaction = false;
/**
* Default Konstructor.
*/
public TransactionalStorage(IStorage storage) {
wrappedStorage = storage;
}
@Override
public void clear() {
if (isInTransaction) {
clearWasCalled = true;
storage.clear();
changedKeys.clear();
} else {
wrappedStorage.clear();
}
}
/**
* Holt die Daten für einen bestimmten Schlüssel.
*
* @param key .
* @return .
*/
@Override
public String getItem(String key) {
if (isInTransaction) {
if (storage.containsKey(key)) {
return storage.get(key);
} else {
if (!clearWasCalled) {
if (!removedItems.containsKey(key)) {
return wrappedStorage.getItem(key);
}
}
}
return null;
} else {
return wrappedStorage.getItem(key);
}
}
/**
* Gibt die Anzahl der gespeicherten Elemente zurück.
*
* @return .
*/
@Override
public int getLength() {
if (isInTransaction) {
int length = 0;
if (!clearWasCalled) {
length += wrappedStorage.getLength();
length -= changedKeys.size();
}
return length += storage.size();
} else {
return wrappedStorage.getLength();
}
}
/**
* Löscht den Wert für einen bestimmten Schlüssel.
*
* @param key .
*/
@Override
public void removeItem(String key) {
if (isInTransaction) {
final String itemToRemove = wrappedStorage.getItem(key);
if (itemToRemove != null) {
removedItems.put(key, itemToRemove);
}
} else {
wrappedStorage.removeItem(key);
}
}
/**
* Speichert einen String für einen bestimmten Schlüssel (überschreibt falls vorhanden).
*
* @param key .
* @param data .
*/
@Override
public void setItem(String key, String data) {
if (isInTransaction) {
storage.put(key, data);
if (wrappedStorage.getItem(key) != null) {
changedKeys.add(key);
}
} else {
wrappedStorage.setItem(key, data);
}
}
@Override
public ITransaction beginTransaction() {
isInTransaction = true;
if (wrappedStorage != null && wrappedStorage.isLocalStorageSupported()) {
return new ITransaction() {
@Override
public void rollback() {
storage.clear();
removedItems.clear();
clearWasCalled = false;
isInTransaction = false;
}
@Override
public void commit(IStorageOperationCompleted callback) {
final CommitChain chain = new CommitChain(callback);
chain.addPart(new IChainPart() {
@Override
public void startPart(IStorageOperationCompleted callback) {
if (clearWasCalled) {
wrappedStorage.clearAsync(callback);
} else {
callback.isCompleted();
}
}
});
chain.addPart(new IChainPart() {
@Override
public void startPart(IStorageOperationCompleted callback) {
wrappedStorage.removeItemsAsync(removedItems.keySet(), callback);
}
});
chain.addPart(new IChainPart() {
@Override
public void startPart(IStorageOperationCompleted callback) {
wrappedStorage.setItemsAsync(storage, callback);
}
});
chain.addPart(new IChainPart() {
@Override
public void startPart(IStorageOperationCompleted callback) {
storage.clear();
removedItems.clear();
clearWasCalled = false;
isInTransaction = false;
callback.isCompleted();
}
});
chain.executeChain();
}
};
} else {
throw new IllegalArgumentException("Transactions are not supported by the underlying storage");
}
}
/**
* Hanlder for uncought exceptions.
*
* @param handler
*/
@Override
public void addUncaughtExceptionHanlder(GWT.UncaughtExceptionHandler handler) {
wrappedStorage.addUncaughtExceptionHanlder(handler);
}
/**
* Will initialize the local storage implementation.
*
* @param afterInitHanlder
*/
@Override
public void initialize(AsyncCallback<Void> afterInitHanlder) {
wrappedStorage.initialize(afterInitHanlder);
}
/**
* Registriert einen Handler für die StorageEvents.
*
* @param handler .
* @return .
*/
@Override
public HandlerRegistration addStorageEventHandler(StorageEvent.Handler handler) {
return wrappedStorage.addStorageEventHandler(handler);
}
/**
* Liefert den local Storage zurück.
*
* @return .
*/
@Override
public IStorage getLocalStorageIfSupported() {
return this;
}
/**
* Prüft ob local storage vorhanden ist.
*
* @return true - wenn ja,false - wenn nein
*/
@Override
public boolean isLocalStorageSupported() {
return wrappedStorage.isLocalStorageSupported();
}
/**
* Löscht einen bestimmten Handler, der auf StorageEvents horcht.
*
* @param handler .
*/
@Override
public void removeStorageEventHandler(StorageEvent.Handler handler) {
wrappedStorage.removeStorageEventHandler(handler);
}
/**
* Returns a key corresponding to the index position.
*
* @param index .
* @return .
*/
@Override
public String key(int index) {
throw new IllegalArgumentException("Unsupported Operation");
}
/**
* Will delete the underlying storage asynchronously.
*
* @param callback callback when completed
*/
@Override
public void clearAsync(IStorageOperationCompleted callback) {
throw new IllegalArgumentException("Unsupported Operation");
}
/**
* @param keys the keys to delete.
* @param callback callback when completed
*/
@Override
public void removeItemsAsync(Set<String> keys, IStorageOperationCompleted callback) {
throw new IllegalArgumentException("Unsupported Operation");
}
/**
* @param values the values to store.
* @param callback callback when completed
*/
@Override
public void setItemsAsync(Map<String, String> values, IStorageOperationCompleted callback) {
throw new IllegalArgumentException("Unsupported Operation");
}
@Override
public Set<String> getKeys() {
if (isInTransaction) {
throw new IllegalArgumentException("Keyset not deterministic whilst in transaction.");
} else {
return wrappedStorage.getKeys();
}
}
/**
* This class is responsible for:
* Chaning callbacks - each chained callback is execuded afer the previous callback will complete.
*/
public static class CommitChain {
private final IStorageOperationCompleted clientNotification;
private final Queue<IChainPart> parts = new LinkedList<IChainPart>();
public CommitChain(IStorageOperationCompleted clientNotification) {
this.clientNotification = clientNotification;
}
public void addPart(IChainPart part) {
parts.add(part);
}
public void executeChain() {
IChainPart part = parts.poll();
if (part != null) {
final IStorageOperationCompleted callback = new IStorageOperationCompleted() {
@Override
public void isCompleted() {
executeChain();
}
};
part.startPart(callback);
} else {
clientNotification.isCompleted();
}
}
}
/**
* Dafined a part of a cllabck chain.
*/
public static interface IChainPart {
/**
* Will start a part.
* @param callback the complete callback.
*/
void startPart(IStorageOperationCompleted callback);
}
}