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