com.objetdirect.tatami.client.data.AbstractDataStore.java Source code

Java tutorial

Introduction

Here is the source code for com.objetdirect.tatami.client.data.AbstractDataStore.java

Source

/*
 * Tatami: 
 * Copyright (C) 2007 Objet Direct
 * Copyright (C) 2007 France Telecom
 * Contact: tatami@googlegroups.com
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * Authors:  Ronan Dunklau
 * Initial developer(s): Ronan Dunklau
 * Contributor(s):
 */
package com.objetdirect.tatami.client.data;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.gwt.core.client.JavaScriptObject;
import com.objetdirect.tatami.client.DateUtil;
import com.objetdirect.tatami.client.DojoController;
import com.objetdirect.tatami.client.HasDojo;
import com.objetdirect.tatami.client.JSHelper;

public abstract class AbstractDataStore implements HasDojo, FetchEventSource, DataStore {

    /**    
     * LastRequest performed. Used to keep parameters when we use pagination
     * 
     */
    protected Request lastRequest = new Request();

    /**
     * The DataStore items : the key is the item's idAttribute value, 
     *    while the item itself is an {@link Item}.
     */
    protected Map<Object, Item> items = new HashMap<Object, Item>();

    /**
     * The fetch listeners. They observe the fetch process
     * @see FetchListener
     */
    protected List<FetchListener> fetchListeners = new ArrayList<FetchListener>();

    /**
     * The load listeners. They observe the loading process
     */
    protected List<LoadItemListener> loadListeners = new ArrayList<LoadItemListener>();

    /**
     * The Datum change listeners. They are notified whenever an item is 
     * added , deleted or modified
     * @see DatumChangeListener
     */
    protected List<DatumChangeListener> datumChangeListeners = new ArrayList<DatumChangeListener>();

    /**
     * The underlying Dojo Object
     * 
     */
    protected JavaScriptObject dojoStore;

    /**
     * Attribute used to retrieve an Item identity
     */
    private String idAttribute;

    /**
     * Attribute used to retrieve an Item Label
     */
    private String labelAttribute;

    /**
     * @param readStoreClassName : The name used to declare the generated dojo class
     */
    public AbstractDataStore() {
        DojoController.getInstance().loadDojoWidget(this);
        doAfterCreation();
    }

    /**
     * @param idAttribute : Attribute used to retrieve an Item identity
     * @param labelAttribute : Attribute used to retrieve an Item Label
     */
    @Deprecated
    public AbstractDataStore(String idAttribute, String labelAttribute) {
        this();
        this.idAttribute = idAttribute;
        this.labelAttribute = labelAttribute;
    }

