edu.harvard.med.screensaver.db.datafetcher.TupleDataFetcher.java Source code

Java tutorial

Introduction

Here is the source code for edu.harvard.med.screensaver.db.datafetcher.TupleDataFetcher.java

Source

// $HeadURL$
// $Id$

// Copyright  2006, 2010, 2011, 2012 by the President and Fellows of Harvard College.

// Screensaver is an open-source project developed by the ICCB-L and NSRB labs
// at Harvard Medical School. This software is distributed under the terms of
// the GNU General Public License.

package edu.harvard.med.screensaver.db.datafetcher;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import org.apache.log4j.Logger;
import org.hibernate.Session;

import edu.harvard.med.screensaver.db.Criterion.Operator;
import edu.harvard.med.screensaver.db.GenericEntityDAO;
import edu.harvard.med.screensaver.db.Query;
import edu.harvard.med.screensaver.db.hqlbuilder.HqlBuilder;
import edu.harvard.med.screensaver.db.hqlbuilder.JoinType;
import edu.harvard.med.screensaver.model.AbstractEntity;
import edu.harvard.med.screensaver.model.meta.Cardinality;
import edu.harvard.med.screensaver.model.meta.PropertyNameAndValue;
import edu.harvard.med.screensaver.model.meta.PropertyPath;
import edu.harvard.med.screensaver.model.meta.RelationshipPath;

/**
 * DataFetcher that fetches tuples from persistent storage. Each tuple property is specified via {@link PropertyPath},
 * to be specified via {@link #setPropertiesToFetch}.
 */
public class TupleDataFetcher<E extends AbstractEntity, K> extends PropertyPathDataFetcher<Tuple<K>, E, K> {
    private static Logger log = Logger.getLogger(TupleDataFetcher.class);

    private static final String COV_ALIAS_SUFFIX = "COV";

    private PropertyPath<E> _idProperty;

    public TupleDataFetcher(Class<E> rootEntityClass, GenericEntityDAO dao) {
        super(rootEntityClass, dao);
        _idProperty = RelationshipPath.from(_rootEntityClass).toId();
    }

    @Override
    public List<Tuple<K>> fetchAllData() {
        log.debug("fetching all data");
        return Lists.newArrayList(doFetchData(Collections.<K>emptySet()).values());
    }

    @Override
    public Map<K, Tuple<K>> fetchData(Set<K> keys) {
        if (log.isDebugEnabled()) {
            log.debug("fetching data subset: " + keys);
        }
        Map<K, Tuple<K>> result = doFetchData(keys);
        assert result.size() == keys.size() : "fetch data query result did not return all requested entities";
        return result;
    }

    /**
     * @param keys if null, fetches all entities for the root entity type (subject
     *          to normal column criteria)
     */
    protected Map<K, Tuple<K>> doFetchData(Set<K> keys) {
        // collate properties into groups of PropertyPaths having same RelationshipPath; 
        // this will allow us to execute one query for each group of properties that are from the same entity type 
        Multimap<RelationshipPath<E>, PropertyPath<E>> pathGroups = Multimaps.index(getProperties(),
                new Function<PropertyPath<E>, RelationshipPath<E>>() {
                    public RelationshipPath<E> apply(PropertyPath<E> p) {
                        return p.getAncestryPath();
                    }
                });

        Map<K, Tuple<K>> tuples = Maps.newHashMapWithExpectedSize(keys.size());
        for (Collection<PropertyPath<E>> propertyPaths : pathGroups.asMap().values()) {
            List<PropertyPath<E>> orderedPropertyPaths = Lists.newArrayList(propertyPaths);
            if (log.isDebugEnabled()) {
                log.debug("fetching " + keys.size() + " values for properties " + orderedPropertyPaths);
            }
            List<Object[]> result = _dao.runQuery(buildQueryForProperty(orderedPropertyPaths, keys));
            packageResultIntoTuples(tuples, orderedPropertyPaths, result);
        }
        return tuples;
    }

    private void packageResultIntoTuples(Map<K, Tuple<K>> tuples, List<PropertyPath<E>> orderedPropertyPaths,
            List<Object[]> result) {
        for (Object[] row : result) {
            assert row.length == orderedPropertyPaths.size() + 1;
            for (int i = 0; i < orderedPropertyPaths.size(); ++i) {
                setTupleProperty(getOrCreateTuple(tuples, (K) row[0]), orderedPropertyPaths.get(i), row[i + 1]);
            }
        }
    }

