Beispiel #1
0
  @Test
  @SuppressWarnings("unchecked")
  public void testBoolean() throws Exception {
    Transaction transaction = fullTextSession.beginTransaction();
    final QueryBuilder monthQb =
        fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(Month.class).get();

    // must
    Query query =
        monthQb
            .bool()
            .must(monthQb.keyword().onField("mythology").matching("colder").createQuery())
            .createQuery();

    List<Month> results = fullTextSession.createFullTextQuery(query, Month.class).list();
    assertEquals(1, results.size());
    assertEquals("January", results.get(0).getName());

    // must not + all
    query =
        monthQb
            .bool()
            .should(monthQb.all().createQuery())
            .must(monthQb.keyword().onField("mythology").matching("colder").createQuery())
            .not()
            .createQuery();

    results = fullTextSession.createFullTextQuery(query, Month.class).list();
    assertEquals(2, results.size());
    assertEquals("February", results.get(0).getName());
    assertEquals("March", results.get(1).getName());

    // implicit must not + all (not recommended)
    query =
        monthQb
            .bool()
            .must(monthQb.keyword().onField("mythology").matching("colder").createQuery())
            .not()
            .createQuery();
    results = fullTextSession.createFullTextQuery(query, Month.class).list();
    assertEquals(2, results.size());
    assertEquals("February", results.get(0).getName());
    assertEquals("March", results.get(1).getName());

    // all except (recommended)
    query =
        monthQb
            .all()
            .except(monthQb.keyword().onField("mythology").matching("colder").createQuery())
            .createQuery();

    results = fullTextSession.createFullTextQuery(query, Month.class).list();
    assertEquals(2, results.size());
    assertEquals("February", results.get(0).getName());
    assertEquals("March", results.get(1).getName());

    transaction.commit();
  }
Beispiel #2
0
  // locate a customer in the database
  public Customers extractCustomer(String email, String password) {

    FullTextEntityManager fullTextEntityManager =
        org.hibernate.search.jpa.Search.getFullTextEntityManager(em);

    QueryBuilder queryBuilder =
        fullTextEntityManager
            .getSearchFactory()
            .buildQueryBuilder()
            .forEntity(Customers.class)
            .get();
    org.apache.lucene.search.Query query =
        queryBuilder
            .bool()
            .must(queryBuilder.keyword().onField("email").matching(email).createQuery())
            .must(queryBuilder.keyword().onField("password").matching(password).createQuery())
            .createQuery();

    FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery(query, Customers.class);

    fullTextQuery.initializeObjectsWith(
        ObjectLookupMethod.SKIP, DatabaseRetrievalMethod.FIND_BY_ID);

    List results = fullTextQuery.getResultList();

    if (results.isEmpty()) {
      return null;
    }

    return (Customers) results.get(0);
  }
Beispiel #3
0
  public static List<Object> performSearch(
      Select command, Class<?> type, String cacheName, CacheContainerWrapper cache)
      throws TranslatorException {

    LogManager.logTrace(LogConstants.CTX_CONNECTOR, "Using Lucene Searching."); // $NON-NLS-1$

    // Map<?, ?> cache,
    SearchManager searchManager = Search.getSearchManager((Cache<?, ?>) cache.getCache(cacheName));

    QueryBuilder queryBuilder = searchManager.buildQueryBuilderForClass(type).get();

    BooleanJunction<BooleanJunction> junction = queryBuilder.bool();
    boolean createdQueries = buildQueryFromWhereClause(command.getWhere(), junction, queryBuilder);

    Query query = null;
    if (createdQueries) {
      query = junction.createQuery();
    } else {
      query = queryBuilder.all().createQuery();
    }

    CacheQuery cacheQuery = searchManager.getQuery(query, type); // rootNodeType

    List<Object> results = cacheQuery.list();
    if (results == null || results.isEmpty()) {
      return Collections.emptyList();
    }

    return results;
  }