    /**
     * This method is used for developper convenience to declare 
     * a new Dojo DataStore class without the need to know anything about dojo. 
     * It declares the methods required to conform to Dojo data APIs, and connect
     * them to the corresponding GWT methods
     * @param dojoReadStoreClassName : The name used to declare the generated dojo class
     * 
     */
    private native void defineReadStore(String dojoReadStoreClassName)/*-{
                                                                      $wnd.dojo.declare("dojox.data.store.TatamiDataStore", null   , {
                                                                      getValue: function (item,attribute,defaultValue) {
                                                                      var newDefaultValue = (defaultValue == undefined ? null : defaultValue);
                                                                      var toReturn = this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::dojoGetValue(Lcom/objetdirect/tatami/client/data/Item;Ljava/lang/String;Ljava/lang/Object;)(item,attribute,newDefaultValue);
                                                                      if(@com.objetdirect.tatami.client.JSHelper::isBoolean(Ljava/lang/Object;)(toReturn)){
                                                                      if(toReturn +"" == "true"){
                                                                      toReturn = true;
                                                                      }else{
                                                                      toReturn = false;
                                                                      }
                                                                      }
                                                                      return toReturn;
                                                                      },
                                                                      isItem: function (item) {
                                                                      return this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::isItem(Ljava/lang/Object;)(item);
                                                                      },
                                                                      getValues: function(item,attribute) {
                                                                      var toReturn = this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::dojoGetValues(Lcom/objetdirect/tatami/client/data/Item;Ljava/lang/String;)(item,attribute);
                                                                      return toReturn;
                                                                      },
                                                                      getAttributes: function(item) {
                                                                      var toReturn = this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::dojoGetAttributes(Lcom/objetdirect/tatami/client/data/Item;)(item);
                                                                      return toReturn;
                                                                      },
                                                                      hasAttribute: function(item , attribute){
                                                                      return this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::hasAttribute(Lcom/objetdirect/tatami/client/data/Item;Ljava/lang/String;)(item,attribute);
                                                                      },
                                                                      containsValue: function (item , attribute , value){
                                                                      return this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::containsValue(Lcom/objetdirect/tatami/client/data/Item;Ljava/lang/String;Ljava/lang/Object;)(item,attribute,value);
                                                                      },
                                                                      isItemLoaded: function(item){
                                                                      return this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::isItemLoaded(Lcom/objetdirect/tatami/client/data/Item;)(item);
                                                                      },
                                                                      loadItem: function(keywordArgs){
                                                                      return this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::loadItem(Lcom/objetdirect/tatami/client/data/Item;)(keywordArgs.item);
                                                                      },
                                                                      fetch: function(keywordArgs){
                                                                      this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::fetch(Lcom/google/gwt/core/client/JavaScriptObject;)(keywordArgs);
                                                                      },
                                                                      fetchItemByIdentity: function(keywordArgs){
                                                                      this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::dojoFetchItemByIdentity(Lcom/google/gwt/core/client/JavaScriptObject;)(keywordArgs);
                                                                      },
                                                                      getFeatures: function(){
                                                                      return { 'dojo.data.api.Read' : true ,
                                                                      'dojo.data.api.Identity' : true ,
                                                                      'dojo.data.api.Write' : true ,
                                                                      'dojo.data.api.Notification' : true
                                                                      };
                                                                      },
                                                                      getLabel: function(item){
                                                                      var label = this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::getLabel(Lcom/objetdirect/tatami/client/data/Item;)(item);
                                                                      return label;
                                                                      },
                                                                      getLabelAttributes: function(item){
                                                                      return this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::getLabelAttributes(Lcom/objetdirect/tatami/client/data/Item;)(item);
                                                                      },
                                                                      getIdentity: function(item){
                                                                      var toReturn = this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::dojoGetIdentity(Lcom/objetdirect/tatami/client/data/Item;)(item); 
                                                                      return toReturn;
                                                                      },
                                                                      getIdentityAttributes: function(item){
                                                                      return this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::getIdentityAttributes(Lcom/objetdirect/tatami/client/data/Item;)(item);
                                                                      },
                                                                      setValue : function(item , attribute , value){
                                                                      var javavalue;
                                                                          
                                                                      //Converting primitive types to Object type
                                                                      //(Because GWT just can't convert js number into Number and boolean into Boolean)
                                                                      if(typeof value == "boolean"){
                                                                      javavalue = @java.lang.Boolean::valueOf(Ljava/lang/String;)(value+"");
                                                                      }else if(typeof value == "number"){
                                                                      javavalue = @java.lang.Double::valueOf(Ljava/lang/String;)(value+"");
                                                                      }else{
                                                                      javavalue = value;
                                                                      }
                                                                      return this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::setValue(Lcom/objetdirect/tatami/client/data/Item;Ljava/lang/String;Ljava/lang/Object;)(item,attribute,javavalue); 
                                                                      },
                                                                      setValues : function(item , attribute , values){
                                                                      return this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::dojoSetValues(Lcom/objetdirect/tatami/client/data/Item;Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)(item,attribute,values); 
                                                                      },
                                                                      onSet : function(item, attribute, oldValue, newValue){
                                                                      },
                                                                      onNew : function(item , parentInfo){
                                                                      },
                                                                      onDelete : function(item){
                                                                      },
                                                                      newItem : function(item , parentInfo){
                                                                      var item = this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::dojoAdd(Lcom/objetdirect/tatami/client/data/Item;)(item);
                                                                      this.onNew(item, parentInfo);
                                                                      return item;
                                                                      },
                                                                      deleteItem : function(item){
                                                                      this.gwtStore.@com.objetdirect.tatami.client.data.AbstractDataStore::dojoRemove(Lcom/objetdirect/tatami/client/data/Item;)(item);
                                                                      this.onDelete(item);
                                                                      }
                                                                      });
                                                                      }-*/;

