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.portlet.journal.util;
016    
017    import com.liferay.portal.kernel.dao.orm.ActionableDynamicQuery;
018    import com.liferay.portal.kernel.dao.orm.DynamicQuery;
019    import com.liferay.portal.kernel.dao.orm.Property;
020    import com.liferay.portal.kernel.dao.orm.PropertyFactoryUtil;
021    import com.liferay.portal.kernel.exception.PortalException;
022    import com.liferay.portal.kernel.exception.SystemException;
023    import com.liferay.portal.kernel.search.BaseIndexer;
024    import com.liferay.portal.kernel.search.BooleanClauseOccur;
025    import com.liferay.portal.kernel.search.BooleanQuery;
026    import com.liferay.portal.kernel.search.BooleanQueryFactoryUtil;
027    import com.liferay.portal.kernel.search.Document;
028    import com.liferay.portal.kernel.search.DocumentImpl;
029    import com.liferay.portal.kernel.search.Field;
030    import com.liferay.portal.kernel.search.SearchContext;
031    import com.liferay.portal.kernel.search.SearchEngineUtil;
032    import com.liferay.portal.kernel.search.Summary;
033    import com.liferay.portal.kernel.util.GetterUtil;
034    import com.liferay.portal.kernel.util.HtmlUtil;
035    import com.liferay.portal.kernel.util.LocaleUtil;
036    import com.liferay.portal.kernel.util.LocalizationUtil;
037    import com.liferay.portal.kernel.util.StringPool;
038    import com.liferay.portal.kernel.util.StringUtil;
039    import com.liferay.portal.kernel.util.Validator;
040    import com.liferay.portal.kernel.workflow.WorkflowConstants;
041    import com.liferay.portal.security.permission.ActionKeys;
042    import com.liferay.portal.security.permission.PermissionChecker;
043    import com.liferay.portal.util.PortalUtil;
044    import com.liferay.portal.util.PortletKeys;
045    import com.liferay.portlet.dynamicdatamapping.StructureFieldException;
046    import com.liferay.portlet.dynamicdatamapping.model.DDMStructure;
047    import com.liferay.portlet.dynamicdatamapping.service.DDMStructureLocalServiceUtil;
048    import com.liferay.portlet.dynamicdatamapping.storage.Fields;
049    import com.liferay.portlet.dynamicdatamapping.util.DDMIndexerUtil;
050    import com.liferay.portlet.dynamicdatamapping.util.DDMUtil;
051    import com.liferay.portlet.journal.asset.JournalArticleAssetRendererFactory;
052    import com.liferay.portlet.journal.model.JournalArticle;
053    import com.liferay.portlet.journal.model.JournalFolder;
054    import com.liferay.portlet.journal.model.JournalFolderConstants;
055    import com.liferay.portlet.journal.service.JournalArticleLocalServiceUtil;
056    import com.liferay.portlet.journal.service.JournalFolderServiceUtil;
057    import com.liferay.portlet.journal.service.permission.JournalArticlePermission;
058    import com.liferay.portlet.journal.service.persistence.JournalArticleActionableDynamicQuery;
059    import com.liferay.portlet.trash.util.TrashUtil;
060    
061    import java.io.Serializable;
062    
063    import java.util.ArrayList;
064    import java.util.Collection;
065    import java.util.LinkedHashMap;
066    import java.util.List;
067    import java.util.Locale;
068    
069    import javax.portlet.PortletURL;
070    
071    /**
072     * @author Brian Wing Shun Chan
073     * @author Harry Mark
074     * @author Bruno Farache
075     * @author Raymond Aug??
076     * @author Hugo Huijser
077     * @author Tibor Lipusz
078     */
079    public class JournalArticleIndexer extends BaseIndexer {
080    
081            public static final String[] CLASS_NAMES = {JournalArticle.class.getName()};
082    
083            public static final String PORTLET_ID = PortletKeys.JOURNAL;
084    
085            public JournalArticleIndexer() {
086                    setFilterSearch(true);
087                    setPermissionAware(true);
088            }
089    
090            @Override
091            public String[] getClassNames() {
092                    return CLASS_NAMES;
093            }
094    
095            @Override
096            public String getPortletId() {
097                    return PORTLET_ID;
098            }
099    
100            @Override
101            public boolean hasPermission(
102                            PermissionChecker permissionChecker, String entryClassName,
103                            long entryClassPK, String actionId)
104                    throws Exception {
105    
106                    return JournalArticlePermission.contains(
107                            permissionChecker, entryClassPK, ActionKeys.VIEW);
108            }
109    
110            @Override
111            public void postProcessContextQuery(
112                            BooleanQuery contextQuery, SearchContext searchContext)
113                    throws Exception {
114    
115                    Long classNameId = (Long)searchContext.getAttribute(
116                            Field.CLASS_NAME_ID);
117    
118                    if ((classNameId != null) && (classNameId.longValue() != 0)) {
119                            contextQuery.addRequiredTerm("classNameId", classNameId.toString());
120                    }
121    
122                    addStatus(contextQuery, searchContext);
123    
124                    addSearchClassTypeIds(contextQuery, searchContext);
125    
126                    String ddmStructureFieldName = (String)searchContext.getAttribute(
127                            "ddmStructureFieldName");
128                    Serializable ddmStructureFieldValue = searchContext.getAttribute(
129                            "ddmStructureFieldValue");
130    
131                    if (Validator.isNotNull(ddmStructureFieldName) &&
132                            Validator.isNotNull(ddmStructureFieldValue)) {
133    
134                            String[] ddmStructureFieldNameParts = StringUtil.split(
135                                    ddmStructureFieldName, StringPool.SLASH);
136    
137                            DDMStructure structure = DDMStructureLocalServiceUtil.getStructure(
138                                    GetterUtil.getLong(ddmStructureFieldNameParts[1]));
139    
140                            String fieldName = StringUtil.replaceLast(
141                                    ddmStructureFieldNameParts[2],
142                                    StringPool.UNDERLINE.concat(
143                                            LocaleUtil.toLanguageId(searchContext.getLocale())),
144                                    StringPool.BLANK);
145    
146                            try {
147                                    ddmStructureFieldValue = DDMUtil.getIndexedFieldValue(
148                                            ddmStructureFieldValue, structure.getFieldType(fieldName));
149                            }
150                            catch (StructureFieldException sfe) {
151                            }
152    
153                            contextQuery.addRequiredTerm(
154                                    ddmStructureFieldName,
155                                    StringPool.QUOTE + ddmStructureFieldValue + StringPool.QUOTE);
156                    }
157    
158                    long[] folderIds = searchContext.getFolderIds();
159    
160                    if ((folderIds != null) && (folderIds.length > 0) &&
161                            (folderIds[0] !=
162                                    JournalFolderConstants.DEFAULT_PARENT_FOLDER_ID)) {
163    
164                            BooleanQuery folderIdsQuery = BooleanQueryFactoryUtil.create(
165                                    searchContext);
166    
167                            for (long folderId : folderIds) {
168                                    try {
169                                            JournalFolderServiceUtil.getFolder(folderId);
170                                    }
171                                    catch (Exception e) {
172                                            continue;
173                                    }
174    
175                                    folderIdsQuery.addTerm(Field.FOLDER_ID, folderId);
176                            }
177    
178                            contextQuery.add(folderIdsQuery, BooleanClauseOccur.MUST);
179                    }
180    
181                    String articleType = (String)searchContext.getAttribute("articleType");
182    
183                    if (Validator.isNotNull(articleType)) {
184                            contextQuery.addRequiredTerm(Field.TYPE, articleType);
185                    }
186    
187                    String ddmStructureKey = (String)searchContext.getAttribute(
188                            "ddmStructureKey");
189    
190                    if (Validator.isNotNull(ddmStructureKey)) {
191                            contextQuery.addRequiredTerm("ddmStructureKey", ddmStructureKey);
192                    }
193    
194                    String ddmTemplateKey = (String)searchContext.getAttribute(
195                            "ddmTemplateKey");
196    
197                    if (Validator.isNotNull(ddmTemplateKey)) {
198                            contextQuery.addRequiredTerm("ddmTemplateKey", ddmTemplateKey);
199                    }
200    
201                    boolean head = GetterUtil.getBoolean(
202                            searchContext.getAttribute("head"), Boolean.TRUE);
203                    boolean relatedClassName = GetterUtil.getBoolean(
204                            searchContext.getAttribute("relatedClassName"));
205    
206                    if (head && !relatedClassName) {
207                            contextQuery.addRequiredTerm("head", Boolean.TRUE);
208                    }
209            }
210    
211            @Override
212            public void postProcessSearchQuery(
213                            BooleanQuery searchQuery, SearchContext searchContext)
214                    throws Exception {
215    
216                    addSearchTerm(searchQuery, searchContext, Field.CLASS_PK, false);
217                    addSearchLocalizedTerm(
218                            searchQuery, searchContext, Field.CONTENT, false);
219                    addSearchLocalizedTerm(
220                            searchQuery, searchContext, Field.DESCRIPTION, false);
221                    addSearchTerm(searchQuery, searchContext, Field.ENTRY_CLASS_PK, false);
222                    addSearchLocalizedTerm(searchQuery, searchContext, Field.TITLE, false);
223                    addSearchTerm(searchQuery, searchContext, Field.TYPE, false);
224                    addSearchTerm(searchQuery, searchContext, Field.USER_NAME, false);
225    
226                    addSearchTerm(searchQuery, searchContext, "articleId", false);
227    
228                    LinkedHashMap<String, Object> params =
229                            (LinkedHashMap<String, Object>)searchContext.getAttribute("params");
230    
231                    if (params != null) {
232                            String expandoAttributes = (String)params.get("expandoAttributes");
233    
234                            if (Validator.isNotNull(expandoAttributes)) {
235                                    addSearchExpando(searchQuery, searchContext, expandoAttributes);
236                            }
237                    }
238            }
239    
240            protected void addDDMStructureAttributes(
241                            Document document, JournalArticle article)
242                    throws Exception {
243    
244                    if (Validator.isNull(article.getStructureId())) {
245                            return;
246                    }
247    
248                    DDMStructure ddmStructure = DDMStructureLocalServiceUtil.fetchStructure(
249                            article.getGroupId(),
250                            PortalUtil.getClassNameId(JournalArticle.class),
251                            article.getStructureId(), true);
252    
253                    if (ddmStructure == null) {
254                            return;
255                    }
256    
257                    document.addKeyword(Field.CLASS_TYPE_ID, ddmStructure.getStructureId());
258    
259                    Fields fields = null;
260    
261                    try {
262                            fields = JournalConverterUtil.getDDMFields(
263                                    ddmStructure, article.getContent());
264                    }
265                    catch (Exception e) {
266                            return;
267                    }
268    
269                    if (fields != null) {
270                            DDMIndexerUtil.addAttributes(document, ddmStructure, fields);
271                    }
272            }
273    
274            @Override
275            protected void addSearchLocalizedTerm(
276                            BooleanQuery searchQuery, SearchContext searchContext, String field,
277                            boolean like)
278                    throws Exception {
279    
280                    if (Validator.isNull(field)) {
281                            return;
282                    }
283    
284                    String value = String.valueOf(searchContext.getAttribute(field));
285    
286                    if (Validator.isNull(value)) {
287                            value = searchContext.getKeywords();
288                    }
289    
290                    if (Validator.isNull(value)) {
291                            return;
292                    }
293    
294                    field = DocumentImpl.getLocalizedName(searchContext.getLocale(), field);
295    
296                    if (searchContext.isAndSearch()) {
297                            searchQuery.addRequiredTerm(field, value, like);
298                    }
299                    else {
300                            searchQuery.addTerm(field, value, like);
301                    }
302            }
303    
304            @Override
305            protected void doDelete(Object obj) throws Exception {
306                    JournalArticle article = (JournalArticle)obj;
307    
308                    deleteDocument(article.getCompanyId(), article.getId());
309    
310                    updateArticles(article);
311            }
312    
313            @Override
314            protected Document doGetDocument(Object obj) throws Exception {
315                    JournalArticle article = (JournalArticle)obj;
316    
317                    Document document = getBaseModelDocument(PORTLET_ID, article);
318    
319                    document.addUID(PORTLET_ID, article.getId());
320    
321                    String articleDefaultLanguageId = LocalizationUtil.getDefaultLanguageId(
322                            article.getContent());
323    
324                    Locale defaultLocale = LocaleUtil.getSiteDefault();
325    
326                    String defaultLanguageId = LocaleUtil.toLanguageId(defaultLocale);
327    
328                    String[] languageIds = getLanguageIds(
329                            defaultLanguageId, article.getContent());
330    
331                    for (String languageId : languageIds) {
332                            String content = extractContent(article, languageId);
333    
334                            String description = article.getDescription(languageId);
335    
336                            String title = article.getTitle(languageId);
337    
338                            if (languageId.equals(articleDefaultLanguageId)) {
339                                    document.addText(Field.CONTENT, content);
340                                    document.addText(Field.DESCRIPTION, description);
341                                    document.addText(Field.TITLE, title);
342                                    document.addText("defaultLanguageId", languageId);
343                            }
344    
345                            document.addText(
346                                    Field.CONTENT.concat(StringPool.UNDERLINE).concat(languageId),
347                                    content);
348                            document.addText(
349                                    Field.DESCRIPTION.concat(StringPool.UNDERLINE).concat(
350                                            languageId), description);
351                            document.addText(
352                                    Field.TITLE.concat(StringPool.UNDERLINE).concat(languageId),
353                                    title);
354                    }
355    
356                    document.addKeyword(Field.FOLDER_ID, article.getFolderId());
357                    document.addKeyword(Field.LAYOUT_UUID, article.getLayoutUuid());
358                    document.addKeyword(Field.TYPE, article.getType());
359                    document.addKeyword(Field.VERSION, article.getVersion());
360    
361                    String articleId = article.getArticleId();
362    
363                    if (article.isInTrash()) {
364                            articleId = TrashUtil.getOriginalTitle(articleId);
365                    }
366    
367                    document.addKeyword("articleId", articleId);
368                    document.addKeyword("ddmStructureKey", article.getStructureId());
369                    document.addKeyword("ddmTemplateKey", article.getTemplateId());
370                    document.addDate("displayDate", article.getDisplayDate());
371                    document.addKeyword("head", false);
372    
373                    addDDMStructureAttributes(document, article);
374    
375                    if (!article.isInTrash() && article.isInTrashContainer()) {
376                            JournalFolder folder = article.getTrashContainer();
377    
378                            addTrashFields(
379                                    document, JournalFolder.class.getName(), folder.getFolderId(),
380                                    null, null, JournalArticleAssetRendererFactory.TYPE);
381    
382                            document.addKeyword(
383                                    Field.ROOT_ENTRY_CLASS_NAME, JournalFolder.class.getName());
384                            document.addKeyword(
385                                    Field.ROOT_ENTRY_CLASS_PK, folder.getFolderId());
386                            document.addKeyword(
387                                    Field.STATUS, WorkflowConstants.STATUS_IN_TRASH);
388                    }
389    
390                    return document;
391            }
392    
393            @Override
394            protected String doGetSortField(String orderByCol) {
395                    if (orderByCol.equals("display-date")) {
396                            return "displayDate";
397                    }
398                    else if (orderByCol.equals("id")) {
399                            return Field.ENTRY_CLASS_PK;
400                    }
401                    else if (orderByCol.equals("modified-date")) {
402                            return Field.MODIFIED_DATE;
403                    }
404                    else if (orderByCol.equals("title")) {
405                            return Field.TITLE;
406                    }
407                    else {
408                            return orderByCol;
409                    }
410            }
411    
412            @Override
413            protected Summary doGetSummary(
414                    Document document, Locale locale, String snippet,
415                    PortletURL portletURL) {
416    
417                    Locale snippetLocale = getSnippetLocale(document, locale);
418    
419                    if (snippetLocale == null) {
420                            snippetLocale = LocaleUtil.fromLanguageId(
421                                    document.get("defaultLanguageId"));
422                    }
423    
424                    String prefix = Field.SNIPPET + StringPool.UNDERLINE;
425    
426                    String title = document.get(
427                            snippetLocale, prefix + Field.TITLE, Field.TITLE);
428    
429                    String content = document.get(
430                            snippetLocale, prefix + Field.DESCRIPTION, prefix + Field.CONTENT);
431    
432                    if (Validator.isBlank(content)) {
433                            content = document.get(
434                                    snippetLocale, Field.DESCRIPTION, Field.CONTENT);
435                    }
436    
437                    if (content.length() > 200) {
438                            content = StringUtil.shorten(content, 200);
439                    }
440    
441                    String groupId = document.get(Field.GROUP_ID);
442                    String articleId = document.get("articleId");
443                    String version = document.get(Field.VERSION);
444    
445                    portletURL.setParameter("struts_action", "/journal/edit_article");
446                    portletURL.setParameter("groupId", groupId);
447                    portletURL.setParameter("articleId", articleId);
448                    portletURL.setParameter("version", version);
449    
450                    return new Summary(snippetLocale, title, content, portletURL);
451            }
452    
453            @Override
454            protected void doReindex(Object obj) throws Exception {
455                    JournalArticle article = (JournalArticle)obj;
456    
457                    Document document = getDocument(article);
458    
459                    if (!article.isIndexable() ||
460                            (PortalUtil.getClassNameId(DDMStructure.class) ==
461                                    article.getClassNameId())) {
462    
463                            SearchEngineUtil.deleteDocument(
464                                    getSearchEngineId(), article.getCompanyId(),
465                                    document.get(Field.UID));
466    
467                            return;
468                    }
469    
470                    SearchEngineUtil.updateDocument(
471                            getSearchEngineId(), article.getCompanyId(), document);
472    
473                    updateArticles(article);
474            }
475    
476            @Override
477            protected void doReindex(String className, long classPK) throws Exception {
478                    JournalArticle article = JournalArticleLocalServiceUtil.getArticle(
479                            classPK);
480    
481                    doReindex(article);
482            }
483    
484            @Override
485            protected void doReindex(String[] ids) throws Exception {
486                    long companyId = GetterUtil.getLong(ids[0]);
487    
488                    reindexArticles(companyId);
489            }
490    
491            @Override
492            protected void doReindexDDMStructures(List<Long> ddmStructureIds)
493                    throws Exception {
494    
495                    String[] ddmStructureKeys = new String[ddmStructureIds.size()];
496    
497                    for (int i = 0; i < ddmStructureIds.size(); i++) {
498                            long structureId = ddmStructureIds.get(i);
499    
500                            DDMStructure ddmStructure =
501                                    DDMStructureLocalServiceUtil.getDDMStructure(structureId);
502    
503                            ddmStructureKeys[i] = ddmStructure.getStructureKey();
504                    }
505    
506                    List<JournalArticle> articles =
507                            JournalArticleLocalServiceUtil.getStructureArticles(
508                                    ddmStructureKeys);
509    
510                    for (JournalArticle article : articles) {
511                            doReindex(article);
512                    }
513            }
514    
515            protected String extractBasicContent(
516                    JournalArticle article, String languageId) {
517    
518                    String content = article.getContentByLocale(languageId);
519    
520                    content = StringUtil.replace(content, "<![CDATA[", StringPool.BLANK);
521                    content = StringUtil.replace(content, "]]>", StringPool.BLANK);
522                    content = StringUtil.replace(content, "&amp;", "&");
523                    content = StringUtil.replace(content, "&lt;", "<");
524                    content = StringUtil.replace(content, "&gt;", ">");
525    
526                    content = HtmlUtil.extractText(content);
527    
528                    return content;
529            }
530    
531            protected String extractContent(JournalArticle article, String languageId)
532                    throws Exception {
533    
534                    if (Validator.isNotNull(article.getStructureId())) {
535                            return extractDDMContent(article, languageId);
536                    }
537    
538                    return extractBasicContent(article, languageId);
539            }
540    
541            protected String extractDDMContent(
542                            JournalArticle article, String languageId)
543                    throws Exception {
544    
545                    if (Validator.isNull(article.getStructureId())) {
546                            return StringPool.BLANK;
547                    }
548    
549                    DDMStructure ddmStructure = DDMStructureLocalServiceUtil.fetchStructure(
550                            article.getGroupId(),
551                            PortalUtil.getClassNameId(JournalArticle.class),
552                            article.getStructureId(), true);
553    
554                    if (ddmStructure == null) {
555                            return StringPool.BLANK;
556                    }
557    
558                    Fields fields = null;
559    
560                    try {
561                            fields = JournalConverterUtil.getDDMFields(
562                                    ddmStructure, article.getContent());
563                    }
564                    catch (Exception e) {
565                            return StringPool.BLANK;
566                    }
567    
568                    if (fields == null) {
569                            return StringPool.BLANK;
570                    }
571    
572                    return DDMIndexerUtil.extractAttributes(
573                            ddmStructure, fields, LocaleUtil.fromLanguageId(languageId));
574            }
575    
576            protected String[] getLanguageIds(
577                    String defaultLanguageId, String content) {
578    
579                    String[] languageIds = LocalizationUtil.getAvailableLanguageIds(
580                            content);
581    
582                    if (languageIds.length == 0) {
583                            languageIds = new String[] {defaultLanguageId};
584                    }
585    
586                    return languageIds;
587            }
588    
589            @Override
590            protected String getPortletId(SearchContext searchContext) {
591                    return PORTLET_ID;
592            }
593    
594            protected void reindexArticles(long companyId)
595                    throws PortalException, SystemException {
596    
597                    final Collection<Document> documents = new ArrayList<Document>();
598    
599                    ActionableDynamicQuery actionableDynamicQuery =
600                            new JournalArticleActionableDynamicQuery() {
601    
602                            @Override
603                            protected void addCriteria(DynamicQuery dynamicQuery) {
604                                    Property indexableProperty = PropertyFactoryUtil.forName(
605                                            "indexable");
606    
607                                    dynamicQuery.add(indexableProperty.eq(true));
608                            }
609    
610                            @Override
611                            protected void performAction(Object object) throws PortalException {
612                                    JournalArticle article = (JournalArticle)object;
613    
614                                    Document document = getDocument(article);
615    
616                                    documents.add(document);
617                            }
618    
619                    };
620    
621                    actionableDynamicQuery.setCompanyId(companyId);
622    
623                    actionableDynamicQuery.performActions();
624    
625                    SearchEngineUtil.updateDocuments(
626                            getSearchEngineId(), companyId, documents);
627            }
628    
629            protected void updateArticles(JournalArticle article) throws Exception {
630                    JournalArticle latestIndexableArticle =
631                            JournalArticleLocalServiceUtil.fetchLatestIndexableArticle(
632                                    article.getResourcePrimKey());
633    
634                    List<JournalArticle> articles =
635                            JournalArticleLocalServiceUtil.getArticlesByResourcePrimKey(
636                                    article.getResourcePrimKey());
637    
638                    for (JournalArticle curArticle : articles) {
639                            if (!curArticle.isIndexable() ||
640                                    ((latestIndexableArticle != null) &&
641                                     (curArticle.getArticleId() ==
642                                            latestIndexableArticle.getArticleId()))) {
643    
644                                    continue;
645                            }
646    
647                            Document document = getDocument(curArticle);
648    
649                            document.addKeyword("head", false);
650    
651                            SearchEngineUtil.updateDocument(
652                                    getSearchEngineId(), curArticle.getCompanyId(), document);
653                    }
654    
655                    if (latestIndexableArticle != null) {
656                            Document document = getDocument(latestIndexableArticle);
657    
658                            document.addKeyword("head", true);
659    
660                            SearchEngineUtil.updateDocument(
661                                    getSearchEngineId(), latestIndexableArticle.getCompanyId(),
662                                    document);
663                    }
664                    else if (article.getStatus() == WorkflowConstants.STATUS_IN_TRASH) {
665                            Document document = getDocument(article);
666    
667                            document.addKeyword("head", true);
668    
669                            SearchEngineUtil.updateDocument(
670                                    getSearchEngineId(), article.getCompanyId(), document);
671                    }
672            }
673    
674    }