001/*
002 * (C) Copyright 2014-2015 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Benoit Delbosc
018 *     Florent Guillaume
019 */
020package org.nuxeo.elasticsearch.core;
021
022import java.io.Serializable;
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028import java.util.NoSuchElementException;
029
030import org.elasticsearch.action.search.SearchResponse;
031import org.elasticsearch.search.SearchHit;
032import org.elasticsearch.search.SearchHitField;
033import org.nuxeo.ecm.core.api.IterableQueryResult;
034import org.nuxeo.ecm.core.query.sql.NXQL;
035import org.nuxeo.ecm.core.schema.types.Type;
036import org.nuxeo.ecm.core.schema.types.primitives.DateType;
037
038/**
039 * Iterable query result of the results of an Elasticsearch query.
040 * <p>
041 * Loads all results in memory.
042 *
043 * @since 7.2
044 */
045public class EsResultSetImpl implements IterableQueryResult, Iterator<Map<String, Serializable>> {
046
047    private final SearchResponse response;
048
049    private final Map<String, Type> selectFieldsAndTypes;
050
051    boolean closed;
052
053    protected List<Map<String, Serializable>> maps;
054
055    protected long size;
056
057    private long pos;
058
059    public EsResultSetImpl(SearchResponse response, Map<String, Type> selectFieldsAndTypes) {
060        this.response = response;
061        this.selectFieldsAndTypes = selectFieldsAndTypes;
062        maps = buildMaps();
063        size = maps.size();
064    }
065
066    protected List<Map<String, Serializable>> buildMaps() {
067        List<Map<String, Serializable>> rows = new ArrayList<>(response.getHits().getHits().length);
068        Map<String, Serializable> emptyRow = new HashMap<>(selectFieldsAndTypes.size());
069        for (String fieldName : selectFieldsAndTypes.keySet()) {
070            emptyRow.put(fieldName, null);
071        }
072        for (SearchHit hit : response.getHits().getHits()) {
073            Map<String, Serializable> row = new HashMap<>(emptyRow);
074            for (SearchHitField field : hit.getFields().values()) {
075                String name = field.getName();
076                Serializable value = field.<Serializable> getValue();
077                // type conversion
078                Type type;
079                if (value instanceof String && (type = selectFieldsAndTypes.get(name)) instanceof DateType) {
080                    // convert back to calendar
081                    value = (Serializable) type.decode(((String) value));
082                }
083                row.put(name, value);
084            }
085            if (selectFieldsAndTypes.containsKey(NXQL.ECM_FULLTEXT_SCORE)) {
086                row.put(NXQL.ECM_FULLTEXT_SCORE, Double.valueOf(hit.getScore()));
087            }
088            rows.add(row);
089        }
090        return rows;
091    }
092
093    @Override
094    public void close() {
095        closed = true;
096        pos = -1;
097    }
098
099    @Override
100    public boolean isLife() {
101        return !closed;
102    }
103
104    @Override
105    public boolean mustBeClosed() {
106        return false; // holds no resources
107    }
108
109    @Override
110    public long size() {
111        return response.getHits().getTotalHits();
112    }
113
114    @Override
115    public long pos() {
116        return pos;
117    }
118
119    @Override
120    public void skipTo(long pos) {
121        if (pos < 0) {
122            pos = 0;
123        } else if (pos > size) {
124            pos = size;
125        }
126        this.pos = pos;
127    }
128
129    @Override
130    public Iterator<Map<String, Serializable>> iterator() {
131        return this;
132    }
133
134    @Override
135    public boolean hasNext() {
136        return pos < size;
137    }
138
139    @Override
140    public Map<String, Serializable> next() {
141        if (closed || pos == size) {
142            throw new NoSuchElementException();
143        }
144        Map<String, Serializable> map = maps.get((int) pos);
145        pos++;
146        return map;
147    }
148
149}