    /**
     * Method required by dojo. This method can be overriden by subclasses to provide their
     * own definition of what an item should be
     * @param item : the object to test
     * @return true if item is an instance of Item and the store contains this item,
     *         false otherwise
     */
    public boolean isItem(Object item) {
        boolean isItem;
        if (item instanceof Item && getItemByIdentity(getIdentity((Item) item)) != null) {
            isItem = true;
        } else {
            isItem = false;
        }
        return isItem;
    }

    /**
     * @param item : the item to get the attribute's value for
     * @param attribute : the name of the attribute
     * @param defaultValue : value to return if the item is not present, or if
     *         the item does not contain the desired attribute.
     * @return the attribute's value, or the default value.
     * 
     */
    @Deprecated
    public Object getValue(Item item, String attribute, Object defaultValue) {
        if (!isItem(item)) {
            return defaultValue;
        }
        return item.getValue(attribute, defaultValue);
    }

    /**
     * @param item : the javascript item coming from dojo 
     * @param attribute : attribute : the name of the attribute
     * @param defaultValue :  value to return if the item is not present, or if
     *         the item does not contain the desired attribute.
     * @return 
     * @see {@link #getValue(Item, String, Object)}
     */
    private Object dojoGetValue(Item item, String attribute, Object defaultValue) {
        Object value = defaultValue;
        value = item.getValue(attribute, defaultValue);
        if (value instanceof String || value instanceof Boolean || value instanceof Number) {
            return value;
        } else if (value instanceof Date) {
            return DateUtil.getJSDate((Date) value);
        } else if (value instanceof Item) {
            return value;
        } else {
            JavaScriptObject toReturn = JSHelper.convertObjectToJSObject(value);
            if (toReturn == null) {
                return value;
            }
            return toReturn;
        }
    }

    private JavaScriptObject dojoGetValues(Item item, String attribute) {
        Object value = item.getValues(attribute);
        JavaScriptObject valuesArray;
        if (value == null) {
            return JavaScriptObject.createArray();
        }
        if (value instanceof Collection || value instanceof Object[]) {
            valuesArray = JSHelper.convertObjectToJSObject(value);
        } else {
            Object[] toConvert = { value };
            valuesArray = JSHelper.convertObjectToJSObject(toConvert);
        }
        return valuesArray;
    }

    /**
     * @param item : the item on which to set the attribute 
     * @param attribute : the attribute to set
     * @param value : value to set the attribute to 
     */
    @Deprecated
    public void setValue(Item item, String attribute, Object value) {
        if (isItem(item)) {
            Object oldValue = item.getValue(attribute, null);
            item.setValue(attribute, value);
            items.put(getIdentity(item), item);
        }
    }

    /**
     * Search items by an attritbute value
     * @param attrName : the name of the attribute
     * @param attrValue : the value to match
     * @return : a List of items which match the specified attribute name / attribute value couple
     */
    public List<Item> searchItemsByAttributes(String attrName, Object attrValue) {
        List<Item> toReturn = new ArrayList<Item>();
        Collection<Item> concreteItems = items.values();
        for (Iterator<Item> iterator = concreteItems.iterator(); iterator.hasNext();) {
            Item currItem = iterator.next();
            Object value;
            value = currItem.getValue(attrName, null);
            if (value.equals(attrValue)) {
                toReturn.add(currItem);
            }
        }
        return toReturn;
    }

    /**
     * @param item : the item to get the attributes from
     * @return : an array containing the attributes names.
     */
    public String[] getAttributes(Item item) {
        if (!isItem(item)) {
            return new String[0];
        }
        return item.getAttributes();
    }

    /**
     * Simple wrapper around {@link #getAttributes(Item)} to be called by dojo
     * @param : the item to get the attributes from
     * @return : an array containing the attributes names
     */
    private JavaScriptObject dojoGetAttributes(Item item) {
        return JSHelper.convertObjectToJSObject(getAttributes(item));
    }

    /**
     * @param item 
     * @param attributeName : attribute to test
     * @return : true if the item has this attribute, 
     *           false otherwise.
     */
    public boolean hasAttribute(Item item, String attributeName) {
        if (!isItem(item)) {
            return false;
        }
        return item.hasAttribute(attributeName);
    }

