/** 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 void testIndexingWithWrapper() throws Exception {
    MarshallerRegistration.registerMarshallers(
        ProtobufMetadataManagerImpl.getSerializationContext(cacheManager));

    // Store some test data:
    ProtobufValueWrapper wrapper1 =
        new ProtobufValueWrapper(createMarshalledUser("Adrian", "Nistor"));
    ProtobufValueWrapper wrapper2 =
        new ProtobufValueWrapper(createMarshalledUser("John", "Batman"));

    cache.put(1, wrapper1); // todo how do we index if the key is a byte array?
    cache.put(2, wrapper2);

    SearchManager sm = Search.getSearchManager(cache);

    SearchIntegrator searchFactory = sm.unwrap(SearchIntegrator.class);
    assertNotNull(searchFactory.getIndexManager(ProtobufValueWrapper.class.getName()));

    Query luceneQuery =
        sm.buildQueryBuilderForClass(ProtobufValueWrapper.class)
            .get()
            .keyword()
            .onField("name")
            .ignoreFieldBridge()
            .ignoreAnalyzer()
            .matching("Adrian")
            .createQuery();

    List<ProtobufValueWrapper> list = sm.<ProtobufValueWrapper>getQuery(luceneQuery).list();
    assertEquals(1, list.size());
    ProtobufValueWrapper pvw = list.get(0);
    User unwrapped =
        (User)
            ProtobufUtil.fromWrappedByteArray(
                ProtobufMetadataManagerImpl.getSerializationContextInternal(cacheManager),
                pvw.getBinary());
    assertEquals("Adrian", unwrapped.getName());

    // an alternative approach ...

    Query luceneQuery2 =
        searchFactory
            .buildQueryBuilder()
            .forEntity(ProtobufValueWrapper.class)
            .get()
            .keyword()
            .onField("name")
            .ignoreFieldBridge()
            .ignoreAnalyzer()
            .matching("Adrian")
            .createQuery();

    List<EntityInfo> queryEntityInfos =
        searchFactory
            .createHSQuery(luceneQuery2, ProtobufValueWrapper.class)
            .projection("surname")
            .queryEntityInfos();

    assertEquals(1, queryEntityInfos.size());
    EntityInfo entityInfo = queryEntityInfos.get(0);
    assertEquals("Nistor", entityInfo.getProjection()[0]);
  }