001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.search;
016    
017    import com.liferay.portal.kernel.dao.orm.QueryUtil;
018    import com.liferay.portal.kernel.dao.search.SearchPaginationUtil;
019    import com.liferay.portal.kernel.util.ArrayUtil;
020    import com.liferay.portal.kernel.util.GetterUtil;
021    import com.liferay.portal.kernel.util.PropsKeys;
022    import com.liferay.portal.kernel.util.PropsUtil;
023    import com.liferay.portal.kernel.util.SetUtil;
024    import com.liferay.portal.kernel.util.Time;
025    
026    import java.util.ArrayList;
027    import java.util.Collections;
028    import java.util.List;
029    import java.util.Set;
030    
031    /**
032     * @author Tina Tian
033     */
034    public abstract class BaseSearchResultPermissionFilter
035            implements SearchResultPermissionFilter {
036    
037            @Override
038            public Hits search(SearchContext searchContext) throws SearchException {
039                    QueryConfig queryConfig = searchContext.getQueryConfig();
040    
041                    if (!queryConfig.isAllFieldsSelected()) {
042                            Set<String> selectedFieldNameSet = SetUtil.fromArray(
043                                    queryConfig.getSelectedFieldNames());
044    
045                            Collections.addAll(
046                                    selectedFieldNameSet, _PERMISSION_SELECTED_FIELD_NAMES);
047    
048                            queryConfig.setSelectedFieldNames(
049                                    selectedFieldNameSet.toArray(
050                                            new String[selectedFieldNameSet.size()]));
051                    }
052    
053                    int end = searchContext.getEnd();
054                    int start = searchContext.getStart();
055    
056                    if ((end == QueryUtil.ALL_POS) && (start == QueryUtil.ALL_POS)) {
057                            Hits hits = getHits(searchContext);
058    
059                            if (!isGroupAdmin(searchContext)) {
060                                    filterHits(hits, searchContext);
061                            }
062    
063                            return hits;
064                    }
065    
066                    if ((start < 0) || (start > end)) {
067                            return new HitsImpl();
068                    }
069    
070                    if (isGroupAdmin(searchContext)) {
071                            return getHits(searchContext);
072                    }
073    
074                    double amplificationFactor = 1.0;
075                    int excludedDocsSize = 0;
076                    int hitsSize = 0;
077                    int offset = 0;
078                    long startTime = 0;
079    
080                    List<Document> documents = new ArrayList<>();
081                    List<Float> scores = new ArrayList<>();
082    
083                    while (true) {
084                            int count = end - documents.size();
085    
086                            int amplifiedCount = (int)Math.ceil(count * amplificationFactor);
087    
088                            int amplifiedEnd = offset + amplifiedCount;
089    
090                            searchContext.setEnd(amplifiedEnd);
091                            searchContext.setStart(offset);
092    
093                            Hits hits = getHits(searchContext);
094    
095                            if (startTime == 0) {
096                                    hitsSize = hits.getLength();
097                                    startTime = hits.getStart();
098                            }
099    
100                            Document[] oldDocs = hits.getDocs();
101    
102                            filterHits(hits, searchContext);
103    
104                            Document[] newDocs = hits.getDocs();
105    
106                            excludedDocsSize += oldDocs.length - newDocs.length;
107    
108                            collectHits(hits, documents, scores, count);
109    
110                            if ((newDocs.length >= count) ||
111                                    (oldDocs.length < amplifiedCount) ||
112                                    (amplifiedEnd >= hitsSize)) {
113    
114                                    updateHits(
115                                            hits, documents, scores, start, end,
116                                            hitsSize - excludedDocsSize, startTime);
117    
118                                    return hits;
119                            }
120    
121                            offset = amplifiedEnd;
122    
123                            amplificationFactor = _getAmplificationFactor(
124                                    documents.size(), offset);
125                    }
126            }
127    
128            protected void collectHits(
129                    Hits hits, List<Document> documents, List<Float> scores, int count) {
130    
131                    Document[] docs = hits.getDocs();
132    
133                    if (docs.length < count) {
134                            count = docs.length;
135                    }
136    
137                    for (int i = 0; i < count; i++) {
138                            documents.add(docs[i]);
139    
140                            scores.add(hits.score(i));
141                    }
142            }
143    
144            protected abstract void filterHits(Hits hits, SearchContext searchContext);
145    
146            protected abstract Hits getHits(SearchContext searchContext)
147                    throws SearchException;
148    
149            protected abstract boolean isGroupAdmin(SearchContext searchContext);
150    
151            protected void updateHits(
152                    Hits hits, List<Document> documents, List<Float> scores, int start,
153                    int end, int size, long startTime) {
154    
155                    int[] startAndEnd = SearchPaginationUtil.calculateStartAndEnd(
156                            start, end, documents.size());
157    
158                    start = startAndEnd[0];
159                    end = startAndEnd[1];
160    
161                    documents = documents.subList(start, end);
162                    scores = scores.subList(start, end);
163    
164                    hits.setDocs(documents.toArray(new Document[documents.size()]));
165                    hits.setScores(ArrayUtil.toFloatArray(scores));
166                    hits.setLength(size);
167                    hits.setSearchTime(
168                            (float)(System.currentTimeMillis() - startTime) / Time.SECOND);
169            }
170    
171            private double _getAmplificationFactor(double totalViewable, double total) {
172                    if (totalViewable == 0) {
173                            return _INDEX_PERMISSION_FILTER_SEARCH_AMPLIFICATION_FACTOR;
174                    }
175    
176                    return Math.min(
177                            1.0 / (totalViewable / total),
178                            _INDEX_PERMISSION_FILTER_SEARCH_AMPLIFICATION_FACTOR);
179            }
180    
181            private static final double
182                    _INDEX_PERMISSION_FILTER_SEARCH_AMPLIFICATION_FACTOR =
183                            GetterUtil.getDouble(
184                                    PropsUtil.get(
185                                            PropsKeys.
186                                                    INDEX_PERMISSION_FILTER_SEARCH_AMPLIFICATION_FACTOR));
187    
188            private static final String[] _PERMISSION_SELECTED_FIELD_NAMES =
189                    {Field.ENTRY_CLASS_NAME, Field.ENTRY_CLASS_PK};
190    
191    }