All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.querydsl.mongodb.AbstractMongodbQuery Maven / Gradle / Ivy

There is a newer version: 6.8
Show newest version
/*
 * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
 *
 * 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.querydsl.mongodb;

import com.mongodb.*;
import com.mysema.commons.lang.CloseableIterator;
import com.querydsl.core.*;
import com.querydsl.core.support.QueryMixin;
import com.querydsl.core.types.*;
import com.querydsl.core.types.dsl.CollectionPathBase;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;

/**
 * {@code AbstractMongodbQuery} provides a base class for general Querydsl query implementation with
 * a pluggable DBObject to Bean transformation
 *
 * @author laimw
 * @param  result type
 * @param  concrete subtype
 */
public abstract class AbstractMongodbQuery>
    implements SimpleQuery, Fetchable {

  @SuppressWarnings("serial")
  private static class NoResults extends RuntimeException {}

  private final MongodbSerializer serializer;

  private final QueryMixin queryMixin;

  private final DBCollection collection;

  private final Function transformer;

  private ReadPreference readPreference;

  /**
   * Create a new MongodbQuery instance
   *
   * @param collection collection
   * @param transformer result transformer
   * @param serializer serializer
   */
  @SuppressWarnings("unchecked")
  public AbstractMongodbQuery(
      DBCollection collection, Function transformer, MongodbSerializer serializer) {
    @SuppressWarnings("unchecked") // Q is this plus subclass
    Q query = (Q) this;
    this.queryMixin = new QueryMixin(query, new DefaultQueryMetadata(), false);
    this.transformer = transformer;
    this.collection = collection;
    this.serializer = serializer;
  }

  /**
   * Define a join
   *
   * @param ref reference
   * @param target join target
   * @return join builder
   */
  public  JoinBuilder join(Path ref, Path target) {
    return new JoinBuilder(queryMixin, ref, target);
  }

  /**
   * Define a join
   *
   * @param ref reference
   * @param target join target
   * @return join builder
   */
  public  JoinBuilder join(CollectionPathBase ref, Path target) {
    return new JoinBuilder(queryMixin, ref, target);
  }

  /**
   * Define a constraint for an embedded object
   *
   * @param collection collection
   * @param target target
   * @return builder
   */
  public  AnyEmbeddedBuilder anyEmbedded(
      Path> collection, Path target) {
    return new AnyEmbeddedBuilder(queryMixin, collection);
  }

  protected abstract DBCollection getCollection(Class type);

  @Nullable
  protected Predicate createFilter(QueryMetadata metadata) {
    Predicate filter;
    if (!metadata.getJoins().isEmpty()) {
      filter = ExpressionUtils.allOf(metadata.getWhere(), createJoinFilter(metadata));
    } else {
      filter = metadata.getWhere();
    }
    return filter;
  }

  @SuppressWarnings("unchecked")
  @Nullable
  protected Predicate createJoinFilter(QueryMetadata metadata) {
    Map, Predicate> predicates = new HashMap<>();
    List joins = metadata.getJoins();
    for (int i = joins.size() - 1; i >= 0; i--) {
      JoinExpression join = joins.get(i);
      Path source = (Path) ((Operation) join.getTarget()).getArg(0);
      Path target = (Path) ((Operation) join.getTarget()).getArg(1);

      final Predicate extraFilters = predicates.get(target.getRoot());
      Predicate filter = ExpressionUtils.allOf(join.getCondition(), extraFilters);
      List ids = getIds(target.getType(), filter);
      if (ids.isEmpty()) {
        throw new NoResults();
      }
      Path path = ExpressionUtils.path(String.class, source, "$id");
      predicates.merge(
          source.getRoot(), ExpressionUtils.in((Path) path, ids), ExpressionUtils::and);
    }
    Path source = (Path) ((Operation) joins.get(0).getTarget()).getArg(0);
    return predicates.get(source.getRoot());
  }

  protected List getIds(Class targetType, Predicate condition) {
    DBCollection collection = getCollection(targetType);
    // TODO : fetch only ids
    DBCursor cursor =
        createCursor(
            collection,
            condition,
            null,
            QueryModifiers.EMPTY,
            Collections.>emptyList());
    if (cursor.hasNext()) {
      List ids = new ArrayList(cursor.count());
      for (DBObject obj : cursor) {
        ids.add(obj.get("_id"));
      }
      return ids;
    } else {
      return Collections.emptyList();
    }
  }

  @Override
  public Q distinct() {
    return queryMixin.distinct();
  }

  public Q where(Predicate e) {
    return queryMixin.where(e);
  }

  @Override
  public Q where(Predicate... e) {
    return queryMixin.where(e);
  }

  @Override
  public Q limit(long limit) {
    return queryMixin.limit(limit);
  }

  @Override
  public Q offset(long offset) {
    return queryMixin.offset(offset);
  }

  @Override
  public Q restrict(QueryModifiers modifiers) {
    return queryMixin.restrict(modifiers);
  }

  public Q orderBy(OrderSpecifier o) {
    return queryMixin.orderBy(o);
  }

  @Override
  public Q orderBy(OrderSpecifier... o) {
    return queryMixin.orderBy(o);
  }

  @Override
  public  Q set(ParamExpression param, T value) {
    return queryMixin.set(param, value);
  }

  /**
   * Iterate with the specific fields
   *
   * @param paths fields to return
   * @return iterator
   */
  public CloseableIterator iterate(Path... paths) {
    queryMixin.setProjection(paths);
    return iterate();
  }

  @Override
  public CloseableIterator iterate() {
    final DBCursor cursor = createCursor();
    return new CloseableIterator() {
      @Override
      public boolean hasNext() {
        return cursor.hasNext();
      }

      @Override
      public K next() {
        return transformer.apply(cursor.next());
      }

      @Override
      public void remove() {}

      @Override
      public void close() {}
    };
  }

  /**
   * Fetch with the specific fields
   *
   * @param paths fields to return
   * @return results
   */
  public List fetch(Path... paths) {
    queryMixin.setProjection(paths);
    return fetch();
  }

  @Override
  public List fetch() {
    try {
      DBCursor cursor = createCursor();
      List results = new ArrayList();
      for (DBObject dbObject : cursor) {
        results.add(transformer.apply(dbObject));
      }
      return results;
    } catch (NoResults ex) {
      return Collections.emptyList();
    }
  }

  protected DBCursor createCursor() {
    QueryMetadata metadata = queryMixin.getMetadata();
    Predicate filter = createFilter(metadata);
    return createCursor(
        collection,
        filter,
        metadata.getProjection(),
        metadata.getModifiers(),
        metadata.getOrderBy());
  }

  protected DBCursor createCursor(
      DBCollection collection,
      @Nullable Predicate where,
      Expression projection,
      QueryModifiers modifiers,
      List> orderBy) {
    DBCursor cursor = collection.find(createQuery(where), createProjection(projection));
    Integer limit = modifiers.getLimitAsInteger();
    Integer offset = modifiers.getOffsetAsInteger();
    if (limit != null) {
      cursor.limit(limit);
    }
    if (offset != null) {
      cursor.skip(offset);
    }
    if (orderBy.size() > 0) {
      cursor.sort(serializer.toSort(orderBy));
    }
    if (readPreference != null) {
      cursor.setReadPreference(readPreference);
    }
    return cursor;
  }

  private DBObject createProjection(Expression projection) {
    if (projection instanceof FactoryExpression) {
      DBObject obj = new BasicDBObject();
      for (Object expr : ((FactoryExpression) projection).getArgs()) {
        if (expr instanceof Expression) {
          obj.put((String) serializer.handle((Expression) expr), 1);
        }
      }
      return obj;
    }
    return null;
  }

  /**
   * Fetch first with the specific fields
   *
   * @param paths fields to return
   * @return first result
   */
  public K fetchFirst(Path... paths) {
    queryMixin.setProjection(paths);
    return fetchFirst();
  }

  @Override
  public K fetchFirst() {
    try {
      DBCursor c = createCursor().limit(1);
      if (c.hasNext()) {
        return transformer.apply(c.next());
      } else {
        return null;
      }
    } catch (NoResults ex) {
      return null;
    }
  }

  /**
   * Fetch one with the specific fields
   *
   * @param paths fields to return
   * @return first result
   */
  public K fetchOne(Path... paths) {
    queryMixin.setProjection(paths);
    return fetchOne();
  }

  @Override
  public K fetchOne() throws NonUniqueResultException {
    try {
      Long limit = queryMixin.getMetadata().getModifiers().getLimit();
      if (limit == null) {
        limit = 2L;
      }
      DBCursor c = createCursor().limit(limit.intValue());
      if (c.hasNext()) {
        K rv = transformer.apply(c.next());
        if (c.hasNext()) {
          throw new NonUniqueResultException();
        }
        return rv;
      } else {
        return null;
      }
    } catch (NoResults ex) {
      return null;
    }
  }

  /**
   * Fetch results with the specific fields
   *
   * @param paths fields to return
   * @return results
   */
  public QueryResults fetchResults(Path... paths) {
    queryMixin.setProjection(paths);
    return fetchResults();
  }

  @Override
  public QueryResults fetchResults() {
    try {
      long total = fetchCount();
      if (total > 0L) {
        return new QueryResults(fetch(), queryMixin.getMetadata().getModifiers(), total);
      } else {
        return QueryResults.emptyResults();
      }
    } catch (NoResults ex) {
      return QueryResults.emptyResults();
    }
  }

  @Override
  public long fetchCount() {
    try {
      Predicate filter = createFilter(queryMixin.getMetadata());
      return collection.count(createQuery(filter));
    } catch (NoResults ex) {
      return 0L;
    }
  }

  private DBObject createQuery(@Nullable Predicate predicate) {
    if (predicate != null) {
      return (DBObject) serializer.handle(predicate);
    } else {
      return new BasicDBObject();
    }
  }

  /**
   * Sets the read preference for this query
   *
   * @param readPreference read preference
   */
  public void setReadPreference(ReadPreference readPreference) {
    this.readPreference = readPreference;
  }

  /**
   * Get the where definition as a DBObject instance
   *
   * @return
   */
  public DBObject asDBObject() {
    return createQuery(queryMixin.getMetadata().getWhere());
  }

  @Override
  public String toString() {
    return asDBObject().toString();
  }
}