    /**
     * Returns true if item's attribute contains the specified value
     * @param item
     * @param attribute
     * @param value
     * @return true if item contains the value for the given attribute, false otherwise
     */
    public boolean containsValue(Item item, String attribute, Object value) {
        if (!isItem(item)) {
            return false;
        }
        return item.containsValue(attribute, value);
    }

    /**
     * @param id : the id to search the item by
     * @return the item with the specified id, null if no one does.
     */
    public Item getItemByIdentity(Object id) {
        return items.get(id);
    }

    /**
     * @param item : the item to get the label from
     * @return the label
     */
    public String getLabel(Item item) {
        if (labelAttribute != null) {
            return item.getValue(labelAttribute).toString();
        }
        return (String) item.getLabel();
    }

    /**
     * This implementation only returns the label attribute from this item.
     * Subclasses implementor could override this method to provide other behavior
     * 
     * @param item 
     * @return an array containing the attributes used to construct the
     *    specified item's label 
     */
    public String[] getLabelAttributes(Item item) {
        if (labelAttribute != null) {
            return new String[] { labelAttribute };
        }
        return item.getLabelAttributes();
    }

    /**
     * @param item
     * @return item's identity
     */
    public Object getIdentity(Item item) {
        if (idAttribute != null) {
            return item.getValue(idAttribute);
        }
        return item.getId();
    }

    /**
     * @return the attribute used as identifier
     */
    @Deprecated
    public String getIdentityAttribute() {
        return idAttribute;
    }

    /**
     * Simple wrapper around {@link #getIdentity(Item)} to be called by dojo
     * @param item
     * @return
     */
    private Object dojoGetIdentity(Item item) {
        Object id = getIdentity(item);
        return id;
    }

    /**
     * This implementation only returns the id attribute from this item.
     * Subclasses implementor could override this method to provide other behavior
     * @param item
     * @return the names from attributes which are used to construct item's identity
     */
    public String[] getIdentityAttributes(Item item) {
        if (idAttribute != null) {
            return new String[] { idAttribute };
        }
        return item.getIdAttributes();
    }

