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