Beispiel #4
0
 @Test(expected = SearchException.class)
 public void testIllegalBooleanJunction() {
   final QueryBuilder monthQb =
       fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(Month.class).get();
   // forgetting to set any condition on the boolean, an exception shall be thrown:
   BooleanJunction<BooleanJunction> booleanJunction = monthQb.bool();
   assertTrue(booleanJunction.isEmpty());
   Query query = booleanJunction.createQuery();
   Assert.fail("should not reach this point");
 }
Beispiel #5
0
  @Test
  @SuppressWarnings("unchecked")
  public void testQueryCustomization() throws Exception {
    Transaction transaction = fullTextSession.beginTransaction();
    final QueryBuilder monthQb =
        fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(Month.class).get();

    // combined query, January and february both contain whitening but February in a longer text
    Query query =
        monthQb
            .bool()
            .should(monthQb.keyword().onField("mythology").matching("whitening").createQuery())
            .should(monthQb.keyword().onField("history").matching("whitening").createQuery())
            .createQuery();

    List<Month> results = fullTextSession.createFullTextQuery(query, Month.class).list();
    assertEquals(2, results.size());
    assertEquals("January", results.get(0).getName());

    // boosted query, January and february both contain whitening but February in a longer text
    // since history is boosted, February should come first though
    query =
        monthQb
            .bool()
            .should(monthQb.keyword().onField("mythology").matching("whitening").createQuery())
            .should(
                monthQb
                    .keyword()
                    .onField("history")
                    .boostedTo(30)
                    .matching("whitening")
                    .createQuery())
            .createQuery();

    results = fullTextSession.createFullTextQuery(query, Month.class).list();
    assertEquals(2, results.size());
    assertEquals("February", results.get(0).getName());

    // FIXME add other method tests besides boostedTo

    transaction.commit();
  }
 private Query getQuery(QueryBuilder queryBuilder, List<Query> queries) {
   if (queries.isEmpty()) {
     return queryBuilder.all().createQuery();
   } else {
     BooleanJunction<BooleanJunction> bool = queryBuilder.bool();
     for (Query query : queries) {
       bool.must(query);
     }
     return bool.createQuery();
   }
 }
  private FullTextQuery buildFullTextQuery(
      UserSearchRequest request, Pageable pageable, Criteria criteria) {
    FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
    QueryBuilder qb =
        fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(User.class).get();

    @SuppressWarnings("rawtypes")
    BooleanJunction<BooleanJunction> junction = qb.bool();
    junction.must(qb.all().createQuery());

    if (StringUtils.hasText(request.getKeyword())) {
      Analyzer analyzer = fullTextEntityManager.getSearchFactory().getAnalyzer("synonyms");
      String[] fields =
          new String[] {
            "loginId", "name.firstName", "name.lastName",
          };
      MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, analyzer);
      parser.setDefaultOperator(QueryParser.Operator.AND);
      Query query = null;
      try {
        query = parser.parse(request.getKeyword());
      } catch (ParseException e1) {
        try {
          query = parser.parse(QueryParser.escape(request.getKeyword()));
        } catch (ParseException e2) {
          throw new RuntimeException(e2);
        }
      }
      junction.must(query);
    }

    if (!CollectionUtils.isEmpty(request.getRoles())) {
      for (User.Role role : request.getRoles()) {
        junction.must(qb.keyword().onField("roles").matching(role).createQuery());
      }
    }

    Query searchQuery = junction.createQuery();

    Sort sort = new Sort(new SortField("id", SortField.Type.STRING, false));

    FullTextQuery persistenceQuery =
        fullTextEntityManager
            .createFullTextQuery(searchQuery, User.class)
            .setCriteriaQuery(criteria)
            //				.setProjection("id")
            .setSort(sort);
    if (pageable != null) {
      persistenceQuery.setFirstResult(pageable.getOffset());
      persistenceQuery.setMaxResults(pageable.getPageSize());
    }
    return persistenceQuery;
  }
  @Override
  public Page<Comment> search(CommentSearchRequest request, Pageable pageable) {
    FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
    QueryBuilder qb =
        fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(Comment.class).get();

    @SuppressWarnings("rawtypes")
    BooleanJunction<BooleanJunction> junction = qb.bool();
    junction.must(qb.all().createQuery());

    if (StringUtils.hasText(request.getKeyword())) {
      Analyzer analyzer = fullTextEntityManager.getSearchFactory().getAnalyzer("synonyms");
      String[] fields = new String[] {"authorName", "content"};
      MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, analyzer);
      parser.setDefaultOperator(QueryParser.Operator.AND);
      Query query = null;
      try {
        query = parser.parse(request.getKeyword());
      } catch (ParseException e1) {
        try {
          query = parser.parse(QueryParser.escape(request.getKeyword()));
        } catch (ParseException e2) {
          throw new RuntimeException(e2);
        }
      }
      junction.must(query);
    }

    if (StringUtils.hasText(request.getLanguage())) {
      junction.must(
          qb.keyword().onField("post.language").matching(request.getLanguage()).createQuery());
    }

    if (request.getPostId() != null) {
      junction.must(qb.keyword().onField("post.id").matching(request.getPostId()).createQuery());
    }

    if (request.getApproved() != null) {
      junction.must(qb.keyword().onField("approved").matching(request.getApproved()).createQuery());
    }

    Query searchQuery = junction.createQuery();

    Session session = (Session) entityManager.getDelegate();
    Criteria criteria =
        session
            .createCriteria(Comment.class)
            .setFetchMode("post", FetchMode.JOIN)
            .setFetchMode("author", FetchMode.JOIN);

    Sort sort = null;
    if (pageable.getSort() != null) {
      if (pageable.getSort().getOrderFor("date") != null) {
        Order order = pageable.getSort().getOrderFor("date");
        sort =
            new Sort(
                new SortField(
                    "date", SortField.Type.STRING, order.getDirection().equals(Direction.DESC)),
                new SortField(
                    "id", SortField.Type.LONG, order.getDirection().equals(Direction.DESC)));
      }
    }

    if (sort == null) {
      sort =
          new Sort(
              new SortField("date", SortField.Type.STRING),
              new SortField("id", SortField.Type.LONG));
    }

    FullTextQuery persistenceQuery =
        fullTextEntityManager
            .createFullTextQuery(searchQuery, Comment.class)
            .setCriteriaQuery(criteria)
            .setSort(sort);
    persistenceQuery.setFirstResult(pageable.getOffset());
    persistenceQuery.setMaxResults(pageable.getPageSize());

    int resultSize = persistenceQuery.getResultSize();

    @SuppressWarnings("unchecked")
    List<Comment> results = persistenceQuery.getResultList();
    return new PageImpl<>(results, pageable, resultSize);
  }
