001    /**
002     * Copyright (c) 2000-2010 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.NoSuchModelException;
018    import com.liferay.portal.kernel.dao.orm.QueryUtil;
019    import com.liferay.portal.kernel.log.Log;
020    import com.liferay.portal.kernel.log.LogFactoryUtil;
021    import com.liferay.portal.kernel.util.GetterUtil;
022    import com.liferay.portal.kernel.util.PropsKeys;
023    import com.liferay.portal.kernel.util.PropsUtil;
024    import com.liferay.portal.kernel.util.Time;
025    import com.liferay.portal.kernel.util.Validator;
026    import com.liferay.portal.model.Group;
027    import com.liferay.portal.security.permission.ActionKeys;
028    import com.liferay.portal.security.permission.PermissionChecker;
029    import com.liferay.portal.security.permission.PermissionThreadLocal;
030    import com.liferay.portal.service.GroupLocalServiceUtil;
031    import com.liferay.portlet.asset.service.AssetCategoryServiceUtil;
032    
033    import java.util.ArrayList;
034    import java.util.List;
035    
036    /**
037     * @author Brian Wing Shun Chan
038     */
039    public abstract class BaseIndexer implements Indexer {
040    
041            public void delete(Object obj) throws SearchException {
042                    try {
043                            doDelete(obj);
044                    }
045                    catch (SearchException se) {
046                            throw se;
047                    }
048                    catch (Exception e) {
049                            throw new SearchException(e);
050                    }
051            }
052    
053            public Document getDocument(Object obj) throws SearchException {
054                    try {
055                            return doGetDocument(obj);
056                    }
057                    catch (SearchException se) {
058                            throw se;
059                    }
060                    catch (Exception e) {
061                            throw new SearchException(e);
062                    }
063            }
064    
065            public void reindex(Object obj) throws SearchException {
066                    try {
067                            if (SearchEngineUtil.isIndexReadOnly()) {
068                                    return;
069                            }
070    
071                            doReindex(obj);
072                    }
073                    catch (SearchException se) {
074                            throw se;
075                    }
076                    catch (Exception e) {
077                            throw new SearchException(e);
078                    }
079            }
080    
081            public void reindex(String className, long classPK) throws SearchException {
082                    try {
083                            if (SearchEngineUtil.isIndexReadOnly()) {
084                                    return;
085                            }
086    
087                            doReindex(className, classPK);
088                    }
089                    catch (NoSuchModelException nsme) {
090                            if (_log.isWarnEnabled()) {
091                                    _log.warn("Unable to index " + className + " " + classPK);
092                            }
093                    }
094                    catch (SearchException se) {
095                            throw se;
096                    }
097                    catch (Exception e) {
098                            throw new SearchException(e);
099                    }
100            }
101    
102            public void reindex(String[] ids) throws SearchException {
103                    try {
104                            if (SearchEngineUtil.isIndexReadOnly()) {
105                                    return;
106                            }
107    
108                            doReindex(ids);
109                    }
110                    catch (SearchException se) {
111                            throw se;
112                    }
113                    catch (Exception e) {
114                            throw new SearchException(e);
115                    }
116            }
117    
118            public Hits search(SearchContext searchContext) throws SearchException {
119                    try {
120                            String className = getClassName(searchContext);
121    
122                            BooleanQuery contextQuery = BooleanQueryFactoryUtil.create();
123    
124                            addSearchAssetCategoryIds(contextQuery, searchContext);
125                            addSearchAssetTagNames(contextQuery, searchContext);
126                            addSearchGroupId(contextQuery, searchContext);
127                            addSearchOwnerUserId(contextQuery, searchContext);
128                            addSearchCategoryIds(contextQuery, searchContext);
129                            addSearchNodeIds(contextQuery, searchContext);
130                            addSearchFolderIds(contextQuery, searchContext);
131                            addSearchPortletIds(contextQuery, searchContext);
132    
133                            BooleanQuery fullQuery = createFullQuery(
134                                    contextQuery, searchContext);
135    
136                            PermissionChecker permissionChecker =
137                                    PermissionThreadLocal.getPermissionChecker();
138    
139                            int start = searchContext.getStart();
140                            int end = searchContext.getEnd();
141    
142                            if (isFilterSearch() && (permissionChecker != null)) {
143                                    start = 0;
144                                    end = end + INDEX_FILTER_SEARCH_LIMIT;
145                            }
146    
147                            Hits hits = SearchEngineUtil.search(
148                                    searchContext.getCompanyId(), searchContext.getGroupIds(),
149                                    searchContext.getUserId(), className, fullQuery,
150                                    searchContext.getSorts(), start, end);
151    
152                            if (isFilterSearch() && (permissionChecker != null)) {
153                                    hits = filterSearch(hits, permissionChecker, searchContext);
154                            }
155    
156                            return hits;
157                    }
158                    catch (SearchException se) {
159                            throw se;
160                    }
161                    catch (Exception e) {
162                            throw new SearchException(e);
163                    }
164            }
165    
166            protected void addSearchAssetCategoryIds(
167                            BooleanQuery contextQuery, SearchContext searchContext)
168                    throws Exception {
169    
170                    long[] assetCategoryIds = searchContext.getAssetCategoryIds();
171    
172                    if ((assetCategoryIds == null) || (assetCategoryIds.length == 0)) {
173                            return;
174                    }
175    
176                    BooleanQuery assetCategoryIdsQuery = BooleanQueryFactoryUtil.create();
177    
178                    for (long assetCategoryId : assetCategoryIds) {
179                            if (searchContext.getUserId() > 0) {
180                                    try {
181                                            AssetCategoryServiceUtil.getCategory(assetCategoryId);
182                                    }
183                                    catch (Exception e) {
184                                            continue;
185                                    }
186                            }
187    
188                            TermQuery termQuery = TermQueryFactoryUtil.create(
189                                    Field.ASSET_CATEGORY_IDS, assetCategoryId);
190    
191                             assetCategoryIdsQuery.add(termQuery, BooleanClauseOccur.MUST);
192                    }
193    
194                    if (!assetCategoryIdsQuery.clauses().isEmpty()) {
195                            contextQuery.add(assetCategoryIdsQuery, BooleanClauseOccur.MUST);
196                    }
197            }
198    
199            protected void addSearchAssetTagNames(
200                            BooleanQuery contextQuery, SearchContext searchContext)
201                    throws Exception {
202    
203                    String[] assetTagNames = searchContext.getAssetTagNames();
204    
205                    if ((assetTagNames == null) || (assetTagNames.length == 0)) {
206                            return;
207                    }
208    
209                    BooleanQuery assetTagNamesQuery = BooleanQueryFactoryUtil.create();
210    
211                    for (String assetTagName : assetTagNames) {
212                            TermQuery termQuery = TermQueryFactoryUtil.create(
213                                    Field.ASSET_TAG_NAMES, assetTagName);
214    
215                            assetTagNamesQuery.add(termQuery, BooleanClauseOccur.MUST);
216                    }
217    
218                    if (!assetTagNamesQuery.clauses().isEmpty()) {
219                            contextQuery.add(assetTagNamesQuery, BooleanClauseOccur.MUST);
220                    }
221            }
222    
223            protected void addSearchCategoryIds(
224                            BooleanQuery contextQuery, SearchContext searchContext)
225                    throws Exception {
226    
227                    long[] categoryIds = searchContext.getCategoryIds();
228    
229                    if ((categoryIds == null) || (categoryIds.length == 0)) {
230                            return;
231                    }
232    
233                    BooleanQuery categoryIdsQuery = BooleanQueryFactoryUtil.create();
234    
235                    for (long categoryId : categoryIds) {
236                            if (searchContext.getUserId() > 0) {
237                                    try {
238                                            checkSearchCategoryId(categoryId, searchContext);
239                                    }
240                                    catch (Exception e) {
241                                            continue;
242                                    }
243                            }
244    
245                            TermQuery termQuery = TermQueryFactoryUtil.create(
246                                    Field.CATEGORY_ID, categoryId);
247    
248                            categoryIdsQuery.add(termQuery, BooleanClauseOccur.SHOULD);
249                    }
250    
251                    if (!categoryIdsQuery.clauses().isEmpty()) {
252                            contextQuery.add(categoryIdsQuery, BooleanClauseOccur.MUST);
253                    }
254            }
255    
256            protected void addSearchFolderIds(
257                            BooleanQuery contextQuery, SearchContext searchContext)
258                    throws Exception {
259    
260                    long[] folderIds = searchContext.getFolderIds();
261    
262                    if ((folderIds == null) || (folderIds.length == 0)) {
263                            return;
264                    }
265    
266                    BooleanQuery folderIdsQuery = BooleanQueryFactoryUtil.create();
267    
268                    for (long folderId : folderIds) {
269                            if (searchContext.getUserId() > 0) {
270                                    try {
271                                            checkSearchFolderId(folderId, searchContext);
272                                    }
273                                    catch (Exception e) {
274                                            continue;
275                                    }
276                            }
277    
278                            TermQuery termQuery = TermQueryFactoryUtil.create(
279                                    Field.FOLDER_ID, folderId);
280    
281                            folderIdsQuery.add(termQuery, BooleanClauseOccur.SHOULD);
282                    }
283    
284                    if (!folderIdsQuery.clauses().isEmpty()) {
285                            contextQuery.add(folderIdsQuery, BooleanClauseOccur.MUST);
286                    }
287            }
288    
289            protected void addSearchGroupId(
290                            BooleanQuery contextQuery, SearchContext searchContext)
291                    throws Exception {
292    
293                    long[] groupIds = searchContext.getGroupIds();
294    
295                    if ((groupIds == null) || (groupIds.length == 0) ||
296                            ((groupIds.length == 1) && (groupIds[0] == 0))){
297    
298                            return;
299                    }
300    
301                    BooleanQuery groupIdsQuery = BooleanQueryFactoryUtil.create();
302    
303                    for (int i = 0; i < groupIds.length; i ++) {
304                            long groupId = groupIds[i];
305    
306                            if (groupId <= 0) {
307                                    continue;
308                            }
309    
310                            try {
311                                    Group group = GroupLocalServiceUtil.getGroup(groupId);
312    
313                                    long parentGroupId = groupId;
314    
315                                    if (group.isLayout() || searchContext.isScopeStrict()) {
316                                            contextQuery.addRequiredTerm(
317                                                    Field.SCOPE_GROUP_ID, groupId);
318                                    }
319    
320                                    if (group.isLayout()) {
321                                            parentGroupId = group.getParentGroupId();
322                                    }
323    
324                                    groupIdsQuery.addTerm(Field.GROUP_ID, parentGroupId);
325    
326                                    groupIds[i] = parentGroupId;
327                            }
328                            catch (Exception e) {
329                                    continue;
330                            }
331                    }
332    
333                    searchContext.setGroupIds(groupIds);
334    
335                    if (!groupIdsQuery.clauses().isEmpty()) {
336                            contextQuery.add(groupIdsQuery, BooleanClauseOccur.MUST);
337                    }
338            }
339    
340            protected void addSearchKeywords(
341                            BooleanQuery searchQuery, SearchContext searchContext)
342                    throws Exception {
343    
344                    String keywords = searchContext.getKeywords();
345    
346                    if (Validator.isNull(keywords)) {
347                            return;
348                    }
349    
350                    searchQuery.addTerms(_KEYWORDS_FIELDS, keywords);
351            }
352    
353            protected void addSearchNodeIds(
354                            BooleanQuery contextQuery, SearchContext searchContext)
355                    throws Exception {
356    
357                    long[] nodeIds = searchContext.getNodeIds();
358    
359                    if ((nodeIds == null) || (nodeIds.length == 0)) {
360                            return;
361                    }
362    
363                    BooleanQuery nodeIdsQuery = BooleanQueryFactoryUtil.create();
364    
365                    for (long nodeId : nodeIds) {
366                            if (searchContext.getUserId() > 0) {
367                                    try {
368                                            checkSearchNodeId(nodeId, searchContext);
369                                    }
370                                    catch (Exception e) {
371                                            continue;
372                                    }
373                            }
374    
375                            TermQuery termQuery = TermQueryFactoryUtil.create(
376                                    Field.NODE_ID, nodeId);
377    
378                            nodeIdsQuery.add(termQuery, BooleanClauseOccur.SHOULD);
379                    }
380    
381                    if (!nodeIdsQuery.clauses().isEmpty()) {
382                            contextQuery.add(nodeIdsQuery, BooleanClauseOccur.MUST);
383                    }
384            }
385    
386            protected void addSearchOwnerUserId(
387                    BooleanQuery contextQuery, SearchContext searchContext) {
388    
389                    long ownerUserId = searchContext.getOwnerUserId();
390    
391                    if (ownerUserId > 0) {
392                            contextQuery.addRequiredTerm(Field.USER_ID, ownerUserId);
393                    }
394            }
395    
396            protected void addSearchPortletIds(
397                            BooleanQuery contextQuery, SearchContext searchContext)
398                    throws Exception {
399    
400                    String[] portletIds = searchContext.getPortletIds();
401    
402                    if ((portletIds == null) || (portletIds.length == 0)) {
403                            contextQuery.addRequiredTerm(
404                                    Field.PORTLET_ID, getPortletId(searchContext));
405                    }
406                    else {
407                            BooleanQuery portletIdsQuery = BooleanQueryFactoryUtil.create();
408    
409                            for (String portletId : portletIds) {
410                                    if (Validator.isNull(portletId)) {
411                                            continue;
412                                    }
413    
414                                    TermQuery termQuery = TermQueryFactoryUtil.create(
415                                            Field.PORTLET_ID, portletId);
416    
417                                    portletIdsQuery.add(termQuery, BooleanClauseOccur.SHOULD);
418                            }
419    
420                            if (!portletIdsQuery.clauses().isEmpty()) {
421                                    contextQuery.add(portletIdsQuery, BooleanClauseOccur.MUST);
422                            }
423                    }
424            }
425    
426            protected void checkSearchCategoryId(
427                            long categoryId, SearchContext searchContext)
428                    throws Exception {
429            }
430    
431            protected void checkSearchFolderId(
432                            long folderId, SearchContext searchContext)
433                    throws Exception {
434            }
435    
436            protected void checkSearchNodeId(
437                            long nodeId, SearchContext searchContext)
438                    throws Exception {
439            }
440    
441            protected BooleanQuery createFullQuery(
442                            BooleanQuery contextQuery, SearchContext searchContext)
443                    throws Exception {
444    
445                    postProcessContextQuery(contextQuery, searchContext);
446    
447                    BooleanQuery searchQuery = BooleanQueryFactoryUtil.create();
448    
449                    addSearchKeywords(searchQuery, searchContext);
450                    postProcessSearchQuery(searchQuery, searchContext);
451    
452                    BooleanQuery fullQuery = BooleanQueryFactoryUtil.create();
453    
454                    fullQuery.add(contextQuery, BooleanClauseOccur.MUST);
455    
456                    if (!searchQuery.clauses().isEmpty()) {
457                            fullQuery.add(searchQuery, BooleanClauseOccur.MUST);
458                    }
459    
460                    BooleanClause[] booleanClauses = searchContext.getBooleanClauses();
461    
462                    if (booleanClauses != null) {
463                            for (BooleanClause booleanClause : booleanClauses) {
464                                    fullQuery.add(
465                                            booleanClause.getQuery(),
466                                            booleanClause.getBooleanClauseOccur());
467                            }
468                    }
469    
470                    postProcessFullQuery(fullQuery, searchContext);
471    
472                    return fullQuery;
473            }
474    
475            protected abstract void doDelete(Object obj) throws Exception;
476    
477            protected abstract Document doGetDocument(Object obj) throws Exception;
478    
479            protected abstract void doReindex(Object obj) throws Exception;
480    
481            protected abstract void doReindex(String className, long classPK)
482                    throws Exception;
483    
484            protected abstract void doReindex(String[] ids) throws Exception;
485    
486            protected Hits filterSearch(
487                    Hits hits, PermissionChecker permissionChecker,
488                    SearchContext searchContext) {
489    
490                    List<Document> docs = new ArrayList<Document>();
491                    List<Float> scores = new ArrayList<Float>();
492    
493                    for (int i = 0; i < hits.getLength(); i++) {
494                            Document doc = hits.doc(i);
495    
496                            long entryClassPK = GetterUtil.getLong(
497                                    doc.get(Field.ENTRY_CLASS_PK));
498    
499                            try {
500                                    if (hasPermission(
501                                                    permissionChecker, entryClassPK, ActionKeys.VIEW)) {
502    
503                                            docs.add(hits.doc(i));
504                                            scores.add(hits.score(i));
505                                    }
506                            }
507                            catch (Exception e) {
508                            }
509                    }
510    
511                    int length = docs.size();
512    
513                    hits.setLength(length);
514    
515                    int start = searchContext.getStart();
516                    int end = searchContext.getEnd();
517    
518                    if ((start != QueryUtil.ALL_POS) && (end != QueryUtil.ALL_POS)) {
519                            if (end > length) {
520                                    end = length;
521                            }
522    
523                            docs = docs.subList(start, end);
524                    }
525    
526                    hits.setDocs(docs.toArray(new Document[docs.size()]));
527                    hits.setScores(scores.toArray(new Float[docs.size()]));
528    
529                    hits.setSearchTime(
530                            (float)(System.currentTimeMillis() - hits.getStart()) /
531                                    Time.SECOND);
532    
533                    return hits;
534            }
535    
536            protected String getClassName(SearchContext searchContext) {
537                    String[] classNames = getClassNames();
538    
539                    if (classNames.length != 1) {
540                            throw new UnsupportedOperationException(
541                                    "Search method needs to be manually implemented for " +
542                                            "indexers with more than one class name");
543                    }
544    
545                    return classNames[0];
546            }
547    
548            protected long getParentGroupId(long groupId) {
549                    long parentGroupId = groupId;
550    
551                    try {
552                            Group group = GroupLocalServiceUtil.getGroup(groupId);
553    
554                            if (group.isLayout()) {
555                                    parentGroupId = group.getParentGroupId();
556                            }
557                    }
558                    catch (Exception e) {
559                    }
560    
561                    return parentGroupId;
562            }
563    
564            protected abstract String getPortletId(SearchContext searchContext);
565    
566            protected boolean hasPermission(
567                            PermissionChecker permissionChecker, long entryClassPK,
568                            String actionId)
569                    throws Exception {
570    
571                    return true;
572            }
573    
574            protected boolean isFilterSearch() {
575                    return _FILTER_SEARCH;
576            }
577    
578            protected void postProcessContextQuery(
579                            BooleanQuery contextQuery, SearchContext searchContext)
580                    throws Exception {
581            }
582    
583            protected void postProcessFullQuery(
584                            BooleanQuery fullQuery, SearchContext searchContext)
585                    throws Exception {
586            }
587    
588            protected void postProcessSearchQuery(
589                            BooleanQuery searchQuery, SearchContext searchContext)
590                    throws Exception {
591            }
592    
593            private static final boolean _FILTER_SEARCH = false;
594    
595            public static final int INDEX_FILTER_SEARCH_LIMIT = GetterUtil.getInteger(
596                    PropsUtil.get(PropsKeys.INDEX_FILTER_SEARCH_LIMIT));
597    
598            private static final String[] _KEYWORDS_FIELDS = {
599                    Field.ASSET_TAG_NAMES, Field.COMMENTS, Field.CONTENT, Field.DESCRIPTION,
600                    Field.PROPERTIES, Field.TITLE, Field.URL, Field.USER_NAME
601            };
602    
603            private static Log _log = LogFactoryUtil.getLog(BaseIndexer.class);
604    
605    }