package org.vaadin.touchkit.extensions;
import java.util.HashMap;
import org.vaadin.touchkit.extensions.LocalStorageCallback.FailureEvent;
import org.vaadin.touchkit.gwt.client.vcom.LocalStorageClientRpc;
import org.vaadin.touchkit.gwt.client.vcom.LocalStorageServerRpc;
import com.google.gwt.storage.client.Storage;
import com.vaadin.server.AbstractExtension;
import com.vaadin.server.Extension;
import com.vaadin.ui.UI;
/**
* A server side proxy for the browser's HTML5 local storage. Local storage is a
* persistent string-string map provided by modern browsers. Using this
* extension, server side code can access that map.
* <p>
* Local storage can be used bit like traditional cookies to store data on the
* browser, but with LocalStorage one can store much larger data and the stored
* data is not transferred in each HTTP request. Also if you have hybrid
* client-server application, you can use this class to easily fetch date stored
* by your client side module.
* <p>
* For more details about HTML 5 storage see {@link Storage}.
* <p>
* To save a value in browser use {@link #put(String, String)} method.
* <p>
* As the values are behind network, retrieving a value must be done using
* asynchronous AP. E.g. like this:
* <code>
LocalStorage.detectValue("myproperty",
new LocalStorageCallback() {
public void onSuccess(String value) {
Notification.show("Value received:" + value);
}
public void onFailure(FailureEvent error) {
Notification.show("Value retrieval failed: " + error.getMessage());
}
});
* </code>
*
*/
@SuppressWarnings("serial")
public class LocalStorage extends AbstractExtension {
private int requests;
private HashMap<Integer, LocalStorageCallback> callbacks;
/**
* Detects the value of the given key in the client side HTML5 storage. The
* value is detected asynchronously, as the value detection requires a
* client server round trip.
* <p>
* This method uses thread local to get the currently active UI.
*
* @see #get(String, LocalStorageCallback)
*/
public static void detectValue(String key, LocalStorageCallback callback) {
get().get(key, callback);
}
/**
* Returns a local storage proxy bound to the currently active UI (detected
* via a thread local).
*
* @return an existing or newly created instance for the currently active UI
*/
public static LocalStorage get() {
UI ui = UI.getCurrent();
return get(ui);
}
/**
* Returns a local storage proxy bound to the given UI.
*
* @param ui
* the UI to bind to.
* @return A LocalStorage extension bound to the given UI. If an extension
* is not yet applied, a new one is created and applied to the UI.
*/
public static LocalStorage get(UI ui) {
if (ui == null) {
throw new IllegalArgumentException("A UI must be provided");
}
LocalStorage locator = null;
// Do we already have an extension attached?
for (Extension e : ui.getExtensions()) {
if (e instanceof LocalStorage) {
locator = (LocalStorage) e;
}
}
// Attach if none found.
if (locator == null) {
locator = new LocalStorage();
locator.extend(UI.getCurrent());
}
return locator;
}
private LocalStorage() {
registerRpc(new LocalStorageServerRpc() {
@Override
public void onValueDetected(int requestId, String value) {
callbacks.remove(requestId).onSuccess(value);
}
@Override
public void onValueDetectionFailure(int requestId,
final String message) {
callbacks.remove(requestId).onFailure(
new LocalStorageCallback.FailureEvent() {
@Override
public String getMessage() {
return message;
}
});
}
@Override
public void putSucceeded(int requestId, String value) {
if (callbacks != null) {
LocalStorageCallback localStorageCallback = callbacks
.remove(requestId);
if (localStorageCallback != null) {
localStorageCallback.onSuccess(value);
}
}
}
@Override
public void putFailed(int requestId, final String message) {
if (callbacks != null) {
LocalStorageCallback localStorageCallback = callbacks
.remove(requestId);
if (localStorageCallback != null) {
localStorageCallback.onFailure(new FailureEvent() {
@Override
public String getMessage() {
return message;
}
});
}
}
}
});
}
/**
* Detects the value of the given key in the client side HTML5 storage. The
* value is detected asynchronously, as the value detection requires a
* client server round trip.
*
* @param callback
* The {@link LocalStorageCallback} called when the value is
* available.
*/
public void get(String key, LocalStorageCallback callback) {
int requestId = nextRequestId();
if (callbacks == null) {
callbacks = new HashMap<Integer, LocalStorageCallback>();
}
callbacks.put(requestId, callback);
getRpcProxy(LocalStorageClientRpc.class).detectValue(requestId, key);
}
/**
* Stores a given key-value pair in the browser's local storage. Any
* previous value is overridden.
* <p>
* Note that if you wish to be sure that the value is persisted properly,
* you can use {@link #put(String, String, LocalStorageCallback)}.
*
* @param key
* The key
* @param value
* The value for the key
*/
public void put(String key, String value) {
getRpcProxy(LocalStorageClientRpc.class).put(nextRequestId(), key,
value);
}
/**
* Stores a given key-value pair in the browser's local storage. Any
* previous value is overridden.
* <p>
* The callback given as parameter is notified if local storage access
* succeeded or failed.
*
* @param key
* The key
* @param value
* The value for the key
* @param callback
* A {@link LocalStorageCallback} to notify of success or
* failure.
*/
public void put(String key, String value, LocalStorageCallback callback) {
int requestId = nextRequestId();
if (callbacks == null) {
callbacks = new HashMap<Integer, LocalStorageCallback>();
}
callbacks.put(requestId, callback);
getRpcProxy(LocalStorageClientRpc.class).put(requestId, key, value);
}
private int nextRequestId() {
return requests++;
}
}