Package com.vaadin.data.hbnutil

Source Code of com.vaadin.data.hbnutil.HbnContainer

/*
* Copyright 2012, Gary Piercey, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

package com.vaadin.data.hbnutil;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import org.hibernate.Criteria;
import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.type.ComponentType;
import org.hibernate.type.Type;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.vaadin.data.Container;
import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.data.hbnutil.HbnContainer.EntityItem.EntityProperty;
import com.vaadin.data.hbnutil.filter.ContainerFilter;
import com.vaadin.data.hbnutil.filter.FilterFactory;
import com.vaadin.data.hbnutil.filter.StringContainerFilter;
import com.vaadin.data.util.MethodProperty;
import com.vaadin.data.util.converter.Converter.ConversionException;
import com.vaadin.data.util.filter.UnsupportedFilterException;

public class HbnContainer<T> implements Container, Container.Indexed, Container.Sortable,
    Container.Filterable, Container.Hierarchical, Container.ItemSetChangeNotifier, Container.Ordered
{
  private static final long serialVersionUID = -6410337120924382057L;
  private ApplicationLogger logger = new ApplicationLogger(HbnContainer.class);

  private SessionFactory sessionFactory;
  private ClassMetadata classMetadata;
  private Class<T> entityType;
  private String parentPropertyName = null;
  private static final int ROW_BUF_SIZE = 100;
  private static final int ID_TO_INDEX_MAX_SIZE = 300;
  private boolean normalOrder = true;
  private List<T> ascRowBuffer;
  private List<T> descRowBuffer;
  private Object lastId;
  private Object firstId;
  private List<T> indexRowBuffer;
  private int indexRowBufferFirstIndex;
  private final Map<Object, Integer> idToIndex = new LinkedHashMap<Object, Integer>();
  private boolean[] orderAscendings;
  private Object[] orderPropertyIds;
  private Integer size;
  private LinkedList<ItemSetChangeListener> itemSetChangeListeners;
  private Set<ContainerFilter> filters;
  private final Map<String, Class<?>> addedProperties = new HashMap<String, Class<?>>();
  private final LoadingCache<Object, EntityItem<T>> cache;
  private final HashMap<Object, Boolean> embeddedPropertiesCache = new HashMap<Object, Boolean>();

  /**
   * Item wrappping a Hibernate mapped entity object. EntityItems are generally instantiated automatically by
   * HbnContainer.
   */
  @SuppressWarnings("hiding")
  public class EntityItem<T> implements Item
  {

    private static final long serialVersionUID = -2847179724504965599L;

    /**
     * Reference to hibernate mapped entity that this Item wraps.
     */
    protected T pojo;

    /**
     * Instantiated properties of this EntityItem. May be either EntityItemProperty (hibernate field) or manually
     * added container property (MethodProperty).
     */
    protected Map<Object, Property<?>> properties = new HashMap<Object, Property<?>>();

    @SuppressWarnings("unchecked")
    public EntityItem(Serializable id)
    {
      logger.executionTrace();

      pojo = (T) sessionFactory.getCurrentSession().get(entityType, id);
      // add non-hibernate mapped container properties
      for (String propertyId : addedProperties.keySet())
      {
        addItemProperty(propertyId, new MethodProperty<Object>(pojo, propertyId));
      }
    }

    /**
     * @return the wrapped entity object.
     */
    public T getPojo()
    {
      logger.executionTrace();
      return pojo;
    }

    @SuppressWarnings("rawtypes")
    public boolean addItemProperty(Object id, Property property) throws UnsupportedOperationException
    {
      logger.executionTrace();

      properties.put(id, property);
      return true;
    }

    public Property<?> getItemProperty(Object id)
    {
      logger.executionTrace();

      Property<?> p = properties.get(id);
      if (p == null)
      {
        p = new EntityProperty(id.toString());
        properties.put(id, p);
      }
      return p;
    }

    public Collection<?> getItemPropertyIds()
    {
      logger.executionTrace();

      return getContainerPropertyIds();
    }

    public boolean removeItemProperty(Object id) throws UnsupportedOperationException
    {
      logger.executionTrace();

      Property<?> removed = properties.remove(id);
      return removed != null;
    }

    /**
     * EntityItemProperty wraps one Hibernate controlled field of the pojo used by EntityItem. For common fields the
     * field value is the same as Property value. For relation fields it is the identifier of related object or a
     * collection of identifiers.
     *
     * The Property is a simple data object that contains one typed value. This interface contains methods to
     * inspect and modify the stored value and its type, and the object's read-only state.
     *
     * The Property also defines the events ReadOnlyStatusChangeEvent and ValueChangeEvent, and the associated
     * listener and notifier interfaces.
     *
     * The Property.Viewer interface should be used to attach the Property to an external data source. This way the
     * value in the data source can be inspected using the Property interface.
     *
     * The Property.editor interface should be implemented if the value needs to be changed through the implementing
     * class.
     */
    @SuppressWarnings("rawtypes")
    public class EntityProperty implements Property, Property.ValueChangeNotifier
    {
      private static final long serialVersionUID = -4086774943938055297L;
      private List<ValueChangeListener> valueChangeListeners;
      private String propertyName;

      /**
       * Default Constructor.
       */
      public EntityProperty(String propertyName)
      {
        logger.executionTrace();

        this.propertyName = propertyName;
      }

      /**
       * This method gets the value that is stored by the property. The returned object is compatible with the
       * class returned by getType().
       */
      @SuppressWarnings("unchecked")
      @Override
      public Object getValue()
      {
        logger.executionTrace();

        final Session session = sessionFactory.getCurrentSession();
        final SessionImplementor sessionImplementor = (SessionImplementor) session;

        if (!sessionFactory.getCurrentSession().contains(pojo))
          pojo = (T) session.get(entityType, (Serializable) getIdForPojo(pojo));

        if (propertyInEmbeddedKey(propertyName))
        {
          final ComponentType identifierType = (ComponentType) classMetadata.getIdentifierType();
          final String[] propertyNames = identifierType.getPropertyNames();

          for (int i = 0; i < propertyNames.length; i++)
          {
            String name = propertyNames[i];

            if (name.equals(propertyName))
            {
              final Object id = classMetadata.getIdentifier(pojo, sessionImplementor);
              return identifierType.getPropertyValue(id, i, EntityMode.POJO);
            }
          }
        }

        final Type propertyType = getPropertyType();
        final Object propertyValue = classMetadata.getPropertyValue(pojo, propertyName);

        if (!propertyType.isAssociationType())
          return propertyValue;

        if (propertyType.isCollectionType())
        {
          if (propertyValue == null)
            return null;

          final HashSet<Serializable> identifiers = new HashSet<Serializable>();
          final Collection<?> pojos = (Collection<?>) propertyValue;

          for (Object object : pojos)
          {
            if (!session.contains(object))
              object = session.merge(object);

            identifiers.add(session.getIdentifier(object));
          }

          return identifiers;
        }

        if (propertyValue == null)
          return null;

        final Class<?> propertyTypeClass = propertyType.getReturnedClass();
        final ClassMetadata metadata = sessionFactory.getClassMetadata(propertyTypeClass);
        final Serializable identifier = metadata.getIdentifier(propertyValue, sessionImplementor);

        return identifier;
      }

      /**
       * This method tests if the Property is in read-only mode. In read-only mode calls to the method setValue
       * will throw ReadOnlyException and will not modify the value of the Property.
       */
      @Override
      public boolean isReadOnly()
      {
        logger.executionTrace();

        return false;
      }

      /**
       * This method sets the property's read-only mode to the specified status. This functionality is optional,
       * but all properties must implement the isReadOnly mode query correctly.
       *
       * HbnContainer does not implement this functionality and will throw an UnsupportedOperationException of
       * this method is called.
       */
      @Override
      public void setReadOnly(boolean newStatus)
      {
        throw new UnsupportedOperationException();
      }

      /**
       * This method sets the value of the property.
       *
       * Implementing this functionality is optional. If the functionality is missing, one should declare the
       * Property to be in read-only mode and throw Property.ReadOnlyException in this function.
       *
       * Note : Since Vaadin 7.0, setting the value of a non-String property as a String is no longer supported.
       */
      @Override
      public void setValue(Object newValue) throws ReadOnlyException, ConversionException
      {
        logger.executionTrace();

        try
        {
          final Session session = sessionFactory.getCurrentSession();
          final SessionImplementor sessionImplementor = (SessionImplementor) sessionFactory
              .getCurrentSession();

          Object value;

          try
          {
            if (newValue == null || getType().isAssignableFrom(newValue.getClass()))
            {
              value = newValue;
            }
            else
            {
              final Constructor<?> constr = getType().getConstructor(new Class[] { String.class });
              value = constr.newInstance(new Object[] { newValue.toString() });
            }

            if (propertyInEmbeddedKey(propertyName))
            {
              final ComponentType identifierType = (ComponentType) classMetadata.getIdentifierType();
              final String[] propertyNames = identifierType.getPropertyNames();

              for (int i = 0; i < propertyNames.length; i++)
              {
                String name = propertyNames[i];

                if (name.equals(propertyName))
                {
                  final Object identifier = classMetadata.getIdentifier(pojo, sessionImplementor);
                  final Object[] values = identifierType.getPropertyValues(identifier,
                      EntityMode.POJO);

                  values[i] = value;
                  identifierType.setPropertyValues(identifier, values, EntityMode.POJO);
                }
              }
            }
            else
            {
              final Type propertyType = classMetadata.getPropertyType(propertyName);

              if (propertyType.isCollectionType())
              {
                final Field declaredField = entityType.getDeclaredField(propertyName);
                final java.lang.reflect.Type genericType = declaredField.getGenericType();
                final java.lang.reflect.Type[] actualTypeArguments =
                    ((ParameterizedType) genericType).getActualTypeArguments();
                final java.lang.reflect.Type assosiatedType = actualTypeArguments[0];
                final String typestring = assosiatedType.toString().substring(6);

                // Reuse existing persistent collection if possible so Hibernate may optimize queries
                // properly.

                @SuppressWarnings("unchecked")
                Collection<Object> pojoCollection = (Collection<Object>) classMetadata
                    .getPropertyValue(pojo, propertyName);

                if (pojoCollection == null)
                {
                  pojoCollection = new HashSet<Object>();
                  classMetadata.setPropertyValue(pojo, propertyName, pojoCollection);
                }

                final Collection<Object> orphans = new HashSet<Object>(pojoCollection);
                final Collection<?> identifiers = (Collection<?>) value;

                for (Object id : identifiers)
                {
                  final Object object = session.get(typestring, (Serializable) id);
                  if (!pojoCollection.contains(object))
                  {
                    pojoCollection.add(object);
                  }
                  else
                  {
                    orphans.remove(object);
                  }
                }

                pojoCollection.removeAll(orphans);
              }
              else if (propertyType.isAssociationType())
              {
                final Class<?> referencedType = classMetadata
                    .getPropertyType(propertyName)
                    .getReturnedClass();

                final Object object = sessionFactory
                    .getCurrentSession()
                    .get(referencedType, (Serializable) value);

                classMetadata.setPropertyValue(pojo, propertyName, object);
                sessionFactory.getCurrentSession().merge(object);
                sessionFactory.getCurrentSession().saveOrUpdate(pojo);
              }
              else
              {
                classMetadata.setPropertyValue(pojo, propertyName, value);
              }
            }

            @SuppressWarnings("unchecked")
            T newPojo = (T) session.merge(pojo);
            pojo = newPojo;

            fireValueChange();
          }
          catch (Exception e)
          {
            logger.error(e);
            throw new ConversionException(e);
          }
        }
        catch (HibernateException e)
        {
          logger.error(e);
        }
      }

      /**
       * This method registers a new value change listener for this property.
       */
      @Override
      public void addListener(ValueChangeListener listener)
      {
        logger.executionTrace();

        if (valueChangeListeners == null)
          valueChangeListeners = new LinkedList<ValueChangeListener>();

        if (!valueChangeListeners.contains(listener))
          valueChangeListeners.add(listener);
      }

      /**
       * This method removes a previously registered value change listener.
       */
      @Override
      public void removeListener(ValueChangeListener listener)
      {
        logger.executionTrace();

        if (valueChangeListeners != null)
          valueChangeListeners.remove(listener);
      }

      /**
       * This method registers a new value change listener for this property.
       */
      @Override
      public void addValueChangeListener(ValueChangeListener listener)
      {
        logger.executionTrace();

        addListener(listener);
      }

      /**
       * This method removes a previously registered value change listener.
       */
      @Override
      public void removeValueChangeListener(ValueChangeListener listener)
      {
        logger.executionTrace();

        removeListener(listener);
      }

      /**
       * This method returns a string representation of the object. In general, the toString method returns a
       * string that "textually represents" this object. The result should be a concise but informative
       * representation that is easy for a person to read. It is recommended that all subclasses override this
       * method.
       *
       * The toString method for class Object returns a string consisting of the name of the class of which the
       * object is an instance, the at-sign character `@', and the unsigned hexadecimal representation of the hash
       * code of the object. In other words, this method returns a string equal to the value of:
       *
       * getClass().getName() + '@' + Integer.toHexString(hashCode())
       */
      @Override
      public String toString()
      {
        logger.executionTrace();

        final Object value = getValue();
        return (value != null) ? value.toString() : null;
      }

      /**
       * This method returns a reference to the containing EntityItem.
       */
      public EntityItem<T> getEntityItem()
      {
        logger.executionTrace();

        return EntityItem.this;
      }

      /**
       * This method returns a reference to the associated pojo.
       */
      public T getPojo()
      {
        logger.executionTrace();

        return pojo;
      }

      /**
       * This method returns the raw type of this property.
       */
      private Type getPropertyType()
      {
        logger.executionTrace();

        return classMetadata.getPropertyType(propertyName);
      }

      /**
       * Returns the type of the Property. The methods getValue and setValue must be compatible with this type:
       * one must be able to safely cast the value returned from getValue to the given type and pass any variable
       * assignable to this type as an argument to setValue.
       */
      public Class<?> getType()
      {
        logger.executionTrace();

        if (propertyInEmbeddedKey(propertyName))
        {
          final ComponentType idType = (ComponentType) classMetadata.getIdentifierType();
          final String[] propertyNames = idType.getPropertyNames();

          for (String name : propertyNames)
          {
            if (name.equals(propertyName))
            {
              try
              {
                final String identifierName = classMetadata.getIdentifierPropertyName();
                final Field identifierField = entityType.getDeclaredField(identifierName);
                final Field propertyField = identifierField.getType().getDeclaredField(propertyName);
                return propertyField.getType();
              }
              catch (NoSuchFieldException e)
              {
                logger.error(e);
                throw new RuntimeException("Failed to find the type of the container property.", e);
              }
            }
          }
        }

        final Type propertyType = getPropertyType();

        if (propertyType.isCollectionType())
        {
          final Class<?> returnedClass = propertyType.getReturnedClass();
          return returnedClass;
        }

        if (propertyType.isAssociationType())
        {
          // For association the the property value type is the type of referenced types identifier.
          final ClassMetadata metadata = sessionFactory.getClassMetadata(
              classMetadata.getPropertyType(propertyName).getReturnedClass());

          return metadata.getIdentifierType().getReturnedClass();
        }

        return classMetadata.getPropertyType(propertyName).getReturnedClass();
      }

      /**
       * Implements a value change event.
       */
      private class HbnPropertyValueChangeEvent implements Property.ValueChangeEvent
      {
        private static final long serialVersionUID = 166764621324404579L;

        public Property<?> getProperty()
        {
          return EntityProperty.this;
        }
      }

      /**
       * This method is used to fire a value change event.
       */
      private void fireValueChange()
      {
        logger.executionTrace();
        if (valueChangeListeners != null)
        {
          final HbnPropertyValueChangeEvent event = new HbnPropertyValueChangeEvent();
          final Object[] array = valueChangeListeners.toArray();

          for (int i = 0; i < array.length; i++)
          {
            final ValueChangeListener listener = (ValueChangeListener) array[i];
            listener.valueChange(event);
          }
        }
      }
    }
  }

  /**
   * Constructor creates a new instance of HbnContainer.
   */
  public HbnContainer(Class<T> entityType, SessionFactory sessionFactory)
  {
    logger.executionTrace();

    this.entityType = entityType;
    this.sessionFactory = sessionFactory;
    this.classMetadata = sessionFactory.getClassMetadata(entityType);

    this.cache = CacheBuilder.newBuilder()
        .expireAfterAccess(2, TimeUnit.MINUTES)
        .maximumSize(10000)
        .recordStats()
        .weakValues()
        .build(new CacheLoader<Object, EntityItem<T>>()
        {
          @Override
          public EntityItem<T> load(Object entityId) throws Exception
          {
            try
            {
              return loadEntity((Serializable) entityId);
            }
            catch (Exception e)
            {
              logger.error(e);
              throw e;
            }
          }
        });
  }

  /**
   * This method is used to load an entity from the database. This method is called automatically by the cache loader
   * when it needs to load an entity into the cache but it can be called manually if necessary.
   */
  protected EntityItem<T> loadEntity(Serializable entityId)
  {
    logger.executionTrace();

    EntityItem<T> entity = null;

    if (entityId != null)
      entity = new EntityItem<T>(entityId);

    return entity;
  }

  /**
   * This method is used to save an entity to the database and in the process it will fire an item set change event.
   */
  public Serializable saveEntity(T entity)
  {
    logger.executionTrace();

    final Session session = sessionFactory.getCurrentSession();
    final Object entityId = session.save(entity);

    clearInternalCache();
    fireItemSetChange();

    return (Serializable) entityId;
  }

  /**
   * This method is used to update an entity in the database, update the cache and fire value change events when
   * necessary.
   */
  public Serializable updateEntity(T entity)
  {
    logger.executionTrace();

    final Session session = sessionFactory.getCurrentSession();
    session.update(entity);

    final Object entityId = getIdForPojo(entity);
    final EntityItem<T> cachedEntity = cache.getIfPresent(entityId);

    cache.refresh(entityId);

    if (cachedEntity != null)
    {
      for (Object propertyId : cachedEntity.getItemPropertyIds())
      {
        Property<?> cachedProperty = cachedEntity.getItemProperty(propertyId);
        if (cachedProperty instanceof EntityItem.EntityProperty)
        {
          @SuppressWarnings("rawtypes")
          EntityProperty entityProperty = (EntityProperty) cachedProperty;
          entityProperty.fireValueChange();
        }
      }
    }

    return (Serializable) entityId;
  }

  /**
   * This method adds a new property to all items in the container. The property id, data type and default value of
   * the new Property are given as parameters. HbnContainer automatically adds all fields that are mapped by Hibernate
   * to the database. With this method we can add a property to the container that is contained in the pojo but not
   * Hibernate mapped.
   */
  @Override
  public boolean addContainerProperty(Object propertyId, Class<?> classType, Object defaultValue)
      throws UnsupportedOperationException
  {
    logger.executionTrace();

    boolean propertyExists = true;

    try
    {
      new MethodProperty<Object>(this.entityType.newInstance(), propertyId.toString());
    }
    catch (Exception e)
    {
      logger.debug("Note: this is not an error: " + e);
      propertyExists = false;
    }

    addedProperties.put(propertyId.toString(), classType);
    return propertyExists;
  }

  /**
   * Creates a new Item into the Container and assigns it an automatic ID. The new ID is returned, or null if the
   * operation fails. After a successful call you can use the getItemmethod to fetch the Item. This functionality is
   * optional.
   */
  @Override
  public Object addItem() throws UnsupportedOperationException
  {
    logger.executionTrace();

    try
    {
      final T entity = entityType.newInstance();
      return saveEntity(entity);
    }
    catch (Exception e)
    {
      logger.error(e);
      return null;
    }
  }

  /**
   * Creates a new Item with the given ID in the Container. The new Item is returned, and it is ready to have its
   * Properties modified. Returns null if the operation fails or the Container already contains a Item with the given
   * ID. This functionality is optional.
   *
   * Note that in this implementation we are expecting auto-generated identifiers so this method is not implemented.
   */
  @Override
  public Item addItem(Object entityId) throws UnsupportedOperationException
  {
    throw new UnsupportedOperationException();
  }

  /**
   * Tests if the Container contains the specified Item. Filtering can hide items so that they will not be visible
   * through the container API, and this method should respect visibility of items (i.e. only indicate visible items
   * as being in the container) if feasible for the container.
   */
  @Override
  public boolean containsId(Object entityId)
  {
    logger.executionTrace();

    try
    {
      final EntityItem<T> entity = cache.get(entityId);
      return (entity != null);
    }
    catch (Exception e)
    {
      logger.error(e);
      return false;
    }
  }

  /**
   * Gets the Property identified by the given entityId and propertyId from the Container. If the Container does not
   * contain the item or it is filtered out, or the Container does not have the Property, null is returned.
   */
  @Override
  public Property<?> getContainerProperty(Object entityId, Object propertyId)
  {
    logger.executionTrace();

    try
    {
      EntityItem<?> entity = cache.get(entityId);
      Property<?> property = entity.getItemProperty(propertyId);
      return property;
    }
    catch (Exception e)
    {
      logger.error(e);
      return null;
    }
  }

  /**
   * Gets the ID's of all Properties stored in the Container. The ID's cannot be modified through the returned
   * collection.
   */
  @Override
  public Collection<String> getContainerPropertyIds()
  {
    logger.executionTrace();

    Collection<String> propertyIds = getSortableContainerPropertyIds();
    propertyIds.addAll(addedProperties.keySet());
    return propertyIds;
  }

  /**
   * This is an HbnContainer specific utility method that is used to retrieve the list of embedded property key
   * identifiers.
   */
  private Collection<String> getEmbeddedKeyPropertyIds()
  {
    logger.executionTrace();

    final ArrayList<String> embeddedKeyPropertyIds = new ArrayList<String>();
    final Type identifierType = classMetadata.getIdentifierType();

    if (identifierType.isComponentType())
    {
      final ComponentType idComponent = (ComponentType) identifierType;
      final String[] propertyNameArray = idComponent.getPropertyNames();

      if (propertyNameArray != null)
      {
        final List<String> propertyNames = Arrays.asList(propertyNameArray);
        embeddedKeyPropertyIds.addAll(propertyNames);
      }
    }

    return embeddedKeyPropertyIds;
  }

  /**
   * Gets the Item with the given Item ID from the Container. If the Container does not contain the requested Item,
   * null is returned. Containers should not return Items that are filtered out.
   */
  @Override
  public EntityItem<T> getItem(Object entityId)
  {
    logger.executionTrace();

    try
    {
      return cache.get(entityId);
    }
    catch (ExecutionException e)
    {
      logger.error(e);
      return null;
    }
  }

  /**
   * Gets the ID's of all visible (after filtering and sorting) Items stored in the Container. The ID's cannot be
   * modified through the returned collection. If the container is Container.Ordered, the collection returned by this
   * method should follow that order. If the container is Container.Sortable, the items should be in the sorted order.
   * Calling this method for large lazy containers can be an expensive operation and should be avoided when practical.
   *
   * Create an optimized query to return only identifiers. Note that this method does not scale well for large
   * database. At least Table is optimized so that it does not call this method.
   */
  @Override
  public Collection<?> getItemIds()
  {
    logger.executionTrace();

    // TODO: BUG: does not preserve sort order!
    final Criteria criteria = getCriteria();
    criteria.setProjection(Projections.id());
    return criteria.list();
  }

  /**
   * Get numberOfItems consecutive item ids from the container, starting with the item id at startIndex.
   *
   * Implementations should return at most numberOfItems item ids, but can contain less if the container has less
   * items than required to fulfill the request. The returned list must hence contain all of the item ids from the
   * range:
   *
   * startIndex to max(startIndex + (numberOfItems-1), container.size()-1).
   */
  @Override
  public List<?> getItemIds(int startIndex, int count)
  {
    logger.executionTrace();

    final List<?> entityIds = (List<?>) getItemIds();
    return entityIds.subList(startIndex, startIndex + count);
  }

  /**
   * Gets the data type of all Properties identified by the given Property ID. This method does pretty much the same
   * thing as EntityItemProperty#getType()
   */
  public Class<?> getType(Object propertyId)
  {
    logger.executionTrace();

    // TODO: refactor to use same code as EntityItemProperty#getType()
    // This will also fix incomplete implementation of this method (for association types). Not critical as
    // Components don't really rely on this methods.

    if (addedProperties.keySet().contains(propertyId))
      return addedProperties.get(propertyId);

    if (propertyInEmbeddedKey(propertyId))
    {
      final ComponentType idType = (ComponentType) classMetadata.getIdentifierType();
      final String[] propertyNames = idType.getPropertyNames();

      for (int i = 0; i < propertyNames.length; i++)
      {
        String name = propertyNames[i];
        if (name.equals(propertyId))
        {
          String idName = classMetadata.getIdentifierPropertyName();
          try
          {
            Field idField = entityType.getDeclaredField(idName);
            Field propertyField = idField.getType().getDeclaredField((String) propertyId);
            return propertyField.getType();
          }
          catch (NoSuchFieldException ex)
          {
            throw new RuntimeException("Could not find the type of specified container property.", ex);
          }
        }
      }
    }

    Type propertyType = classMetadata.getPropertyType(propertyId.toString());
    return propertyType.getReturnedClass();
  }

  /**
   * Removes all Items from the Container. Note that Property ID and type information is preserved. This functionality
   * is optional.
   */
  @Override
  public boolean removeAllItems() throws UnsupportedOperationException
  {
    logger.executionTrace();

    try
    {
      final Session session = sessionFactory.getCurrentSession();
      final Query query = session.createQuery("DELETE FROM " + entityType.getSimpleName());

      final int deleted = query.executeUpdate();
      cache.invalidateAll();

      if (deleted > 0)
      {
        clearInternalCache();
        fireItemSetChange();
      }

      return (size() == 0);
    }
    catch (Exception e)
    {
      logger.error(e);
      return false;
    }
  }

  /**
   * Removes a Property specified by the given Property ID from the Container. Note that the Property will be removed
   * from all Items in the Container. This functionality is optional.
   */
  @Override
  public boolean removeContainerProperty(Object propertyId) throws UnsupportedOperationException
  {
    logger.executionTrace();

    final Class<?> removed = addedProperties.remove(propertyId);
    return (removed != null);
  }

  /**
   * Removes the Item identified by entityId from the Container. Containers that support filtering should also allow
   * removing an item that is currently filtered out. This functionality is optional.
   *
   * Note that this method recursively removes all children of this entity before removing this entity.
   */
  @Override
  public boolean removeItem(Object entityId) throws UnsupportedOperationException
  {
    logger.executionTrace();

    for (Object id : getChildren(entityId))
      removeItem(id);

    final Session session = sessionFactory.getCurrentSession();
    final Object entity = session.load(entityType, (Serializable) entityId);

    session.delete(entity);
    cache.invalidate(entityId);

    clearInternalCache();
    fireItemSetChange();

    return true;
  }

  /**
   * Gets the number of visible Items in the Container. Filtering can hide items so that they will not be visible
   * through the container API.
   */
  @Override
  public int size()
  {
    logger.executionTrace();

    size = ((Number) getBaseCriteria()
        .setProjection(Projections.rowCount())
        .uniqueResult())
        .intValue();

    return size.intValue();
  }

  /**
   * Adds a new item after the given item. Adding an item after null item adds the item as first item of the ordered
   * container. Note that we can't implement properly for database backed container like this so it is unsupported.
   */
  @Override
  public Object addItemAfter(Object previousEntityId) throws UnsupportedOperationException
  {
    logger.executionTrace();

    throw new UnsupportedOperationException();
  }

  /**
   * Adds a new item after the given item. Adding an item after null item adds the item as first item of the ordered
   * container. Note that we can't implement properly for database backed container like this so it is unsupported.
   */
  @Override
  public Item addItemAfter(Object previousEntityId, Object newEntityId) throws UnsupportedOperationException
  {
    logger.executionTrace();

    throw new UnsupportedOperationException();
  }

  /**
   * Gets the ID of the first Item in the Container.
   */
  @Override
  public Object firstItemId()
  {
    logger.executionTrace();

    final Object firstPojo = getCriteria()
        .setMaxResults(1)
        .setCacheable(true)
        .uniqueResult();

    firstId = getIdForPojo(firstPojo);
    idToIndex.put(firstId, normalOrder ? 0 : size() - 1);

    return firstId;
  }

  /**
   * Tests if the Item corresponding to the given Item ID is the first Item in the Container.
   */
  @Override
  public boolean isFirstId(Object entityId)
  {
    logger.executionTrace();
    return entityId.equals(firstItemId());
  }

  /**
   * Tests if the Item corresponding to the given Item ID is the last Item in the Container.
   */
  @Override
  public boolean isLastId(Object entityId)
  {
    logger.executionTrace();
    return entityId.equals(lastItemId());
  }

  /**
   * Gets the ID of the last Item in the Container.
   */
  @Override
  public Object lastItemId()
  {
    logger.executionTrace();

    if (lastId == null)
    {
      normalOrder = !normalOrder;
      lastId = firstItemId();
      normalOrder = !normalOrder;
    }

    return lastId;
  }

  /**
   * Gets the ID of the Item following the Item that corresponds to entityId. If the given Item is the last or not
   * found in the Container, null is returned.
   *
   * This is a simple method but it contains a lot of code. The complicated logic is needed to avoid:
   *
   * - a large number of database queries - scrolling through a large query result
   *
   * This way this container can be used with large data sets.
   */
  @Override
  public Object nextItemId(Object entityId)
  {
    logger.executionTrace();

    EntityItem<T> entity = null;
    List<T> rowBuffer = null;

    try
    {
      entity = cache.get(entityId);
      rowBuffer = getRowBuffer();
    }
    catch (Exception e)
    {
      logger.error(e);
      return null;
    }

    try
    {
      int index;
      if ((index = rowBuffer.indexOf(entity.getPojo())) != -1)
      {
        final T nextEntity = rowBuffer.get(index + 1);
        return getIdForPojo(nextEntity);
      }
    }
    catch (Exception e) // entityId is not in rowBuffer, suppress the exception
    {
    }

    int currentIndex = indexOfId(entityId);
    int size = size();

    int firstIndex = (normalOrder)
        ? currentIndex + 1
        : size - currentIndex;

    if (firstIndex < 0 || firstIndex >= size)
      return null;

    final Criteria criteria = getCriteria()
        .setFirstResult(firstIndex)
        .setMaxResults(ROW_BUF_SIZE);

    @SuppressWarnings("unchecked")
    final List<T> newRowBuffer = criteria.list();

    if (newRowBuffer.size() > 0)
    {
      setRowBuffer(newRowBuffer, firstIndex);
      final T nextPojo = newRowBuffer.get(0);
      return getIdForPojo(nextPojo);
    }

    return null;
  }

  /**
   * Gets the ID of the Item preceding the Item that corresponds to entityId. If the given Item is the first or not
   * found in the Container, null is returned.
   */
  @Override
  public Object prevItemId(Object entityId)
  {
    logger.executionTrace();

    normalOrder = !normalOrder;
    Object previous = nextItemId(entityId);
    normalOrder = !normalOrder;
    return previous;
  }

  /**
   * Not supported in HbnContainer. Indexing/order is controlled by underlying database.
   */
  @Override
  public Object addItemAt(int index) throws UnsupportedOperationException
  {
    throw new UnsupportedOperationException();
  }

  /**
   * Not supported in HbnContainer. Indexing/order is controlled by underlying database.
   */
  @Override
  public Item addItemAt(int index, Object newEntityId) throws UnsupportedOperationException
  {
    throw new UnsupportedOperationException();
  }

  /**
   * Get the item id for the item at the position given by index.
   */
  @Override
  public Object getIdByIndex(int index)
  {
    logger.executionTrace();

    if (indexRowBuffer == null)
      resetIndexRowBuffer(index);

    int indexInCache = index - indexRowBufferFirstIndex;

    if (!(indexInCache >= 0 && indexInCache < indexRowBuffer.size()))
    {
      resetIndexRowBuffer(index);
      indexInCache = 0;
    }

    final T pojo = indexRowBuffer.get(indexInCache);
    final Object id = getIdForPojo(pojo);

    idToIndex.put(id, new Integer(index));

    if (idToIndex.size() > ID_TO_INDEX_MAX_SIZE)
      idToIndex.remove(idToIndex.keySet().iterator().next());

    return id;
  }

  /**
   * Gets the index of the Item corresponding to the entityId. The following is true for the returned index: 0 <=
   * index < size(), or index = -1 if there is no visible item with that id in the container.
   *
   * Note! Expects that getIdByIndex is called for this entityId. Otherwise it will be potentially rather slow
   * operation with large tables. When used with Table, this shouldn't be a problem.
   */
  @Override
  public int indexOfId(Object entityId)
  {
    logger.executionTrace();

    final Integer index = idToIndex.get(entityId);

    return (index == null)
        ? slowIndexOfId(entityId)
        : index;
  }

  /**
   * Gets the container property IDs which can be used to sort the items.
   */
  @Override
  public Collection<String> getSortableContainerPropertyIds()
  {
    logger.executionTrace();

    final String[] propertyNames = classMetadata.getPropertyNames();
    final LinkedList<String> propertyIds = new LinkedList<String>();

    propertyIds.addAll(Arrays.asList(propertyNames));
    propertyIds.addAll(getEmbeddedKeyPropertyIds());

    return propertyIds;
  }

  /**
   * Sort method. Sorts the container items. Sorting a container can irreversibly change the order of its items or
   * only change the order temporarily, depending on the container.
   *
   * HbnContainer does not actually sort anything here, just clearing cache will do the thing lazily.
   */
  @Override
  public void sort(Object[] propertyId, boolean[] ascending)
  {
    logger.executionTrace();

    clearInternalCache();
    orderPropertyIds = propertyId;
    orderAscendings = ascending;
  }

  /**
   * Remove all active filters from the container.
   */
  @Override
  public void removeAllContainerFilters()
  {
    logger.executionTrace();

    if (filters != null)
    {
      filters = null;
      clearInternalCache();
      fireItemSetChange();
    }
  }

  /**
   * Adds a filter for the container
   *
   * Note that for complex filtering it is possible to override {@link #getBaseCriteria()} method and add filter
   * so the query directly.
   */
  @Override
  public void addContainerFilter(Filter filter) throws UnsupportedFilterException
  {
    logger.executionTrace();

    final ContainerFilter containerFilter = FilterFactory.getContainerFilter(filter);

    addContainerFilter(containerFilter);
  }

  /**
   * Finds the identifiers for the children of the given item. The returned collection is unmodifiable.
   */
  @Override
  public Collection<?> getChildren(Object entityId)
  {
    logger.executionTrace();

    final ArrayList<Object> children = new ArrayList<Object>();

    try
    {
      parentPropertyName = getParentPropertyName();

      if (parentPropertyName == null)
        return children;

      for (Object id : getItemIds())
      {
        EntityItem<T> entity = cache.get(id);
        Property<?> property = entity.getItemProperty(parentPropertyName);
        Object value = property.getValue();

        if (entityId.equals(value))
          children.add(id);
      }
    }
    catch (Exception e)
    {
      logger.error(e);
    }

    return children;
  }

  /**
   * Gets the identifier of the given item's parent. If there is no parent or we are unable to infer the name of the
   * parent property this method will return null.
   */
  @Override
  public Object getParent(Object entityId)
  {
    logger.executionTrace();

    try
    {
      parentPropertyName = getParentPropertyName();

      if (parentPropertyName == null)
      {
        logger.warn("failed to find a parent property name; hierarchy may be incomplete.");
        return null;
      }

      final EntityItem<T> entity = cache.get(entityId);
      final Property<?> property = entity.getItemProperty(parentPropertyName);
      final Object value = property.getValue();

      return value;
    }
    catch (Exception e)
    {
      logger.error(e);
      return null;
    }
  }

  /**
   * Gets the IDs of all Items in the container that don't have a parent. Such items are called root Items. The
   * returned collection is unmodifiable.
   */
  @Override
  public Collection<?> rootItemIds()
  {
    logger.executionTrace();

    final ArrayList<Object> rootItems = new ArrayList<Object>();

    try
    {
      parentPropertyName = getParentPropertyName();

      if (parentPropertyName == null)
      {
        logger.warn("failed to find a parent property name; hierarchy may be incomplete.");
        return rootItems;
      }

      final Collection<?> allItemIds = getItemIds();

      for (Object id : allItemIds)
      {
        EntityItem<T> entity = cache.get(id);
        Property<?> property = entity.getItemProperty(parentPropertyName);
        Object value = property.getValue();

        if (value == null)
          rootItems.add(id);
      }
    }
    catch (Exception e)
    {
      logger.error(e);
    }

    return rootItems;
  }

  /**
   * Sets the parent of an Item. The new parent item must exist and be able to have children. (
   * areChildrenAllowed(Object) == true ). It is also possible to detach a node from the hierarchy (and thus make it
   * root) by setting the parent null. This operation is optional.
   */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  @Override
  public boolean setParent(Object entityId, Object newParentId)
  {
    logger.executionTrace();

    try
    {
      parentPropertyName = getParentPropertyName();

      if (parentPropertyName == null)
      {
        logger.warn("failed to find a parent property name; unable to set the parent.");
        return false;
      }

      final EntityItem<T> item = cache.get(entityId);
      final Property property = item.getItemProperty(parentPropertyName);

      property.setValue(newParentId);
      final Object value = property.getValue();

      return (value.equals(newParentId));
    }
    catch (Exception e)
    {
      logger.error(e);
      return false;
    }
  }

  /**
   * Tests if the Item with given ID can have children.
   */
  @Override
  public boolean areChildrenAllowed(Object entityId)
  {
    logger.executionTrace();

    if ((parentPropertyName = getParentPropertyName()) != null)
      return containsId(entityId);

    return false;
  }

  /**
   * Sets the given Item's capability to have children. If the Item identified with entityId already has children and
   * areChildrenAllowed(Object) is false this method fails and false is returned.
   *
   * The children must be first explicitly removed with setParent(Object entityId, Object newParentId)or
   * com.vaadin.data.Container.removeItem(Object entityId).
   *
   * This operation is optional. If it is not implemented, the method always returns false.
   */
  @Override
  public boolean setChildrenAllowed(Object entityId, boolean areChildrenAllowed)
  {
    logger.executionTrace();

    return false;
  }

  /**
   * Tests if the Item specified with entityId is a root Item. The hierarchical container can have more than one root
   * and must have at least one unless it is empty. The getParent(Object entityId) method always returns null for root
   * Items.
   */
  @Override
  public boolean isRoot(Object entityId)
  {
    logger.executionTrace();

    try
    {
      parentPropertyName = getParentPropertyName();

      if (parentPropertyName == null)
      {
        logger.warn("failed to find a parent property name; hierarchy may be incomplete.");
        return false;
      }

      final EntityItem<T> item = cache.get(entityId);
      final Property<?> property = item.getItemProperty(parentPropertyName);
      final Object value = property.getValue();

      return (value == null);
    }
    catch (Exception e)
    {
      logger.error(e);
      return false;
    }
  }

  /**
   * Tests if the Item specified with entityId has child Items or if it is a leaf. The getChildren(Object entityId)
   * method always returns null for leaf Items.
   *
   * Note that being a leaf does not imply whether or not an Item is allowed to have children.
   */
  @Override
  public boolean hasChildren(Object entityId)
  {
    logger.executionTrace();

    try
    {
      parentPropertyName = getParentPropertyName();

      if (parentPropertyName == null)
      {
        logger.warn("failed to find a parent property name; hierarchy may be incomplete.");
        return false;
      }

      for (Object id : getItemIds())
      {
        EntityItem<T> item = cache.get(id);
        Property<?> property = item.getItemProperty(parentPropertyName);
        Object value = property.getValue();

        if (entityId.equals(value))
          return true;
      }

      return false;
    }
    catch (Exception e)
    {
      logger.error(e);
      return false;
    }
  }

  /**
   * Adds an Item set change listener for the object.
   */
  @Override
  public void addItemSetChangeListener(ItemSetChangeListener listener)
  {
    logger.executionTrace();

    if (itemSetChangeListeners == null)
      itemSetChangeListeners = new LinkedList<ItemSetChangeListener>();

    itemSetChangeListeners.add(listener);
  }

  /**
   * Removes the Item set change listener from the object.
   */
  @Override
  public void removeItemSetChangeListener(ItemSetChangeListener listener)
  {
    logger.executionTrace();
   
    if (itemSetChangeListeners != null)
      itemSetChangeListeners.remove(listener);
  }

  /**
   * Adds an Item set change listener for the object. This method is deprecated. You should use
   * addItemSetChangeListener() instead.
   */
  @Override
  @Deprecated
  public void addListener(ItemSetChangeListener listener)
  {
    logger.executionTrace();
    addItemSetChangeListener(listener);
  }

  /**
   * Removes the Item set change listener from the object. This method is deprecated. You should use
   * addItemSetChangeListener() instead.
   */
  @Override
  @Deprecated
  public void removeListener(ItemSetChangeListener listener)
  {
    logger.executionTrace();
    removeItemSetChangeListener(listener);
  }

  //
  // UTILITY METHODS
  //

  /**
   * This method was added mainly to allow unit tests to be written to cover the filter add/remove methods.
   */
  public Set<ContainerFilter> getContainerFilters()
  {
    return filters;
  }

  /**
   * This method was added mainly to allow unit tests to be written to cover the listener add/remove methods.
   */
  public List<ItemSetChangeListener> getItemSetChangeListeners()
  {
    return itemSetChangeListeners;
  }

  /**
   * This is an internal HbnContainer utility method. Determines if a property is contained within an embedded key.
   */
  private boolean propertyInEmbeddedKey(Object propertyId)
  {
    logger.executionTrace();

    if (embeddedPropertiesCache.containsKey(propertyId))
      return embeddedPropertiesCache.get(propertyId);

    final Type identifierType = classMetadata.getIdentifierType();

    if (identifierType.isComponentType())
    {
      final ComponentType componentType = (ComponentType) identifierType;
      final String[] idPropertyNames = componentType.getPropertyNames();
      final List<String> idPropertyNameList = Arrays.asList(idPropertyNames);
      return idPropertyNameList.contains(propertyId);
    }

    return false;
  }

  /**
   * This is an internal HbnContainer utility method. This method triggers events associated with the
   * ItemSetChangeListener.
   */
  private void fireItemSetChange()
  {
    logger.executionTrace();

    if (itemSetChangeListeners != null)
    {
      final Object[] changeListeners = itemSetChangeListeners.toArray();

      final Container.ItemSetChangeEvent changeEvent = new Container.ItemSetChangeEvent()
      {
        private static final long serialVersionUID = -3002746333251784195L;

        public Container getContainer()
        {
          return HbnContainer.this;
        }
      };

      for (int i = 0; i < changeListeners.length; i++)
      {
        ItemSetChangeListener changeListener = (ItemSetChangeListener) changeListeners[i];
        changeListener.containerItemSetChange(changeEvent);
      }
    }
  }

  /**
   * This is an internal HbnContainer utility method. Gets a base listing using current ordering criteria.
   */
  private Criteria getCriteria()
  {
    logger.executionTrace();

    final Criteria criteria = getBaseCriteria();
    final List<Order> orders = getOrder(!normalOrder);

    for (Order order : orders)
    {
      criteria.addOrder(order);
    }

    return criteria;
  }

  /**
   * This is an internal HbnContainer utility method. Return the ordering criteria in the order in which they should
   * be applied. The composed order must be stable and must include {@link #getNaturalOrder(boolean)} at the end.
   */
  protected final List<Order> getOrder(boolean flipOrder)
  {
    logger.executionTrace();

    final List<Order> orders = new ArrayList<Order>();
    orders.addAll(getDefaultOrder(flipOrder));
    orders.add(getNaturalOrder(flipOrder));
    return orders;
  }

  /**
   * This is an internal HbnContainer utility method. Returns the ordering to use for the container contents. The
   * default implementation provides the {@link Container.Sortable} functionality. Can be overridden to customize item
   * sort order.
   */
  protected List<Order> getDefaultOrder(boolean flipOrder)
  {
    logger.executionTrace();

    final List<Order> orders = new ArrayList<Order>();

    if (orderPropertyIds != null)
    {
      for (int i = 0; i < orderPropertyIds.length; i++)
      {
        String propertyId = orderPropertyIds[i].toString();

        if (propertyInEmbeddedKey(propertyId))
          propertyId = classMetadata.getIdentifierPropertyName() + "." + propertyId;

        boolean ascending = (flipOrder)
            ? !orderAscendings[i]
            : orderAscendings[i];

        Order order = (ascending)
            ? Order.asc(propertyId)
            : Order.desc(propertyId);

        orders.add(order);
      }
    }

    return orders;
  }

  /**
   * This is an internal HbnContainer utility method. Creates the base criteria for entity class and add possible
   * restrictions to query. This method is protected so developers can add their own custom criteria.
   */
  protected Criteria getBaseCriteria()
  {
    logger.executionTrace();

    final Session session = sessionFactory.getCurrentSession();
    Criteria criteria = session.createCriteria(entityType);

    if (filters != null)
    {
      for (ContainerFilter filter : filters)
      {
        String idName = null;

        if (propertyInEmbeddedKey(filter.getPropertyId()))
          idName = classMetadata.getIdentifierPropertyName();

        criteria = criteria.add(filter.getCriterion(idName));
      }
    }

    return criteria;
  }

  /**
   * This is an internal HbnContainer utility method. Natural order is the order in which the database is sorted if
   * container has no other ordering set. Natural order is always added as least significant order to queries. This is
   * needed to keep items stable order across queries. The default implementation sorts entities by identifier column.
   */
  protected Order getNaturalOrder(boolean flipOrder)
  {
    logger.executionTrace();

    final String propertyName = getIdPropertyName();

    return (flipOrder)
        ? Order.desc(propertyName)
        : Order.asc(propertyName);
  }

  /**
   * This is an internal HbnContainer utility method to detect identifier of given entity object.
   */
  private Object getIdForPojo(Object pojo)
  {
    logger.executionTrace();

    final Session session = sessionFactory.getCurrentSession();
    return classMetadata.getIdentifier(pojo, (SessionImplementor) session);
  }

  /**
   * This is an internal HbnContainer utility method. RowBuffer stores a list of entity items to avoid excessive
   * number of DB queries.
   */
  private List<T> getRowBuffer()
  {
    logger.executionTrace();

    return (normalOrder) ? ascRowBuffer : descRowBuffer;
  }

  /**
   * This is an internal HbnContainer utility method. RowBuffer stores some pojos to avoid excessive number of DB
   * queries. Also updates the idToIndex map.
   */
  private void setRowBuffer(List<T> list, int firstIndex)
  {
    logger.executionTrace();

    if (normalOrder)
    {
      ascRowBuffer = list;

      for (int i = 0; i < list.size(); ++i)
      {
        idToIndex.put(getIdForPojo(list.get(i)), firstIndex + i);
      }
    }
    else
    {
      descRowBuffer = list;
      final int lastIndex = size() - 1;

      for (int i = 0; i < list.size(); ++i)
      {
        idToIndex.put(getIdForPojo(list.get(i)), lastIndex - firstIndex - i);
      }
    }
  }

  /**
   * This is an internal HbnContainer utility method that gets the property name of the identifier.
   */
  private String getIdPropertyName()
  {
    logger.executionTrace();

    return classMetadata.getIdentifierPropertyName();
  }

  /**
   * This is an internal HbnContainer utility method to query new set of entity items to cache from given index.
   */
  @SuppressWarnings("unchecked")
  private void resetIndexRowBuffer(int index)
  {
    logger.executionTrace();

    indexRowBufferFirstIndex = index;
    indexRowBuffer = getCriteria().setFirstResult(index).setMaxResults(ROW_BUF_SIZE).list();
  }

  /**
   * This is an internal HbnContainer utility method that gets the index of the given identifier.
   */
  private int slowIndexOfId(Object entityId)
  {
    logger.executionTrace();

    final Criteria criteria = getCriteria().setProjection(Projections.id());
    final List<?> list = criteria.list();
    return list.indexOf(entityId);
  }

  /**
   * This is an internal HbnContainer utility method. Adds container filter for hibernate mapped property. For
   * property not mapped by Hibernate.
   */
  public void addContainerFilter(Object propertyId, String filterString, boolean ignoreCase, boolean onlyMatchPrefix)
  {
    logger.executionTrace();

    addContainerFilter(new StringContainerFilter(propertyId, filterString, ignoreCase, onlyMatchPrefix));
  }

  /**
   * Clears all filters present in filters which have the same property id as
   * that set in containerFilter
   *
   * @param containerFilter
   *            the filter which will be used to filter filters
   * @param filters
   *            set of filters to filter
   * @return A filtered set of ContainerFilters where there are no filters
   *         with the same property ID as the one in containerFilter
   */
  /*
   * TODO: is this really necessary? Enums do not work properly in tables
   * without this, but those may have to be better handled somewhere else
   */
  private Set<ContainerFilter> filterFilters(
      final ContainerFilter containerFilter,
      final Set<ContainerFilter> filters)
  {
    final HashSet<ContainerFilter> filtered = new HashSet<ContainerFilter>();

    final Object propertyId = containerFilter.getPropertyId();
    for (ContainerFilter filter : filters)
    {
      if (!propertyId.equals(filter.getPropertyId()))
      {
        filtered.add(filter);
      }
    }

    return filtered;
  }

  /**
   * This is an internal HbnContainer utility method that adds a container filter.
   */
  public void addContainerFilter(ContainerFilter containerFilter)
  {
    logger.executionTrace();

    if (addedProperties.containsKey(containerFilter.getPropertyId()))
    {
      final String message = "HbnContainer does not support filtering properties not mapped by Hibernate";
      throw new UnsupportedOperationException(message);
    }

    if (filters == null)
      filters = new HashSet<ContainerFilter>();

    filters = filterFilters(containerFilter, filters);

    filters.add(containerFilter);

    clearInternalCache();
    fireItemSetChange();
  }

  /**
   * This is an internal HbnContainer utility method that removes container filters for the given property identifier.
   */
  public void removeContainerFilters(Object propertyId)
  {
    logger.executionTrace();

    if (filters != null)
    {
      for (Iterator<ContainerFilter> iterator = filters.iterator(); iterator.hasNext();)
      {
        ContainerFilter containerFilter = iterator.next();

        if (containerFilter.getPropertyId().equals(propertyId))
          iterator.remove();
      }

      clearInternalCache();
      fireItemSetChange();
    }
  }

  /**
   * This is an internal HbnContainer utility method that removes the given container filter.
   */
  @Override
  public void removeContainerFilter(Filter filter)
  {
    logger.executionTrace();

    final ContainerFilter containerFilter = FilterFactory.getContainerFilter(filter);
    filters.remove(containerFilter);

    fireItemSetChange();
  }

  /**
   * This is an internal HbnContainer utility method that infers the name of the parent field belonging to the current
   * property based on type.
   */
  private String getParentPropertyName()
  {
    logger.executionTrace();

    // TODO: make this a little more robust, there are a number of cases where this will fail.

    if (parentPropertyName == null)
    {
      String[] propertyNames = classMetadata.getPropertyNames();

      for (int i = 0; i < propertyNames.length; ++i)
      {
        String entityTypeName = entityType.getName();
        String propertyTypeName = classMetadata.getPropertyType(propertyNames[i]).getName();

        if (entityTypeName.equals(propertyTypeName))
        {
          parentPropertyName = propertyNames[i];
          break;
        }
      }
    }

    return parentPropertyName;
  }

  /**
   * This is an internal HbnContainer utility method to clear all cache fields.
   */
  protected void clearInternalCache()
  {
    logger.executionTrace();

    idToIndex.clear();
    indexRowBuffer = null;
    ascRowBuffer = null;
    descRowBuffer = null;
    firstId = null;
    lastId = null;
    size = null;
    embeddedPropertiesCache.clear();
  }
}
TOP

Related Classes of com.vaadin.data.hbnutil.HbnContainer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.