    /**
     * Template method implemented for concrete ReadStore implementer's convenience
     * Test if the item has been loaded , and if not try to load the item.
     * If it loads it, it notify the itemFetchListeners.
     * 
     * @see com.objetdirect.tatami.client.data.ReadStore#loadItem(com.objetdirect.tatami.client.data.Item, com.objetdirect.tatami.client.data.LoadItemListener)
     */
    public boolean loadItem(Item item) {
        if (isItemLoaded(item)) {
            return false;
        } else {
            if (loadItemImpl(item)) {
                notifyLoadItemListeners(item);
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * This method has to be implemented by data store implementors.
     * It permits to load an Item if it has to. 
     * (Exemple : if the item is just a "stub" for a real item, containing only
     *  an attribute reffering to how to load the other ones (an url for exemple)).
     * 
     * @param item : the item to load
     * @return true if the item has been loaded , false otherwise.
     */
    public abstract boolean loadItemImpl(Item item);

    /**
     * @param item : the item to test
     * @return true if the item has already been loaded, false otherwise
     */
    public abstract boolean isItemLoaded(Item item);

    /**
     * Concrete implementation of the fetch method
     * @param request : the Request object to fetch the item from
     */
    public abstract void fetch(Request request);

    public void fetchItemByIdentity(Request request) {
        Object id = request.getQuery().get("identity");
        Item item = getItemByIdentity(id);
        callOnItem(request.getOnItemCallback(), item);
    }

    private native void callOnItem(JavaScriptObject onItemCallback, Item item)/*-{
                                                                              onItemCallback(item);
                                                                              }-*/;

    private void dojoFetchItemByIdentity(JavaScriptObject object) {
        Request request = new Request(object);
        fetchItemByIdentity(request);
    }

    /**
     * Simple wrapper around {@link #fetch(Request)} to be called by dojo
     * @param object : the javascript object containing the request
     */
    private void fetch(JavaScriptObject object) {
        Request request = new Request(object);
        fetch(request);
    }

    /* (non-Javadoc)
     * @see com.objetdirect.tatami.client.HasDojo#getDojoName()
     */
    public String getDojoName() {
        return "dojo.data.api.Identity";
    }

    /* (non-Javadoc)
     * @see com.objetdirect.tatami.client.HasDojo#onDojoLoad()
     */
    public void onDojoLoad() {
        defineReadStore("dojox.data.store.TatamiDataStore");
    }

    /**
     * Adds an item to the store, then fire the onNew event
     * @param item
     */
    public Item add(Item item) {
        return add(item, null);
    }

    public Item add(Item item, JavaScriptObject parentInfo) {
        Object id = getIdentity(item);
        Item oldItem = items.get(id);
        if (oldItem != null) {
            dojoAdd(item);
            String[] attributes = oldItem.getAttributes();
            for (int i = 0; i < attributes.length; i++) {
                String attr = attributes[i];
                oldItem.setValue(attr, item.getValue(attr));
            }
            return oldItem;
        } else {
            if (dojoStore != null) {
                //We use the javascript interface to add the item to the datastore,thus
                //allowing dojo to connect to any point of the add process
                callJSOnNew(item, parentInfo);
            } else {
                //We stay purely in the java world, the dojostore not being initialized now.
                dojoAdd(item);
            }
            return item;
        }
    }

    /**
     * Method used by dojo to add an item to the store
     * 
     * @param item
     * @return
     */
    private Item dojoAdd(Item item) {
        item.setStore(this);
        items.put(getIdentity(item), item);
        notifyOnNewListeners(item);
        return item;
    }

    private Item dojoRemove(Item item) {
        item.setStore(null);
        if (item.getParentItem() != null) {
            item.getParentItem().removeChild(item);
        }
        items.remove(getIdentity(item));
        notifyOnDeleteListeners(item);
        return item;
    }

    private void dojoSetValues(Item item, String attribute, JavaScriptObject values) {
        item.setValue(attribute, JSHelper.convertJSArrayToCollection(values));
    }

    /**
     * Do nothing. Called when a new Item is added to the store
     */
    public void onNew(Item item) {
    }

    /**
     * Calls underlying DojoStore's onNew method, on which dojo can connect
     * to propagate the event.
     * @param item : the javascript item to pass to dojo
     */
    protected native void callJSOnNew(Item item, JavaScriptObject parentInfo)/*-{
                                                                             this.@com.objetdirect.tatami.client.data.AbstractDataStore::dojoStore.newItem(item,parentInfo);
                                                                             }-*/;

    protected void callJSOnNew(Item item) {
        callJSOnNew(item, null);
    }

    /**
     * Fires an onSet event.
     * 
     * @param item : item on which an attribute has changed
     * @param attribute : the attribute which has changed
     * @param oldValue 
     * @param newValue
     */
    public void onSet(Item item, String attribute, Object oldValue, Object newValue) {
        boolean shouldNotify = false;
        if (newValue instanceof Collection || newValue instanceof Map) {
            shouldNotify = true;
        } else if (newValue == null && newValue != oldValue) {
            shouldNotify = true;
        } else if (!newValue.equals(oldValue)) {
            shouldNotify = true;
        }
        if (shouldNotify) {
            notifyDatumChangeListeners(item, attribute, oldValue, newValue);
            if (newValue instanceof Date) {
                newValue = DateUtil.getJSDate((Date) newValue);
            }
            if (oldValue instanceof Date) {
                oldValue = DateUtil.getJSDate((Date) oldValue);
            }
            if (dojoStore != null) {
                callJSOnSet(item, attribute, oldValue, newValue);
            }
        }

    }

    /**
     * Calls underlying DojoStore's onSet method, on which dojo can connect
     * to propagate the event.
     * 
     * @param item
     * @param attribute
     * @param oldValue
     * @param newValue
     */
    protected native void callJSOnSet(Item item, String attribute, Object oldValue, Object newValue)/*-{
                                                                                                    var jsOldValue;
                                                                                                    var jsNewValue;
                                                                                                    if(@com.objetdirect.tatami.client.JSHelper::isBoolean(Ljava/lang/Object;)(oldValue)){
                                                                                                    if(oldValue +"" == "true"){
                                                                                                    jsOldValue = true;
                                                                                                    }else{
                                                                                                    jsOldValue = false;
                                                                                                    }
                                                                                                    }else{
                                                                                                    jsOldValue = oldValue;
                                                                                                    }
                                                                                                    if(@com.objetdirect.tatami.client.JSHelper::isBoolean(Ljava/lang/Object;)(newValue)){
                                                                                                    if(newValue +"" == "true"){
                                                                                                    jsNewValue = true;
                                                                                                    }else{
                                                                                                    jsNewValue = false;
                                                                                                    }
                                                                                                    }else{
                                                                                                    jsNewValue = newValue;
                                                                                                    }
                                                                                                    this.@com.objetdirect.tatami.client.data.AbstractDataStore::dojoStore.onSet(item , attribute , jsOldValue , jsNewValue);
                                                                                                    }-*/;

    /**
     * Do nothing
     * @param item : the item which has been deleted
     */
    public void onDelete(Item item) {
    }

    /**
     * Calls underlying DojoStore's onDelete method, on which dojo can connect
     * to propagate the event.
     * 
     * @param item
     */
    protected native void callJSOnDelete(Item item)/*-{
                                                   this.@com.objetdirect.tatami.client.data.AbstractDataStore::dojoStore.deleteItem(item);
                                                   }-*/;

    /**
     * Remove an item from the store, the fires an onDelete event
     * 
     * @param item
     */
    public void remove(Item item) {
        if (dojoStore != null) {
            //We let the javascript store delete the item
            //thus allowing dojo widgets to connect to any point of the delete process
            callJSOnDelete(item);
        } else {
            //We 
            dojoRemove(item);
        }
    }

    /**
     * @return the current store's size
     */
    public int size() {
        return items.size();
    }

    /**
     * Adds a LoadItemListener
     * @param listener
     * 
     * @see {@link LoadItemListener}
     */
    public void addLoadItemListener(LoadItemListener listener) {
        loadListeners.add(listener);
    }

    /**
     * Removes a DatumChangeListener
     * @param listener
     * 
     * @see {@link LoadItemListener}
     */
    public void removeLoadItemListener(LoadItemListener listener) {
        loadListeners.remove(listener);
    }

    /**
     * Notifies the load listeners that an item has been loaded
     * 
     * @param item : the item which has been load
     * 
     */
    protected void notifyLoadItemListeners(Item item) {
        for (Iterator<LoadItemListener> iterator = loadListeners.iterator(); iterator.hasNext();) {
            LoadItemListener loadListeners = iterator.next();
            loadListeners.onLoad(item);
        }
    }

    /**
     * Adds a DatumChangeListener
     * @param listener
     * 
     * @see {@link DatumChangeListener}
     */
    public void addDatumChangeListener(DatumChangeListener listener) {
        datumChangeListeners.add(listener);
    }

    /**
     * Removes a DatumChangeListener
     * @param listener
     * 
     * @see {@link DatumChangeListener}
     */
    public void removeDatumChangeListener(DatumChangeListener listener) {
        datumChangeListeners.remove(listener);
    }

    /**
     * Notifies the datum change listeners that an item has been modified
     * 
     * @param item
     * @param attribute
     * @param oldValue
     * @param newValue
     * 
     * @see {@link DatumChangeListener}
     */
    protected void notifyDatumChangeListeners(Item item, String attribute, Object oldValue, Object newValue) {
        for (Iterator<DatumChangeListener> iterator = datumChangeListeners.iterator(); iterator.hasNext();) {
            DatumChangeListener datumChangeListener = iterator.next();
            datumChangeListener.onDataChange(item, attribute, oldValue, newValue);
        }
    }

    /**
     * Notifies the datum change listeners that an item has been added
     * 
     * @param item
     * 
     * @see {@link DatumChangeListener}
     * 
     */
    protected void notifyOnNewListeners(Item item) {
        for (Iterator<DatumChangeListener> iterator = datumChangeListeners.iterator(); iterator.hasNext();) {
            DatumChangeListener datumChangeListener = iterator.next();
            datumChangeListener.onNew(item);
        }
    }

    /**
     * Notifies the datum change listeners that an item has been deleted
     * 
     * @param item
     * 
     * @see {@link DatumChangeListener}
     */
    protected void notifyOnDeleteListeners(Item item) {
        for (Iterator<DatumChangeListener> iterator = datumChangeListeners.iterator(); iterator.hasNext();) {
            DatumChangeListener datumChangeListener = iterator.next();
            datumChangeListener.onDelete(item);
        }
    }

    /**
     * Adds a FetchListener
     * @param listener
     * 
     * @see {@link FetchListener}
     */
    public void addFetchListener(FetchListener listener) {
        fetchListeners.add(listener);
    }

    /**
     * Removes a FetchListener
     * @param listener
     * 
     * @see {@link FetchListener}
     */
    public void removeFetchListener(FetchListener listener) {
        fetchListeners.remove(listener);
    }

    /**
     * Notifies the fetch listeners of the fetch's end
     * 
     * @param items : items returned from the fetch
     * 
     * @see {@link FetchListener}
     */
    protected void notifyCompleteFetchListeners(List<?> items, Request request) {
        for (Iterator<FetchListener> iterator = fetchListeners.iterator(); iterator.hasNext();) {
            FetchListener listener = iterator.next();
            listener.onComplete(this, items, request);
        }
    }

    /**
     * Notifies the fetch listeners of the fetch's befin
     * 
     * @param size : expected fetch size
     * 
     * @see {@link FetchListener}
     */
    protected void notifyBeginFetchListeners(int size, Request request) {
        for (Iterator<FetchListener> iterator = fetchListeners.iterator(); iterator.hasNext();) {
            FetchListener listener = iterator.next();
            listener.onBegin(this, size, request);
        }
    }

    /**
     * Notifies the fetch listeners that an error occured during the fetch
     * 
     * @see {@link FetchListener}
     */
    protected void notifyErrorFetchListeners() {
        for (Iterator<FetchListener> iterator = fetchListeners.iterator(); iterator.hasNext();) {
            FetchListener listener = iterator.next();
            listener.onError(this);
        }
    }

    /**
     * Notifies the fetch listeners that an item has been loaded/fetched
     * 
     * @param item : the item which has been loaded/fetched
     */
    protected void notifyItemFetchListeners(Item item) {
        for (Iterator<FetchListener> iterator = fetchListeners.iterator(); iterator.hasNext();) {
            FetchListener listener = iterator.next();
            listener.onItem(this, item);
        }
    }

    /* (non-Javadoc)
     * @see com.objetdirect.tatami.client.HasDojo#getDojoWidget()
     */
    public JavaScriptObject getDojoWidget() {
        return dojoStore;
    }

    /**
     * Used to initialize the store by setting a reference to the gwt store 
     * in the dojo store.
     * 
     * @param dojoStore
     * @param gwtStore
     */
    private native void setGWTStore(JavaScriptObject dojoStore, AbstractDataStore gwtStore)
    /*-{
    try {
    dojoStore.gwtStore = gwtStore;
    } catch(e) {
    }
     }-*/;

    /**
     * Used before destruction to break the reference from dojo store
     * to gwt store
     * 
     * @param dojoStore
     */
    private native void unSetGWTStore(JavaScriptObject dojoStore)/*-{
                                                                 try{
                                                                 dojoStore.gwtStore = null;
                                                                 } catch(e) {
                                                                     
                                                                 }
                                                                 }-*/;

    /* (non-Javadoc)
     * @see com.objetdirect.tatami.client.HasDojo#doAfterCreation()
     */
    public void doAfterCreation() {
        try {
            if (dojoStore == null) {
                createDojoWidget();
                setGWTStore(dojoStore, this);
                Collection<Item> itemsValues = items.values();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private native void destroyStore(JavaScriptObject store)/*-{
                                                                
                                                            }-*/;

    /* (non-Javadoc)
     * @see com.objetdirect.tatami.client.HasDojo#free()
     */
    public void free() {
        this.dojoStore = null;
    }

    /* (non-Javadoc)
     * @see com.objetdirect.tatami.client.HasDojo#doBeforeDestruction()
     */
    public void doBeforeDestruction() {
        unSetGWTStore(dojoStore);
        free();
    }

    /**
     * @return : the last request performed
     */
    public Request getLastRequest() {
        return lastRequest;
    }

    public void clearDataStore() {
        Collection<Item> collec = items.values();
        for (Iterator iterator = collec.iterator(); iterator.hasNext();) {
            iterator.next();
            iterator.remove();
        }
    }

    /**
     * Execute the given request against the given collection of items
     * 
     * @param items
     * @param request
     * @return
     */
    public List<Item> executeQuery(Collection<?> items, Request request) {
        List<Item> itemsMatchingAndSorted = new ArrayList<Item>();
        for (Iterator<?> iterator = items.iterator(); iterator.hasNext();) {
            Item item = (Item) iterator.next();
            if (itemMatchQuery(item, request)) {
                itemsMatchingAndSorted.add(item);
            }
        }
        if (request.getSortFields().size() > 0) {
            ItemComparator comparator = new ItemComparator(request.getSortFields());
            Collections.sort(itemsMatchingAndSorted, comparator);
        }
        return itemsMatchingAndSorted;
    }

    /**
     * Inner class used compare items with one another when we have to sort them.
     * 
     * @author rdunklau
     *
     */
    private static class ItemComparator implements Comparator<Object> {

        private List<?> sortFields;

        public ItemComparator(List<?> sortFields) {
            this.sortFields = sortFields;
        }

        @SuppressWarnings("unchecked")
        public int compare(Object arg0, Object arg1) {
            Item item1 = (Item) arg0;
            Item item2 = (Item) arg1;
            int result = 0;
            for (Iterator<?> iterator = sortFields.iterator(); iterator.hasNext();) {
                SortField sortField = (SortField) iterator.next();

                Object value1 = item1.getValue(sortField.getAttribute(), null);
                Object value2 = item2.getValue(sortField.getAttribute(), null);
                if (value1 == null && value2 != null) {
                    result = Integer.MIN_VALUE;
                } else if (value2 == null && value1 != null) {
                    result = Integer.MAX_VALUE;
                } else if (value1 == value2) {
                    result = 0;
                } else if (value1 instanceof Comparable && value2 instanceof Comparable) {
                    try {
                        result = ((Comparable<Object>) value1).compareTo(value2);
                    } catch (ClassCastException e) {
                        result = value1.toString().compareTo(value2.toString());
                    }
                } else {
                    result = value1.toString().compareTo(value2.toString());
                }
                if (sortField.isDescending()) {
                    result = -result;
                }
                if (result != 0) {
                    return result;
                }
            }
            return result;
        }

    }

    /**
     * @param item
     * @param request
     * @return : true it item matches the criteria of the request
     */
    @SuppressWarnings("unchecked")
    public boolean itemMatchQuery(Item item, Request request) {
        boolean match = true;
        Map<?, ?> query = request.getQuery();
        Set<?> keys = query.keySet();
        boolean result = true;
        for (Iterator<?> iterator = keys.iterator(); iterator.hasNext();) {
            String queryparameter = (String) iterator.next();
            if (query.get(queryparameter).toString().compareTo("*") != 0) {
                Object actualProperty = item.getValues(queryparameter);
                Object expectedProperty = query.get(queryparameter);
                if (actualProperty == null) {
                    result = false;
                } else if (actualProperty instanceof Comparable && expectedProperty instanceof Comparable) {
                    try {
                        if (actualProperty instanceof Boolean || expectedProperty instanceof Boolean) {
                            result = actualProperty.toString().compareTo(expectedProperty.toString()) == 0 ? true
                                    : false;
                        } else {
                            result = ((Comparable<Comparable<?>>) actualProperty)
                                    .compareTo((Comparable<?>) expectedProperty) == 0 ? true : false;
                        }
                    } catch (ClassCastException e) {
                        result = actualProperty.toString().compareTo(expectedProperty.toString()) == 0 ? true
                                : false;
                    }
                } else {
                    result = actualProperty.toString().compareTo(expectedProperty.toString()) == 0 ? true : false;
                }
                if (result == false) {
                    return result;
                }
            }
        }
        return result;
    }

    public void createDojoWidget() throws Exception {
        this.dojoStore = createDataStore();
    }

    private native JavaScriptObject createDataStore()/*-{
                                                     var dataStore = new $wnd.dojox.data.store.TatamiDataStore();
                                                     return dataStore;
                                                     }-*/;

}