    private void setTupleProperty(Tuple<K> tuple, PropertyPath<E> propertyPath, Object propertyValue) {
        if (propertyPath.getCardinality() == Cardinality.TO_MANY) {
            tuple.addMultiPropertyElement(makePropertyKey(propertyPath), propertyValue);
        } else {
            tuple.addProperty(makePropertyKey(propertyPath), propertyValue);
        }
    }

    private Tuple<K> getOrCreateTuple(Map<K, Tuple<K>> tuples, K tupleKey) {
        if (!tuples.containsKey(tupleKey)) {
            tuples.put(tupleKey, new Tuple<K>(tupleKey));
        }
        Tuple<K> tuple = tuples.get(tupleKey);
        assert tuple != null;
        return tuple;
    }

    public static String makePropertyKey(PropertyPath<?> propertyPath) {
        return propertyPath.toString().split("\\.", 2)[1];
    }

    private Query buildQueryForProperty(List<PropertyPath<E>> propertyPaths, Set<K> keys) {
        final HqlBuilder hql = new HqlBuilder();
        Map<RelationshipPath<E>, String> path2Alias = Maps.newHashMap();
        String rootEntityIdPropertyName = "id";
        String propertyEntityAlias;
        assert propertyPaths.size() >= 1;
        RelationshipPath<E> relPath = propertyPaths.get(0).getAncestryPath();

        // if possible, eliminate the root entity from the query, saving a join operation.
        // this can only occur if the property to be retrieved is from an entity that is directly related to the root entity via a to-one relationship
        if (!!!keys.isEmpty()) { // cannot apply this optimization if we're asked to fetch all data, since eliminating the root entity can break the expectations of addDomainRestrictions() implementations, which is called below 
            Iterator<String> inversePathIter = relPath.inversePathIterator();
            if (inversePathIter.hasNext()) {
                String inverseEntityName = inversePathIter.next();
                if (inverseEntityName != null) {
                    // select tuple ID property from the second entity, rather than the root entity
                    path2Alias.put(relPath, getRootAlias());
                    assert relPath.entityClassIterator().hasNext();
                    hql.from(relPath.entityClassIterator().next(), getRootAlias());
                    rootEntityIdPropertyName = inverseEntityName + "." + rootEntityIdPropertyName;

                    // explicitly add restriction from rootEntity->relatedEntity, since this restriction would otherwise be lost
                    Iterator<PropertyNameAndValue> restrictionIterator = relPath.restrictionIterator();
                    PropertyNameAndValue restriction = restrictionIterator.hasNext() ? restrictionIterator.next()
                            : null;
                    if (restriction != null) {
                        hql.where(getRootAlias(), restriction.getName(), Operator.EQUAL, restriction.getValue());
                    }
                }
            }
        }

        propertyEntityAlias = getOrCreateJoin(hql, relPath, path2Alias, JoinType.LEFT);
        hql.select(getRootAlias(), rootEntityIdPropertyName);

        for (PropertyPath<E> propertyPath : propertyPaths) {
            if (propertyPath.isCollectionOfValues()) {
                // retrieve entire element as a tuple property
                String covAlias = getOrCreateJoin(hql, propertyPath, path2Alias, JoinType.LEFT);
                hql.select(covAlias);
            } else if (propertyPath.getPropertyName().equals(PropertyPath.FULL_ENTITY)) {
                // retrieve entire entity as a tuple property
                hql.select(propertyEntityAlias);
            } else {
                hql.select(propertyEntityAlias, propertyPath.getPropertyName());
            }
        }

        if (!keys.isEmpty()) {
            hql.whereIn(getRootAlias(), rootEntityIdPropertyName, keys);
        } else {
            // if explicit set of keys has not been provided, we must still
            // restrict result with top-level restrictions
            addDomainRestrictions(hql);
        }

        if (log.isDebugEnabled()) {
            log.debug("fetch data query for properties " + propertyPaths + ": " + hql);
        }

        return new Query() {
            @Override
            public List execute(Session session) {
                return hql.toQuery(session, true).list();
            }
        };
    }
}