001/*
002 * (C) Copyright 2007-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Julien Anguenot
016 *     Florent Guillaume
017 */
018package org.nuxeo.ecm.core.search.api.client.search.results.impl;
019
020import java.util.ArrayList;
021import java.util.List;
022
023import org.nuxeo.ecm.core.api.CoreSession;
024import org.nuxeo.ecm.core.api.DocumentModel;
025import org.nuxeo.ecm.core.api.DocumentModelList;
026import org.nuxeo.ecm.core.api.DocumentSecurityException;
027import org.nuxeo.ecm.core.query.QueryParseException;
028import org.nuxeo.ecm.core.query.sql.model.SQLQuery;
029import org.nuxeo.ecm.core.search.api.client.SearchException;
030import org.nuxeo.ecm.core.search.api.client.search.results.ResultItem;
031import org.nuxeo.ecm.core.search.api.client.search.results.ResultSet;
032import org.nuxeo.runtime.api.Framework;
033
034/**
035 * Result set implementation.
036 */
037public class ResultSetImpl extends ArrayList<ResultItem> implements ResultSet {
038
039    private static final long serialVersionUID = -6376330426798015144L;
040
041    protected final int offset;
042
043    /** 0 means all results */
044    protected final int range;
045
046    protected final int totalHits;
047
048    protected final int pageHits;
049
050    protected final String query;
051
052    protected final SQLQuery sqlQuery;
053
054    protected final CoreSession session;
055
056    protected Boolean detachResultsFlag;
057
058    /**
059     * Constructor used when a CoreSession is available.
060     */
061    public ResultSetImpl(String query, CoreSession session, int offset, int range, List<ResultItem> resultItems,
062            int totalHits, int pageHits) {
063        this.query = query;
064        sqlQuery = null;
065        this.session = session;
066        this.offset = offset;
067        this.range = range;
068        this.totalHits = totalHits;
069        this.pageHits = pageHits;
070        if (resultItems != null) {
071            addAll(resultItems);
072        }
073    }
074
075    public boolean detachResults() {
076        if (detachResultsFlag == null) {
077            detachResultsFlag = Framework.isBooleanPropertyTrue(ALWAYS_DETACH_SEARCH_RESULTS_KEY);
078        }
079        return detachResultsFlag.booleanValue();
080    }
081
082    @Override
083    public int getOffset() {
084        return offset;
085    }
086
087    @Override
088    public int getRange() {
089        return range;
090    }
091
092    @Override
093    public ResultSet nextPage() throws SearchException {
094        if (!hasNextPage()) {
095            return null;
096        }
097        return replay(offset + range, range);
098    }
099
100    @Override
101    public ResultSet goToPage(int page) throws SearchException {
102        int newOffset = range * (page - 1);
103        if (newOffset >= 0 && newOffset < totalHits) {
104            return replay(newOffset, range);
105        }
106        return null;
107    }
108
109    @Override
110    public int getTotalHits() {
111        return totalHits;
112    }
113
114    @Override
115    public int getPageHits() {
116        return pageHits;
117    }
118
119    @Override
120    public boolean hasNextPage() {
121        if (range == 0) {
122            return false;
123        }
124        if (pageHits < range) {
125            return false;
126        }
127        return (offset + range) < totalHits;
128    }
129
130    @Override
131    public boolean isFirstPage() {
132        return range == 0 ? true : offset < range;
133    }
134
135    @Override
136    public ResultSet replay() throws SearchException {
137        return replay(offset, range);
138    }
139
140    @Override
141    public ResultSet replay(int offset, int range) throws SearchException {
142        if (session != null) {
143            try {
144                DocumentModelList list = session.query(query, null, range, offset, true);
145                List<ResultItem> resultItems = new ArrayList<ResultItem>(list.size());
146                for (DocumentModel doc : list) {
147                    if (doc == null) {
148                        continue;
149                    }
150                    if (detachResults()) {
151                        // detach the document so that we can use it beyond the
152                        // session
153                        try {
154                            doc.detach(true);
155                        } catch (DocumentSecurityException e) {
156                            // no access to the document (why?)
157                            continue;
158                        }
159                    }
160                    resultItems.add(new DocumentModelResultItem(doc));
161                }
162                return new ResultSetImpl(query, session, offset, range, resultItems, (int) list.totalSize(),
163                        list.size());
164            } catch (QueryParseException e) {
165                throw new SearchException("QueryException for: " + query, e);
166            }
167        }
168        throw new SearchException("No session");
169    }
170
171    @Override
172    public int getPageNumber() {
173        if (range == 0) {
174            return 1;
175        }
176        return (offset + range) / range;
177    }
178
179}