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