Package com.tll.dao.db4o

Source Code of com.tll.dao.db4o.Db4oEntityDao

/**
* The Logic Lab
* @author jpk
* @since Sep 15, 2009
*/
package com.tll.dao.db4o;

import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.math.NumberRange;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springextensions.db4o.Db4oCallback;
import org.springextensions.db4o.Db4oTemplate;
import org.springextensions.db4o.support.Db4oDaoSupport;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.dao.DataAccessException;

import com.db4o.ObjectContainer;
import com.db4o.events.Event4;
import com.db4o.events.EventArgs;
import com.db4o.events.EventListener4;
import com.db4o.events.EventRegistry;
import com.db4o.events.EventRegistryFactory;
import com.db4o.events.ObjectEventArgs;
import com.db4o.query.Constraint;
import com.db4o.query.Predicate;
import com.db4o.query.Query;
import com.google.inject.Inject;
import com.tll.criteria.Criteria;
import com.tll.criteria.Criterion;
import com.tll.criteria.CriterionGroup;
import com.tll.criteria.ICriterion;
import com.tll.criteria.IQueryParam;
import com.tll.criteria.InvalidCriteriaException;
import com.tll.dao.EntityExistsException;
import com.tll.dao.EntityNotFoundException;
import com.tll.dao.IEntityDao;
import com.tll.dao.IPageResult;
import com.tll.dao.NonUniqueResultException;
import com.tll.dao.SearchResult;
import com.tll.dao.SortColumn;
import com.tll.dao.Sorting;
import com.tll.key.IKey;
import com.tll.model.IEntity;
import com.tll.model.INamedEntity;
import com.tll.model.IScalar;
import com.tll.model.ITimeStampEntity;
import com.tll.model.IVersionSupport;
import com.tll.model.Scalar;
import com.tll.model.key.BusinessKeyPropertyException;
import com.tll.model.key.BusinessKeyUtil;
import com.tll.model.key.IBusinessKey;
import com.tll.model.key.NameKey;
import com.tll.model.key.NonUniqueBusinessKeyException;
import com.tll.model.key.PrimaryKey;
import com.tll.util.DBType;
import com.tll.util.DateRange;
import com.tll.util.PropertyPath;

