001/*
002 * (C) Copyright 2007-2011 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 *     Julien Anguenot
018 *     Florent Guillaume
019 */
020package org.nuxeo.ecm.core.search.api.client.search.results.impl;
021
022import java.util.ArrayList;
023import java.util.List;
024
025import org.nuxeo.ecm.core.api.CoreSession;
026import org.nuxeo.ecm.core.api.DocumentModel;
027import org.nuxeo.ecm.core.api.DocumentModelList;
028import org.nuxeo.ecm.core.api.DocumentSecurityException;
029import org.nuxeo.ecm.core.query.QueryParseException;
030import org.nuxeo.ecm.core.query.sql.model.SQLQuery;
031import org.nuxeo.ecm.core.search.api.client.SearchException;
032import org.nuxeo.ecm.core.search.api.client.search.results.ResultItem;
033import org.nuxeo.ecm.core.search.api.client.search.results.ResultSet;
034import org.nuxeo.runtime.api.Framework;
035
036/**
037 * Result set implementation.
038 */
039public class ResultSetImpl extends ArrayList<ResultItem> implements ResultSet {
040
041    private static final long serialVersionUID = -6376330426798015144L;
042
043    protected final int offset;
044
045    /** 0 means all results */
046    protected final int range;
047
048    protected final int totalHits;
049
050    protected final int pageHits;
051
052    protected final String query;
053
054    protected final SQLQuery sqlQuery;
055
056    protected final CoreSession session;
057
058    protected Boolean detachResultsFlag;
059
060    /**
061     * Constructor used when a CoreSession is available.
062     */
063    public ResultSetImpl(String query, CoreSession session, int offset, int range, List<ResultItem> resultItems,
064            int totalHits, int pageHits) {
065        this.query = query;
066        sqlQuery = null;
067        this.session = session;
068        this.offset = offset;
069        this.range = range;
070        this.totalHits = totalHits;
071        this.pageHits = pageHits;
072        if (resultItems != null) {
073            addAll(resultItems);
074        }
075    }
076
077    public boolean detachResults() {
078        if (detachResultsFlag == null) {
079            detachResultsFlag = Framework.isBooleanPropertyTrue(ALWAYS_DETACH_SEARCH_RESULTS_KEY);
080        }
081        return detachResultsFlag.booleanValue();
082    }
083
084    @Override
085    public int getOffset() {
086        return offset;
087    }
088
089    @Override
090    public int getRange() {
091        return range;
092    }
093
094    @Override
095    public ResultSet nextPage() throws SearchException {
096        if (!hasNextPage()) {
097            return null;
098        }
099        return replay(offset + range, range);
100    }
101
102    @Override
103    public ResultSet goToPage(int page) throws SearchException {
104        int newOffset = range * (page - 1);
105        if (newOffset >= 0 && newOffset < totalHits) {
106            return replay(newOffset, range);
107        }
108        return null;
109    }
110
111    @Override
112    public int getTotalHits() {
113        return totalHits;
114    }
115
116    @Override
117    public int getPageHits() {
118        return pageHits;
119    }
120
121    @Override
122    public boolean hasNextPage() {
123        if (range == 0) {
124            return false;
125        }
126        if (pageHits < range) {
127            return false;
128        }
129        return (offset + range) < totalHits;
130    }
131
132    @Override
133    public boolean isFirstPage() {
134        return range == 0 ? true : offset < range;
135    }
136
137    @Override
138    public ResultSet replay() throws SearchException {
139        return replay(offset, range);
140    }
141
142    @Override
143    public ResultSet replay(int offset, int range) throws SearchException {
144        if (session != null) {
145            try {
146                DocumentModelList list = session.query(query, null, range, offset, true);
147                List<ResultItem> resultItems = new ArrayList<>(list.size());
148                for (DocumentModel doc : list) {
149                    if (doc == null) {
150                        continue;
151                    }
152                    if (detachResults()) {
153                        // detach the document so that we can use it beyond the
154                        // session
155                        try {
156                            doc.detach(true);
157                        } catch (DocumentSecurityException e) {
158                            // no access to the document (why?)
159                            continue;
160                        }
161                    }
162                    resultItems.add(new DocumentModelResultItem(doc));
163                }
164                return new ResultSetImpl(query, session, offset, range, resultItems, (int) list.totalSize(),
165                        list.size());
166            } catch (QueryParseException e) {
167                throw new SearchException("QueryException for: " + query, e);
168            }
169        }
170        throw new SearchException("No session");
171    }
172
173    @Override
174    public int getPageNumber() {
175        if (range == 0) {
176            return 1;
177        }
178        return (offset + range) / range;
179    }
180
181}