001    /**
002     * Copyright (c) 2000-2013 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.Time;
024    
025    import java.util.ArrayList;
026    import java.util.List;
027    
028    /**
029     * @author Tina Tian
030     */
031    public abstract class BaseSearchResultPermissionFilter
032            implements SearchResultPermissionFilter {
033    
034            @Override
035            public Hits search(SearchContext searchContext) throws SearchException {
036                    int end = searchContext.getEnd();
037                    int start = searchContext.getStart();
038    
039                    if ((end == QueryUtil.ALL_POS) && (start == QueryUtil.ALL_POS)) {
040                            Hits hits = getHits(searchContext);
041    
042                            if (!isGroupAdmin(searchContext)) {
043                                    filterHits(hits, searchContext);
044                            }
045    
046                            return hits;
047                    }
048    
049                    if ((start < 0) || (start > end)) {
050                            return new HitsImpl();
051                    }
052    
053                    if (isGroupAdmin(searchContext)) {
054                            return getHits(searchContext);
055                    }
056    
057                    double amplificationFactor = 1.0;
058                    int excludedDocsSize = 0;
059                    int hitsSize = 0;
060                    int offset = 0;
061                    long startTime = 0;
062    
063                    List<Document> documents = new ArrayList<Document>();
064                    List<Float> scores = new ArrayList<Float>();
065    
066                    while (true) {
067                            int count = end - documents.size();
068    
069                            int amplifiedCount = (int)Math.ceil(count * amplificationFactor);
070    
071                            int amplifiedEnd = offset + amplifiedCount;
072    
073                            searchContext.setEnd(amplifiedEnd);
074                            searchContext.setStart(offset);
075    
076                            Hits hits = getHits(searchContext);
077    
078                            if (startTime == 0) {
079                                    hitsSize = hits.getLength();
080                                    startTime = hits.getStart();
081                            }
082    
083                            Document[] oldDocs = hits.getDocs();
084    
085                            filterHits(hits, searchContext);
086    
087                            Document[] newDocs = hits.getDocs();
088    
089                            excludedDocsSize += oldDocs.length - newDocs.length;
090    
091                            collectHits(hits, documents, scores, count);
092    
093                            if ((newDocs.length >= count) ||
094                                    (oldDocs.length < amplifiedCount) ||
095                                    (amplifiedEnd >= hitsSize)) {
096    
097                                    updateHits(
098                                            hits, documents, scores, start, end,
099                                            hitsSize - excludedDocsSize, startTime);
100    
101                                    return hits;
102                            }
103    
104                            offset = amplifiedEnd;
105    
106                            amplificationFactor = _getAmplificationFactor(
107                                    documents.size(), offset);
108                    }
109            }
110    
111            protected void collectHits(
112                    Hits hits, List<Document> documents, List<Float> scores, int count) {
113    
114                    Document[] docs = hits.getDocs();
115    
116                    if (docs.length < count) {
117                            count = docs.length;
118                    }
119    
120                    for (int i = 0; i < count; i++) {
121                            documents.add(docs[i]);
122    
123                            scores.add(hits.score(i));
124                    }
125            }
126    
127            protected abstract void filterHits(Hits hits, SearchContext searchContext);
128    
129            protected abstract Hits getHits(SearchContext searchContext)
130                    throws SearchException;
131    
132            protected abstract boolean isGroupAdmin(SearchContext searchContext);
133    
134            protected void updateHits(
135                    Hits hits, List<Document> documents, List<Float> scores, int start,
136                    int end, int size, long startTime) {
137    
138                    int[] startAndEnd = SearchPaginationUtil.calculateStartAndEnd(
139                            start, end, documents.size());
140    
141                    start = startAndEnd[0];
142                    end = startAndEnd[1];
143    
144                    documents = documents.subList(start, end);
145                    scores = scores.subList(start, end);
146    
147                    hits.setDocs(documents.toArray(new Document[documents.size()]));
148                    hits.setScores(ArrayUtil.toFloatArray(scores));
149                    hits.setLength(size);
150                    hits.setSearchTime(
151                            (float)(System.currentTimeMillis() - startTime) / Time.SECOND);
152            }
153    
154            private double _getAmplificationFactor(double totalViewable, double total) {
155                    if (totalViewable == 0) {
156                            return _INDEX_PERMISSION_FILTER_SEARCH_AMPLIFICATION_FACTOR;
157                    }
158    
159                    return Math.min(
160                            1.0 / (totalViewable / total),
161                            _INDEX_PERMISSION_FILTER_SEARCH_AMPLIFICATION_FACTOR);
162            }
163    
164            private static final double
165                    _INDEX_PERMISSION_FILTER_SEARCH_AMPLIFICATION_FACTOR =
166                            GetterUtil.getDouble(
167                                    PropsUtil.get(
168                                            PropsKeys.
169                                                    INDEX_PERMISSION_FILTER_SEARCH_AMPLIFICATION_FACTOR));
170    
171    }