Beispiel #9
0
  private static boolean buildQueryFromWhereClause(
      Condition criteria, BooleanJunction<BooleanJunction> junction, QueryBuilder queryBuilder)
      throws TranslatorException {
    boolean createdQueries = false;
    BooleanJunction<BooleanJunction> inUse = junction;

    if (criteria instanceof AndOr) {
      LogManager.logTrace(LogConstants.CTX_CONNECTOR, "Parsing compound criteria."); // $NON-NLS-1$
      AndOr crit = (AndOr) criteria;
      AndOr.Operator op = crit.getOperator();

      switch (op) {
        case AND:
          BooleanJunction<BooleanJunction> leftAnd = queryBuilder.bool();
          boolean andLeftHasQueries =
              buildQueryFromWhereClause(crit.getLeftCondition(), leftAnd, queryBuilder);

          BooleanJunction<BooleanJunction> rightAnd = queryBuilder.bool();
          boolean andRightHasQueries =
              buildQueryFromWhereClause(crit.getRightCondition(), rightAnd, queryBuilder);

          if (andLeftHasQueries && andRightHasQueries) {
            leftAnd.must(rightAnd.createQuery());
            inUse.should(leftAnd.createQuery());
          } else if (andLeftHasQueries) {

            inUse.should(leftAnd.createQuery());
          } else if (andRightHasQueries) {
            inUse.should(rightAnd.createQuery());
          }

          createdQueries = (andLeftHasQueries ? andLeftHasQueries : andRightHasQueries);

          break;

        case OR:
          boolean orLeftHasQueries =
              buildQueryFromWhereClause(crit.getLeftCondition(), inUse, queryBuilder);
          boolean orRightHasQueries =
              buildQueryFromWhereClause(crit.getRightCondition(), inUse, queryBuilder);

          createdQueries = (orLeftHasQueries ? orLeftHasQueries : orRightHasQueries);

          break;

        default:
          final String msg =
              ObjectPlugin.Util.getString(
                  "LuceneSearch.invalidOperator",
                  new Object[] {op, "And, Or"}); // $NON-NLS-1$ //$NON-NLS-2$
          throw new TranslatorException(msg);
      }

    } else if (criteria instanceof Comparison) {
      createdQueries = visit((Comparison) criteria, inUse, queryBuilder);

    } else if (criteria instanceof Exists) {
      LogManager.logTrace(
          LogConstants.CTX_CONNECTOR,
          "Parsing EXISTS criteria: NOT IMPLEMENTED YET"); //$NON-NLS-1$
      // TODO Exists should be supported in a future release.

    } else if (criteria instanceof Like) {
      createdQueries = visit((Like) criteria, inUse, queryBuilder);

    } else if (criteria instanceof In) {
      createdQueries = visit((In) criteria, inUse, queryBuilder);
    }
    // else if (criteria instanceof Not) {
    //			LogManager.logTrace(LogConstants.CTX_CONNECTOR, "Parsing NOT criteria."); //$NON-NLS-1$
    // isNegated = true;
    // filterList.addAll(getSearchFilterFromWhereClause(((Not)criteria).getCriteria(),
    // new LinkedList<String>()));
    // }

    return createdQueries;
  }
  /** Execute Lucene index query. */
  private QueryResponse executeQuery(
      AdvancedCache<byte[], byte[]> cache, SerializationContext serCtx, QueryRequest request) {
    final SearchManager searchManager = Search.getSearchManager(cache);
    final SearchIntegrator searchFactory = searchManager.getSearchFactory();
    final QueryCache queryCache = ComponentRegistryUtils.getQueryCache(cache); // optional component

    LuceneQueryParsingResult parsingResult;
    Query luceneQuery;

    if (queryCache != null) {
      KeyValuePair<String, Class> queryCacheKey =
          new KeyValuePair<String, Class>(request.getJpqlString(), LuceneQueryParsingResult.class);
      parsingResult = queryCache.get(queryCacheKey);
      if (parsingResult == null) {
        parsingResult = parseQuery(cache, serCtx, request.getJpqlString(), searchFactory);
        queryCache.put(queryCacheKey, parsingResult);
      }
    } else {
      parsingResult = parseQuery(cache, serCtx, request.getJpqlString(), searchFactory);
    }

    luceneQuery = parsingResult.getQuery();

    if (!cache.getCacheConfiguration().compatibility().enabled()) {
      // restrict on entity type
      QueryBuilder qb =
          searchFactory.buildQueryBuilder().forEntity(parsingResult.getTargetEntity()).get();
      luceneQuery =
          qb.bool()
              .must(
                  qb.keyword()
                      .onField(TYPE_FIELD_NAME)
                      .ignoreFieldBridge()
                      .ignoreAnalyzer()
                      .matching(parsingResult.getTargetEntityName())
                      .createQuery())
              .must(luceneQuery)
              .createQuery();
    }

    CacheQuery cacheQuery = searchManager.getQuery(luceneQuery, parsingResult.getTargetEntity());

    if (parsingResult.getSort() != null) {
      cacheQuery = cacheQuery.sort(parsingResult.getSort());
    }

    int projSize = 0;
    if (parsingResult.getProjections() != null && !parsingResult.getProjections().isEmpty()) {
      projSize = parsingResult.getProjections().size();
      cacheQuery =
          cacheQuery.projection(parsingResult.getProjections().toArray(new String[projSize]));
    }
    if (request.getStartOffset() > 0) {
      cacheQuery = cacheQuery.firstResult((int) request.getStartOffset());
    }
    if (request.getMaxResults() > 0) {
      cacheQuery = cacheQuery.maxResults(request.getMaxResults());
    }

    List<?> list = cacheQuery.list();
    List<WrappedMessage> results =
        new ArrayList<WrappedMessage>(projSize == 0 ? list.size() : list.size() * projSize);
    for (Object o : list) {
      if (projSize == 0) {
        results.add(new WrappedMessage(o));
      } else {
        Object[] row = (Object[]) o;
        for (int j = 0; j < projSize; j++) {
          results.add(new WrappedMessage(row[j]));
        }
      }
    }

    QueryResponse response = new QueryResponse();
    response.setTotalResults(cacheQuery.getResultSize());
    response.setNumResults(list.size());
    response.setProjectionSize(projSize);
    response.setResults(results);

    return response;
  }
  public static void Bench() {
    Session session = null;
    FullTextSession fullTextSession = null;
    SessionFactory sessionFactory = null;
    try {

      sessionFactory =
          new Configuration().configure(hibernateConfigurationFile).buildSessionFactory();

      session = sessionFactory.openSession();
      session.beginTransaction();
      fullTextSession = Search.getFullTextSession(session);

      long gridTotalDuration = 0;
      long spatialTotalDuration = 0;
      long doubleRangeTotalDuration = 0;
      long distanceDoubleRangeTotalDuration = 0;

      long gridDocsFetched = 0;
      long spatialDocsFetched = 0;
      long doubleRangeDocsFetched = 0;
      long distanceDoubleRangeDocsFetched = 0;

      org.apache.lucene.search.Query luceneQuery;
      long startTime, endTime, duration;
      FullTextQuery hibQuery;
      List gridResults, rangeResults;
      final QueryBuilder queryBuilder =
          fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(POI.class).get();
      org.apache.lucene.search.Query query;
      final Integer iterations = 2000;
      final Integer warmUp = 50;
      Random random = new Random(42);

      for (int i = 0; i < iterations; i++) {
        Point center = Point.fromDegrees(random.nextDouble() * 2 + 44, random.nextDouble() * 2 + 3);
        double radius = 25.0d;
        Rectangle boundingBox = Rectangle.fromBoundingCircle(center, radius);

        query =
            queryBuilder
                .bool()
                .must(
                    queryBuilder
                        .range()
                        .onField("latitude")
                        .from(boundingBox.getLowerLeft().getLatitude())
                        .to(boundingBox.getUpperRight().getLatitude())
                        .createQuery())
                .must(
                    queryBuilder
                        .range()
                        .onField("longitude")
                        .from(boundingBox.getLowerLeft().getLongitude())
                        .to(boundingBox.getUpperRight().getLongitude())
                        .createQuery())
                .createQuery();
        hibQuery = fullTextSession.createFullTextQuery(query, POI.class);
        hibQuery.setProjection("id", "name");
        startTime = System.nanoTime();
        try {
          doubleRangeDocsFetched += hibQuery.getResultSize();
        } finally {
          endTime = System.nanoTime();
        }
        duration = endTime - startTime;
        if (i > warmUp) {
          doubleRangeTotalDuration += duration;
        }
        session.clear();

        query =
            queryBuilder
                .bool()
                .must(
                    queryBuilder
                        .range()
                        .onField("latitude")
                        .from(boundingBox.getLowerLeft().getLatitude())
                        .to(boundingBox.getUpperRight().getLatitude())
                        .createQuery())
                .must(
                    queryBuilder
                        .range()
                        .onField("longitude")
                        .from(boundingBox.getLowerLeft().getLongitude())
                        .to(boundingBox.getUpperRight().getLongitude())
                        .createQuery())
                .createQuery();
        org.apache.lucene.search.Query filteredQuery =
            new ConstantScoreQuery(
                SpatialQueryBuilderFromPoint.buildDistanceFilter(
                    new QueryWrapperFilter(query), center, radius, "location"));
        hibQuery = fullTextSession.createFullTextQuery(filteredQuery, POI.class);
        hibQuery.setProjection("id", "name");
        startTime = System.nanoTime();
        try {
          distanceDoubleRangeDocsFetched += hibQuery.getResultSize();
        } finally {
          endTime = System.nanoTime();
        }
        duration = endTime - startTime;
        if (i > warmUp) {
          distanceDoubleRangeTotalDuration += duration;
        }
        rangeResults = hibQuery.list();
        session.clear();

        luceneQuery = SpatialQueryBuilderFromPoint.buildGridQuery(center, radius, "location");
        hibQuery = fullTextSession.createFullTextQuery(luceneQuery, POI.class);
        hibQuery.setProjection("id", "name");
        startTime = System.nanoTime();

        try {
          gridDocsFetched += hibQuery.getResultSize();
        } finally {
          endTime = System.nanoTime();
        }
        duration = endTime - startTime;
        if (i > warmUp) {
          gridTotalDuration += duration;
        }
        session.clear();

        luceneQuery =
            SpatialQueryBuilderFromPoint.buildSpatialQueryByGrid(center, radius, "location");
        hibQuery = fullTextSession.createFullTextQuery(luceneQuery, POI.class);
        hibQuery.setProjection("id", "name");
        startTime = System.nanoTime();

        try {
          spatialDocsFetched += hibQuery.getResultSize();
        } finally {
          endTime = System.nanoTime();
        }
        duration = endTime - startTime;
        if (i > warmUp) {
          spatialTotalDuration += duration;
        }
        gridResults = hibQuery.list();
        session.clear();

        if (rangeResults.size() != gridResults.size()) {
          luceneQuery = SpatialQueryBuilderFromPoint.buildDistanceQuery(center, radius, "location");
          hibQuery = fullTextSession.createFullTextQuery(luceneQuery, POI.class);
          hibQuery.setProjection("id", "name");

          System.out.println(
              ">>>>> Different numbers of documents fetched for point ("
                  + Double.toString(center.getLatitude())
                  + ","
                  + Double.toString(center.getLongitude())
                  + ") and radius "
                  + Double.toString(radius));
          System.out.println("Range results : " + rangeResults);
          System.out.println("Grid results : " + gridResults);
          System.out.println("Pure distance results : " + hibQuery.getResultSize());

          List<Integer> rangeIds = new ArrayList<Integer>();
          for (int index = 0; index < rangeResults.size(); index++) {
            rangeIds.add((Integer) ((Object[]) rangeResults.get(index))[0]);
          }
          List<Integer> gridIds = new ArrayList<Integer>();
          for (int index = 0; index < gridResults.size(); index++) {
            gridIds.add((Integer) ((Object[]) gridResults.get(index))[0]);
          }

          rangeIds.removeAll(gridIds);

          System.out.println("Missing Ids : " + rangeIds);
        }
      }
      session.getTransaction().commit();
      session.close();
      sessionFactory.close();

      System.out.println(
          "Mean time with Grid : "
              + Double.toString(
                  (double) gridTotalDuration * Math.pow(10, -6) / (iterations - warmUp))
              + " ms. Average number of docs  fetched : "
              + Double.toString(gridDocsFetched / ((iterations - warmUp) * 1.0d)));
      System.out.println(
          "Mean time with Grid + Distance filter : "
              + Double.toString(
                  (double) spatialTotalDuration * Math.pow(10, -6) / (iterations - warmUp))
              + " ms. Average number of docs  fetched : "
              + Double.toString(spatialDocsFetched / ((iterations - warmUp) * 1.0d)));
      System.out.println(
          "Mean time with DoubleRange : "
              + Double.toString(
                  (double) doubleRangeTotalDuration * Math.pow(10, -6) / (iterations - warmUp))
              + " ms. Average number of docs  fetched : "
              + Double.toString(doubleRangeDocsFetched / ((iterations - warmUp) * 1.0d)));
      System.out.println(
          "Mean time with DoubleRange + Distance filter : "
              + Double.toString(
                  (double) distanceDoubleRangeTotalDuration
                      * Math.pow(10, -6)
                      / (iterations - warmUp))
              + " ms. Average number of docs  fetched : "
              + Double.toString(distanceDoubleRangeDocsFetched / ((iterations - warmUp) * 1.0d)));

    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      if (fullTextSession != null && fullTextSession.isOpen()) {
        Transaction transaction = fullTextSession.getTransaction();
        if (transaction != null && transaction.isActive()) {
          transaction.rollback();
        }
        fullTextSession.close();
      }
      if (sessionFactory != null && !sessionFactory.isClosed()) {
        sessionFactory.close();
      }
    }
  }
  private FullTextQuery buildFullTextQuery(
      ArticleSearchRequest request, Pageable pageable, Criteria criteria) {
    FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
    QueryBuilder qb =
        fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(Article.class).get();

    @SuppressWarnings("rawtypes")
    BooleanJunction<BooleanJunction> junction = qb.bool();
    junction.must(qb.all().createQuery());

    junction.must(
        qb.keyword().onField("drafted").ignoreAnalyzer().matching("_null_").createQuery());

    if (StringUtils.hasText(request.getKeyword())) {
      Analyzer analyzer = fullTextEntityManager.getSearchFactory().getAnalyzer("synonyms");
      String[] fields =
          new String[] {
            "title", "body",
            "categories.name", "tags.name",
          };
      MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, analyzer);
      parser.setDefaultOperator(QueryParser.Operator.AND);
      Query query = null;
      try {
        query = parser.parse(request.getKeyword());
      } catch (ParseException e1) {
        try {
          query = parser.parse(QueryParser.escape(request.getKeyword()));
        } catch (ParseException e2) {
          throw new RuntimeException(e2);
        }
      }
      junction.must(query);
    }
    if (request.getStatus() != null) {
      junction.must(qb.keyword().onField("status").matching(request.getStatus()).createQuery());
    }
    if (StringUtils.hasText(request.getLanguage())) {
      junction.must(qb.keyword().onField("language").matching(request.getLanguage()).createQuery());
    }

    if (request.getDateFrom() != null) {
      junction.must(qb.range().onField("date").above(request.getDateFrom()).createQuery());
    }
    if (request.getDateTo() != null) {
      junction.must(qb.range().onField("date").below(request.getDateTo()).createQuery());
    }

    if (!CollectionUtils.isEmpty(request.getCategoryIds())) {
      BooleanJunction<BooleanJunction> subJunction = qb.bool();
      for (long categoryId : request.getCategoryIds()) {
        subJunction.should(
            qb.keyword().onField("categories.id").matching(categoryId).createQuery());
      }
      junction.must(subJunction.createQuery());
    }
    if (!CollectionUtils.isEmpty(request.getCategoryCodes())) {
      BooleanJunction<BooleanJunction> subJunction = qb.bool();
      for (String categoryCode : request.getCategoryCodes()) {
        subJunction.should(
            qb.keyword().onField("categories.code").matching(categoryCode).createQuery());
      }
      junction.must(subJunction.createQuery());
    }

    if (!CollectionUtils.isEmpty(request.getTagIds())) {
      BooleanJunction<BooleanJunction> subJunction = qb.bool();
      for (long tagId : request.getTagIds()) {
        subJunction.should(qb.keyword().onField("tags.id").matching(tagId).createQuery());
      }
      junction.must(subJunction.createQuery());
    }
    if (!CollectionUtils.isEmpty(request.getTagNames())) {
      BooleanJunction<BooleanJunction> subJunction = qb.bool();
      for (String tagName : request.getTagNames()) {
        subJunction.should(qb.phrase().onField("tags.name").sentence(tagName).createQuery());
      }
      junction.must(subJunction.createQuery());
    }

    if (request.getAuthorId() != null) {
      junction.must(
          qb.keyword().onField("author.id").matching(request.getAuthorId()).createQuery());
    }

    Query searchQuery = junction.createQuery();

    Sort sort =
        new Sort(
            new SortField("date", SortField.Type.STRING, true),
            new SortField("id", SortField.Type.LONG, true));

    FullTextQuery persistenceQuery =
        fullTextEntityManager
            .createFullTextQuery(searchQuery, Article.class)
            .setCriteriaQuery(criteria)
            .setSort(sort);
    if (pageable != null) {
      persistenceQuery.setFirstResult(pageable.getOffset());
      persistenceQuery.setMaxResults(pageable.getPageSize());
    }
    return persistenceQuery;
  }