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