/**
* Db4oEntityDao
* @author jpk
*/
@SuppressWarnings( {
  "unchecked", "serial" })
  public class Db4oEntityDao extends Db4oDaoSupport implements IEntityDao {

  static final Log log = LogFactory.getLog(Db4oEntityDao.class);

  /**
   * Scalarizes an entity by first transforming the entity into a map of
   * property name/property value where the property names are those contained
   * in the <code>inclusionProperties</code> argument.
   * @param <E>
   * @param entity
   * @param inclusionProperties The properties to scalarize. If
   *        <code>null</code>, all 1-st level deep entity properties are
   *        scalarized.
   * @return A new {@link IScalar} instance
   */
  private static <E extends IEntity> IScalar scalarize(final E entity, Collection<String> inclusionProperties) {
    final BeanWrapper bw = new BeanWrapperImpl(entity);
    final Map<String, Object> map = new LinkedHashMap<String, Object>();
    for(final PropertyDescriptor pd : bw.getPropertyDescriptors()) {
      final String pname = pd.getName();
      if(bw.isWritableProperty(pname)) {
        map.put(pname, bw.getPropertyValue(pname));
      }
    }
    return new Scalar(entity.entityClass(), map);
  }

  /**
   * Transforms an entity list to {@link SearchResult} instances. If
   * <code>inclusionProperties</code> are specified (non-<code>null</code>),
   * {@link IScalar} instances are created in place of the {@link IEntity}
   * containing only those properties specifed in the
   * <code>inclusionProperties</code> argument.
   * @param <E>
   * @param entityList
   * @param inclusionProperties May be <code>null</code>
   * @return New list of transormed {@link SearchResult}s.
   */
  private static <E extends IEntity> List<SearchResult<?>> transformEntityList(final List<E> entityList,
      final Collection<String> inclusionProperties) {
    final List<SearchResult<?>> slist = new ArrayList<SearchResult<?>>(entityList.size());
    for(final E e : entityList) {
      if(inclusionProperties != null) {
        slist.add(new SearchResult<IScalar>(scalarize(e, inclusionProperties)));
      }
      else {
        slist.add(new SearchResult<E>(e));
      }
    }
    return slist;
  }

  private static void registerCallbacks(ObjectContainer oc) {
    final EventRegistry registry = EventRegistryFactory.forObjectContainer(oc);
    registry.creating().addListener(new Timestamper(true));
    registry.updating().addListener(new Timestamper(false));
    final Versioner vsnr = new Versioner();
    registry.created().addListener(vsnr);
    registry.updated().addListener(vsnr);
  }

  /**
   * Timestamper
   * @author jpk
   */
  static class Timestamper implements EventListener4 {

    private final boolean creating;

    public Timestamper(boolean creating) {
      super();
      this.creating = creating;
    }

    @Override
    public void onEvent(Event4 e, EventArgs args) {
      final ObjectEventArgs queryArgs = ((ObjectEventArgs) args);
      final Object o = queryArgs.object();
      if(o instanceof ITimeStampEntity) {
        final Date now = new Date();
        if(creating) ((ITimeStampEntity) o).setDateCreated(now);
        ((ITimeStampEntity) o).setDateModified(now);
        log.debug("Timestamped entity: " + o);
      }
    }

  } // Timestamper

  /**
   * Versioner
   * @author jpk
   */
  static class Versioner implements EventListener4 {

    @Override
    public void onEvent(Event4 e, EventArgs args) {
      final ObjectEventArgs queryArgs = ((ObjectEventArgs) args);
      final Object o = queryArgs.object();
      if(o instanceof IVersionSupport) {
        final Integer cv = ((IVersionSupport) o).getVersion();
        ((IVersionSupport) o).setVersion(cv == null ? 0 : cv.intValue() + 1);
        log.debug("Versioned entity: " + o);
      }
    }

  } // Versioner

  private final IDb4oNamedQueryTranslator nqt;

  /**
   * Constructor
   * @param container The required db4o object container
   * @param namedQueryTranslator optional named query translator to handle named
   *        query based queries
   */
  @Inject
  public Db4oEntityDao(ObjectContainer container, IDb4oNamedQueryTranslator namedQueryTranslator) {
    super();
    this.nqt = namedQueryTranslator;
    setObjectContainer(container);
  }

  @Override
  protected Db4oTemplate createDb4oTemplate(ObjectContainer container) {
    final Db4oTemplate t = super.createDb4oTemplate(container);
    registerCallbacks(t.getObjectContainer());
    return t;
  }

  @Override
  public int executeQuery(String queryName, IQueryParam[] params) throws DataAccessException {
    throw new UnsupportedOperationException();
  }

  @Override
  public <E extends IEntity> List<SearchResult<?>> find(Criteria<E> criteria, Sorting sorting)
  throws InvalidCriteriaException, DataAccessException {
    if(criteria == null) {
      throw new InvalidCriteriaException("No criteria specified.");
    }
    if(criteria.getCriteriaType() == null) {
      throw new InvalidCriteriaException("A criteria type must be specified.");
    }
    final List<E> list = findEntities(criteria, sorting);

    // transform list
    // TODO handle case where we want a sub-set of properties (a tuple scalar)
    return transformEntityList(list, null);
  }

  @Override
  public <E extends IEntity> List<E> findByIds(Class<E> entityType, final Collection<String> ids, Sorting sorting)
  throws DataAccessException {
    return getDb4oTemplate().query(new Predicate<E>(entityType) {

      @Override
      public boolean match(E candidate) {
        return ids.contains(candidate.getId());
      }
    });
  }

  @Override
  public <E extends IEntity> List<E> findEntities(Criteria<E> criteria, final Sorting sorting)
  throws InvalidCriteriaException, DataAccessException {
    if(criteria == null) throw new InvalidCriteriaException("No criteria specified.");

    final Query query = getDb4oTemplate().query();

    if(criteria.getCriteriaType().isQuery()) {
      if(nqt == null) throw new InvalidCriteriaException("No db4o named query translator specified.");
      nqt.translateNamedQuery(criteria.getNamedQueryDefinition(), criteria.getQueryParams(), query);
    }
    else {
      query.constrain(criteria.getEntityClass());
      final CriterionGroup pg = criteria.getPrimaryGroup();
      if(pg != null && pg.isSet()) {
        for(final ICriterion ic : pg) {
          if(ic.isGroup()) throw new InvalidCriteriaException("Nested criterion groups are not supported");
          if(!ic.isSet()) throw new InvalidCriteriaException("criterion not set");
          final Criterion ctn = (Criterion) ic;
          final Object checkValue = ctn.getValue();
          final String pname = ctn.getPropertyName();

          Query pquery;
          if(pname.indexOf('.') > 0) {
            pquery = query;
            // descend one time for each node in the pname (which may be a dot
            // notated property path)
            final PropertyPath path = new PropertyPath(pname);
            for(final String node : path.nodes()) {
              pquery = pquery.descend(node);
            }
          }
          else {
            pquery = query.descend(pname);
          }

          switch(ctn.getComparator()) {
          case BETWEEN: {
            Object min, max;
            if(checkValue instanceof NumberRange) {
              final NumberRange range = (NumberRange) checkValue;
              min = range.getMinimumNumber();
              max = range.getMaximumNumber();
            }
            else if(checkValue instanceof DateRange) {
              final DateRange range = (DateRange) checkValue;
              min = range.getStartDate();
              max = range.getEndDate();
            }
            else {
              // presume an object array
              final Object[] oarr = (Object[]) checkValue;
              min = oarr[0];
              max = oarr[1];
            }
            pquery.constrain(min).greater().equal().or(pquery.constrain(max).smaller().equal());
            break;
          }
          case CONTAINS:
            pquery.constrain(checkValue).contains();
            break;
          case ENDS_WITH:
            pquery.constrain(checkValue).endsWith(ctn.isCaseSensitive());
            break;
          case EQUALS:
            if(!ctn.isCaseSensitive())
              throw new InvalidCriteriaException("Case insensitive equals checking is currently not supported");
            pquery.constrain(checkValue);
            break;
          case GREATER_THAN:
            pquery.constrain(checkValue).greater();
            break;
          case GREATER_THAN_EQUALS:
            pquery.constrain(checkValue).greater().equal();
            break;
          case IN: {
            Object[] arr;
            if(checkValue.getClass().isArray()) {
              arr = (Object[]) checkValue;
            }
            else if(checkValue instanceof Collection<?>) {
              arr = ((Collection) checkValue).toArray();
            }
            else if(checkValue instanceof String) {
              // assume comma-delimited string
              arr =
                org.springframework.util.ObjectUtils.toObjectArray(org.springframework.util.StringUtils
                    .commaDelimitedListToStringArray((String) checkValue));
            }
            else {
              throw new InvalidCriteriaException(
                  "Unsupported or null type for IN comparator: " + checkValue == null ? "<null>" : checkValue
                      .getClass().toString());
            }
            Constraint c = null;
            for(final Object o : arr) {
              if(c == null) {
                c = pquery.constrain(o);
              }
              else {
                c.or(pquery.constrain(o));
              }
            }
            break;
          }
          case IS:
            if(checkValue instanceof DBType == false) {
              throw new InvalidCriteriaException("IS clauses support only check values of type: "
                  + DBType.class.getSimpleName());
            }
            final DBType dbType = (DBType) checkValue;
            if(dbType == DBType.NULL) {
              // null
              pquery.constrain(null);
            }
            else {
              // not null
              pquery.constrain(null).not();
            }
          case LESS_THAN:
            pquery.constrain(checkValue).smaller();
            break;
          case LESS_THAN_EQUALS:
            pquery.constrain(checkValue).smaller().equal();
            break;
          case LIKE:
            pquery.constrain(checkValue).like();
            break;
          case NOT_EQUALS:
            pquery.constrain(checkValue).not();
            break;
          case STARTS_WITH:
            pquery.constrain(checkValue).startsWith(ctn.isCaseSensitive());
            break;
          } // comparator switch
        }
      }
    }

    // apply sorting
    if(sorting != null) {
      for(final SortColumn sc : sorting.getColumns()) {
        if(sc.isAscending()) {
          query.descend(sc.getPropertyName()).orderAscending();
        }
        else {
          query.descend(sc.getPropertyName()).orderDescending();
        }
      }
    }

    return (List<E>) getDb4oTemplate().execute(new Db4oCallback() {

      @Override
      public Object doInDb4o(ObjectContainer container) throws RuntimeException {
        return query.execute();
      }
    });
  }

  @Override
  public <E extends IEntity> E findEntity(Criteria<E> criteria) throws InvalidCriteriaException,
  EntityNotFoundException, NonUniqueResultException, DataAccessException {
    final List<E> list = findEntities(criteria, null);
    if(list == null || list.size() < 1) {
      throw new EntityNotFoundException("No matching entity found.");
    }
    else if(list.size() > 1) {
      throw new NonUniqueResultException("More than one matching entity found.");
    }
    assert list.size() == 1;
    return list.get(0);
  }

  @Override
  public <E extends IEntity> List<String> getIds(Criteria<E> criteria, Sorting sorting)
  throws InvalidCriteriaException, DataAccessException {
    final List<E> list = findEntities(criteria, sorting);
    if(list == null) {
      return null;
    }
    final List<String> idlist = new ArrayList<String>();
    for(final E e : list) {
      idlist.add(e.getId());
    }
    return idlist;
  }

  @Override
  public <E extends IEntity> IPageResult<SearchResult<?>> getPage(Criteria<E> criteria, Sorting sorting, int offset,
      int pageSize) throws InvalidCriteriaException, DataAccessException {
    List<SearchResult<?>> elist = find(criteria, sorting);
    if(elist == null) {
      elist = new ArrayList<SearchResult<?>>();
    }
    final int size = elist.size();
    if(size >= 1) {
      int fi = offset;
      int li = fi + pageSize;
      if(fi > size - 1) {
        fi = size - 1;
      }
      if(li > size - 1) {
        li = size; // NOTE: exclusive index
      }
      elist = elist.subList(fi, li);
    }
    final List<SearchResult<?>> subList = elist;
    return new IPageResult<SearchResult<?>>() {

      @Override
      public List<SearchResult<?>> getPageList() {
        return subList;
      }

      @Override
      public int getResultCount() {
        return size;
      }
    };
  }

  /**
   * Loads entities by a given {@link Predicate}.
   * @param <E>
   * @param p the predicate
   * @param key The key that identifies the entity to be loaded
   * @return All matching entities
   */
  private <E extends IEntity> E loadByPredicate(Predicate<E> p, IKey<E> key) throws EntityNotFoundException, DataAccessException {
    final List<E> list = getDb4oTemplate().query(p);
    if(list == null || list.size() < 1) {
      final String msg = "No matching entity found for key: [" + key +  ']';
      log.debug(msg);
      throw new EntityNotFoundException(msg);
    }
    if(list.size() > 1) {
      final String msg = list.size() + " matching entities found (not one) for key: [" + key + ']';
      log.debug(msg);
      throw new EntityNotFoundException(msg);
    }
    assert list.size() == 1;
    return list.get(0);
  }

  @Override
  public <E extends IEntity> E load(final PrimaryKey<E> key) throws EntityNotFoundException, DataAccessException {
    log.debug("Loading entity by PK: " + key);
    return loadByPredicate(new Predicate<E>(key.getType()) {

      @Override
      public boolean match(E candidate) {
        return candidate.getId().equals(key.getId());
      }
    }, key);
  }

  @Override
  public <E extends IEntity> E load(final IBusinessKey<E> key) throws EntityNotFoundException, DataAccessException {
    return loadByPredicate(new Predicate<E>(key.getType()) {

      @Override
      public boolean match(E candidate) {
        return BusinessKeyUtil.equals(candidate, key);
      }
    }, key);
  }

  @Override
  public <N extends INamedEntity> N load(final NameKey<N> nameKey) throws EntityNotFoundException,
  NonUniqueResultException, DataAccessException {
    return loadByPredicate(new Predicate<N>(nameKey.getType()) {

      @Override
      public boolean match(N candidate) {
        return nameKey.getName().equals(candidate.getName());
      }
    }, nameKey);
  }

  @Override
  public <E extends IEntity> List<E> loadAll(Class<E> entityType) throws DataAccessException {
    final List<E> list = getDb4oTemplate().query(new Predicate<E>(entityType) {

      @Override
      public boolean match(E candidate) {
        return true;
      }
    });
    return list;
  }

  @Override
  public <E extends IEntity> E persist(E entity) throws EntityExistsException, DataAccessException {
    log.debug("Persisting entity: " + entity);
    // must check for business key uniqueness first!
    try {
      final List<E> list = (List<E>) loadAll(entity.entityClass());
      final ArrayList<E> mlist = new ArrayList<E>((list == null ? 0 : list.size()) + 1);
      mlist.addAll(list);
      if(entity.isNew()) {
        mlist.add(entity);
      }
      else {
        // find it in mlist and replace
        for(int i = 0; i < mlist.size(); i++) {
          final E e = mlist.get(i);
          if(e.equals(entity)) {
            mlist.set(i, entity);
            break;
          }
        }
      }
      BusinessKeyUtil.isBusinessKeyUnique(mlist);
    }
    catch(final NonUniqueBusinessKeyException e) {
      throw new EntityExistsException(e.getMessage());
    }
    catch(final BusinessKeyPropertyException e) {
      throw new IllegalStateException(e);
    }
    getDb4oTemplate().store(entity);
    return entity;
  }

  @Override
  public <E extends IEntity> Collection<E> persistAll(Collection<E> entities) throws DataAccessException {
    if(entities != null) {
      for(final E e : entities) {
        persist(e);
      }
    }
    return entities;
  }

  @Override
  public <E extends IEntity> void purge(E entity) throws EntityNotFoundException, DataAccessException {
    log.debug("Purging entity: " + entity);
    purge(new PrimaryKey(entity));
  }

  @Override
  public <E extends IEntity> void purge(PrimaryKey<E> key) throws EntityNotFoundException, DataAccessException {
    final E existing = load(key);
    if(existing == null) throw new EntityNotFoundException("Entity of primary key: " + key + " not found for purging");
    getDb4oTemplate().delete(existing);
    getDb4oTemplate().purge(existing);
  }

  @Override
  public <E extends IEntity> void purgeAll(Collection<E> entities) throws DataAccessException {
    if(entities != null) {
      for(final E e : entities) {
        purge(e);
      }
    }
  }

}
TOP

Related Classes of com.tll.dao.db4o.Db4oEntityDao

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.