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