001    /**
002     * Copyright (c) 2000-2012 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.log.Log;
026    import com.liferay.portal.kernel.log.LogFactoryUtil;
027    import com.liferay.portal.kernel.search.BaseIndexer;
028    import com.liferay.portal.kernel.search.BooleanClauseOccur;
029    import com.liferay.portal.kernel.search.BooleanQuery;
030    import com.liferay.portal.kernel.search.BooleanQueryFactoryUtil;
031    import com.liferay.portal.kernel.search.Document;
032    import com.liferay.portal.kernel.search.DocumentImpl;
033    import com.liferay.portal.kernel.search.Field;
034    import com.liferay.portal.kernel.search.SearchContext;
035    import com.liferay.portal.kernel.search.SearchEngineUtil;
036    import com.liferay.portal.kernel.search.Summary;
037    import com.liferay.portal.kernel.util.GetterUtil;
038    import com.liferay.portal.kernel.util.HtmlUtil;
039    import com.liferay.portal.kernel.util.LocaleUtil;
040    import com.liferay.portal.kernel.util.LocalizationUtil;
041    import com.liferay.portal.kernel.util.StringBundler;
042    import com.liferay.portal.kernel.util.StringPool;
043    import com.liferay.portal.kernel.util.StringUtil;
044    import com.liferay.portal.kernel.util.Validator;
045    import com.liferay.portal.kernel.workflow.WorkflowConstants;
046    import com.liferay.portal.kernel.xml.DocumentException;
047    import com.liferay.portal.kernel.xml.Element;
048    import com.liferay.portal.kernel.xml.Node;
049    import com.liferay.portal.kernel.xml.SAXReaderUtil;
050    import com.liferay.portal.util.PortletKeys;
051    import com.liferay.portlet.journal.NoSuchStructureException;
052    import com.liferay.portlet.journal.model.JournalArticle;
053    import com.liferay.portlet.journal.model.JournalArticleConstants;
054    import com.liferay.portlet.journal.model.JournalFolderConstants;
055    import com.liferay.portlet.journal.model.JournalStructure;
056    import com.liferay.portlet.journal.service.JournalArticleLocalServiceUtil;
057    import com.liferay.portlet.journal.service.JournalFolderServiceUtil;
058    import com.liferay.portlet.journal.service.JournalStructureLocalServiceUtil;
059    import com.liferay.portlet.journal.service.persistence.JournalArticleActionableDynamicQuery;
060    
061    import java.util.ArrayList;
062    import java.util.Collection;
063    import java.util.LinkedHashMap;
064    import java.util.LinkedList;
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 JournalIndexer 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 JournalIndexer() {
084                    setPermissionAware(true);
085            }
086    
087            public String[] getClassNames() {
088                    return CLASS_NAMES;
089            }
090    
091            public String getPortletId() {
092                    return PORTLET_ID;
093            }
094    
095            @Override
096            public void postProcessContextQuery(
097                            BooleanQuery contextQuery, SearchContext searchContext)
098                    throws Exception {
099    
100                    Long classNameId = (Long)searchContext.getAttribute(
101                            Field.CLASS_NAME_ID);
102    
103                    if (classNameId != null) {
104                            contextQuery.addRequiredTerm("classNameId", classNameId.toString());
105                    }
106    
107                    int status = GetterUtil.getInteger(
108                            searchContext.getAttribute(Field.STATUS),
109                            WorkflowConstants.STATUS_APPROVED);
110    
111                    if (status != WorkflowConstants.STATUS_ANY) {
112                            contextQuery.addRequiredTerm(Field.STATUS, status);
113                    }
114    
115                    long[] folderIds = searchContext.getFolderIds();
116    
117                    if ((folderIds != null) && (folderIds.length > 0)) {
118                            if (folderIds[0] ==
119                                            JournalFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
120    
121                                    return;
122                            }
123    
124                            BooleanQuery folderIdsQuery = BooleanQueryFactoryUtil.create(
125                                    searchContext);
126    
127                            for (long folderId : folderIds) {
128                                    try {
129                                            JournalFolderServiceUtil.getFolder(folderId);
130                                    }
131                                    catch (Exception e) {
132                                            continue;
133                                    }
134    
135                                    folderIdsQuery.addTerm(Field.FOLDER_ID, folderId);
136                            }
137    
138                            contextQuery.add(folderIdsQuery, BooleanClauseOccur.MUST);
139                    }
140    
141                    String articleType = (String)searchContext.getAttribute("articleType");
142    
143                    if (Validator.isNotNull(articleType)) {
144                            contextQuery.addRequiredTerm(Field.TYPE, articleType);
145                    }
146    
147                    String structureId = (String)searchContext.getAttribute("structureId");
148    
149                    if (Validator.isNotNull(structureId)) {
150                            contextQuery.addRequiredTerm("structureId", structureId);
151                    }
152    
153                    String templateId = (String)searchContext.getAttribute("templateId");
154    
155                    if (Validator.isNotNull(templateId)) {
156                            contextQuery.addRequiredTerm("templateId", templateId);
157                    }
158            }
159    
160            @Override
161            public void postProcessSearchQuery(
162                            BooleanQuery searchQuery, SearchContext searchContext)
163                    throws Exception {
164    
165                    addSearchTerm(searchQuery, searchContext, Field.CLASS_PK, false);
166                    addSearchLocalizedTerm(
167                            searchQuery, searchContext, Field.CONTENT, false);
168                    addSearchLocalizedTerm(
169                            searchQuery, searchContext, Field.DESCRIPTION, false);
170                    addSearchTerm(searchQuery, searchContext, Field.ENTRY_CLASS_PK, false);
171                    addSearchLocalizedTerm(searchQuery, searchContext, Field.TITLE, false);
172                    addSearchTerm(searchQuery, searchContext, Field.TYPE, false);
173                    addSearchTerm(searchQuery, searchContext, Field.USER_NAME, false);
174    
175                    addSearchTerm(searchQuery, searchContext, "articleId", false);
176    
177                    LinkedHashMap<String, Object> params =
178                            (LinkedHashMap<String, Object>)searchContext.getAttribute("params");
179    
180                    if (params != null) {
181                            String expandoAttributes = (String)params.get("expandoAttributes");
182    
183                            if (Validator.isNotNull(expandoAttributes)) {
184                                    addSearchExpando(searchQuery, searchContext, expandoAttributes);
185                            }
186                    }
187            }
188    
189            @Override
190            protected void addSearchLocalizedTerm(
191                            BooleanQuery searchQuery, SearchContext searchContext, String field,
192                            boolean like)
193                    throws Exception {
194    
195                    if (Validator.isNull(field)) {
196                            return;
197                    }
198    
199                    String value = String.valueOf(searchContext.getAttribute(field));
200    
201                    if (Validator.isNull(value)) {
202                            value = searchContext.getKeywords();
203                    }
204    
205                    if (Validator.isNull(value)) {
206                            return;
207                    }
208    
209                    field = DocumentImpl.getLocalizedName(searchContext.getLocale(), field);
210    
211                    if (searchContext.isAndSearch()) {
212                            searchQuery.addRequiredTerm(field, value, like);
213                    }
214                    else {
215                            searchQuery.addTerm(field, value, like);
216                    }
217            }
218    
219            @Override
220            protected void doDelete(Object obj) throws Exception {
221                    JournalArticle article = (JournalArticle)obj;
222    
223                    deleteDocument(
224                            article.getCompanyId(), article.getGroupId(),
225                            article.getArticleId());
226            }
227    
228            @Override
229            protected Document doGetDocument(Object obj) throws Exception {
230                    JournalArticle article = (JournalArticle)obj;
231    
232                    Document document = getBaseModelDocument(PORTLET_ID, article);
233    
234                    document.addUID(
235                            PORTLET_ID, article.getGroupId(), article.getArticleId());
236    
237                    Locale defaultLocale = LocaleUtil.getDefault();
238    
239                    String defaultLanguageId = LocaleUtil.toLanguageId(defaultLocale);
240    
241                    String[] languageIds = getLanguageIds(
242                            defaultLanguageId, article.getContent());
243    
244                    for (String languageId : languageIds) {
245                            String content = extractContent(article, languageId);
246    
247                            if (languageId.equals(defaultLanguageId)) {
248                                    document.addText(Field.CONTENT, content);
249                            }
250    
251                            document.addText(
252                                    Field.CONTENT.concat(StringPool.UNDERLINE).concat(languageId),
253                                    content);
254                    }
255    
256                    document.addLocalizedText(
257                            Field.DESCRIPTION, article.getDescriptionMap());
258                    document.addKeyword(Field.FOLDER_ID, article.getFolderId());
259                    document.addLocalizedText(Field.TITLE, article.getTitleMap());
260                    document.addKeyword(Field.TYPE, article.getType());
261                    document.addKeyword(Field.VERSION, article.getVersion());
262    
263                    document.addKeyword("articleId", article.getArticleId());
264                    document.addDate("displayDate", article.getDisplayDate());
265                    document.addKeyword("layoutUuid", article.getLayoutUuid());
266                    document.addKeyword("structureId", article.getStructureId());
267                    document.addKeyword("templateId", article.getTemplateId());
268    
269                    JournalStructure structure = null;
270    
271                    if (Validator.isNotNull(article.getStructureId())) {
272                            try {
273                                    structure = JournalStructureLocalServiceUtil.getStructure(
274                                            article.getGroupId(), article.getStructureId(), true);
275                            }
276                            catch (NoSuchStructureException nsse) {
277                            }
278                    }
279    
280                    processStructure(structure, document, article.getContent());
281    
282                    return document;
283            }
284    
285            @Override
286            protected String doGetSortField(String orderByCol) {
287                    if (orderByCol.equals("display-date")) {
288                            return "displayDate";
289                    }
290                    else if (orderByCol.equals("id")) {
291                            return Field.ENTRY_CLASS_PK;
292                    }
293                    else if (orderByCol.equals("modified-date")) {
294                            return Field.MODIFIED_DATE;
295                    }
296                    else if (orderByCol.equals("title")) {
297                            return Field.TITLE;
298                    }
299                    else {
300                            return orderByCol;
301                    }
302            }
303    
304            @Override
305            protected Summary doGetSummary(
306                    Document document, Locale locale, String snippet,
307                    PortletURL portletURL) {
308    
309                    Locale snippetLocale = getSnippetLocale(document, locale);
310    
311                    String prefix = Field.SNIPPET + StringPool.UNDERLINE;
312    
313                    String title = document.get(
314                            snippetLocale, prefix + Field.TITLE, Field.TITLE);
315    
316                    String content = document.get(
317                            snippetLocale, prefix + Field.DESCRIPTION, prefix + Field.CONTENT);
318    
319                    if (Validator.isBlank(content)) {
320                            content = document.get(locale, Field.DESCRIPTION, Field.CONTENT);
321    
322                            if (Validator.isBlank(content)) {
323                                    content = document.get(Field.DESCRIPTION, Field.CONTENT);
324                            }
325                    }
326    
327                    if (content.length() > 200) {
328                            content = StringUtil.shorten(content, 200);
329                    }
330    
331                    String groupId = document.get(Field.GROUP_ID);
332                    String articleId = document.get("articleId");
333                    String version = document.get(Field.VERSION);
334    
335                    portletURL.setParameter("struts_action", "/journal/edit_article");
336                    portletURL.setParameter("groupId", groupId);
337                    portletURL.setParameter("articleId", articleId);
338                    portletURL.setParameter("version", version);
339    
340                    return new Summary(snippetLocale, title, content, portletURL);
341            }
342    
343            @Override
344            protected void doReindex(Object obj) throws Exception {
345                    JournalArticle article = (JournalArticle)obj;
346    
347                    Document document = getDocument(article);
348    
349                    if (!article.isIndexable() ||
350                            (!article.isApproved() &&
351                             (article.getVersion() !=
352                                      JournalArticleConstants.VERSION_DEFAULT))) {
353    
354                            SearchEngineUtil.deleteDocument(
355                                    getSearchEngineId(), article.getCompanyId(),
356                                    document.get(Field.UID));
357    
358                            return;
359                    }
360    
361                    SearchEngineUtil.updateDocument(
362                            getSearchEngineId(), article.getCompanyId(), document);
363            }
364    
365            @Override
366            protected void doReindex(String className, long classPK) throws Exception {
367                    JournalArticle article =
368                            JournalArticleLocalServiceUtil.getLatestArticle(
369                                    classPK, WorkflowConstants.STATUS_APPROVED);
370    
371                    doReindex(article);
372            }
373    
374            @Override
375            protected void doReindex(String[] ids) throws Exception {
376                    long companyId = GetterUtil.getLong(ids[0]);
377    
378                    reindexArticles(companyId);
379            }
380    
381            protected String encodeFieldName(String name) {
382                    return _FIELD_NAMESPACE.concat(StringPool.FORWARD_SLASH).concat(name);
383            }
384    
385            protected String extractContent(JournalArticle article, String languageId) {
386                    String content = article.getContentByLocale(languageId);
387    
388                    if (Validator.isNotNull(article.getStructureId())) {
389                            content = extractDynamicContent(content);
390                    }
391                    else {
392                            content = extractStaticContent(content);
393                    }
394    
395                    content = HtmlUtil.extractText(content);
396    
397                    return content;
398            }
399    
400            protected String extractDynamicContent(Element rootElement) {
401                    StringBundler sb = new StringBundler();
402    
403                    List<Element> dynamicElementElements = rootElement.elements(
404                            "dynamic-element");
405    
406                    for (Element dynamicElementElement : dynamicElementElements) {
407                            String type = dynamicElementElement.attributeValue(
408                                    "type", StringPool.BLANK);
409    
410                            if (!type.equals("boolean") && !type.equals("document_library") &&
411                                    !type.equals("image") && !type.equals("list") &&
412                                    !type.equals("link_to_layout") && !type.equals("multi-list") &&
413                                    !type.equals("selection_break")) {
414    
415                                    Element dynamicContentElement = dynamicElementElement.element(
416                                            "dynamic-content");
417    
418                                    if (dynamicContentElement != null) {
419                                            String dynamicContent = dynamicContentElement.getText();
420    
421                                            sb.append(dynamicContent);
422                                            sb.append(StringPool.SPACE);
423                                    }
424                            }
425    
426                            sb.append(extractDynamicContent(dynamicElementElement));
427                    }
428    
429                    return sb.toString();
430            }
431    
432            protected String extractDynamicContent(String content) {
433                    try {
434                            com.liferay.portal.kernel.xml.Document document =
435                                    SAXReaderUtil.read(content);
436    
437                            Element rootElement = document.getRootElement();
438    
439                            return extractDynamicContent(rootElement);
440                    }
441                    catch (DocumentException de) {
442                            _log.error(de);
443                    }
444    
445                    return StringPool.BLANK;
446            }
447    
448            protected String extractStaticContent(String content) {
449                    content = StringUtil.replace(content, "<![CDATA[", StringPool.BLANK);
450                    content = StringUtil.replace(content, "]]>", StringPool.BLANK);
451                    content = StringUtil.replace(content, "&amp;", "&");
452                    content = StringUtil.replace(content, "&lt;", "<");
453                    content = StringUtil.replace(content, "&gt;", ">");
454    
455                    return content;
456            }
457    
458            protected String[] getLanguageIds(
459                    String defaultLanguageId, String content) {
460    
461                    String[] languageIds = LocalizationUtil.getAvailableLocales(content);
462    
463                    if (languageIds.length == 0) {
464                            languageIds = new String[] {defaultLanguageId};
465                    }
466    
467                    return languageIds;
468            }
469    
470            @Override
471            protected String getPortletId(SearchContext searchContext) {
472                    return PORTLET_ID;
473            }
474    
475            protected void indexField(
476                    Document document, Element element, String elType, String elIndexType) {
477    
478                    if (Validator.isNull(elIndexType)) {
479                            return;
480                    }
481    
482                    com.liferay.portal.kernel.xml.Document structureDocument =
483                            element.getDocument();
484    
485                    Element rootElement = structureDocument.getRootElement();
486    
487                    String defaultLocale = GetterUtil.getString(
488                            rootElement.attributeValue("default-locale"));
489    
490                    String name = encodeFieldName(element.attributeValue("name"));
491    
492                    List<Element> dynamicContentElements = element.elements(
493                            "dynamic-content");
494    
495                    for (Element dynamicContentElement : dynamicContentElements) {
496                            String contentLocale = GetterUtil.getString(
497                                    dynamicContentElement.attributeValue("language-id"));
498    
499                            String[] value = new String[] {dynamicContentElement.getText()};
500    
501                            if (elType.equals("multi-list")) {
502                                    List<Element> optionElements = dynamicContentElement.elements(
503                                            "option");
504    
505                                    value = new String[optionElements.size()];
506    
507                                    for (int i = 0; i < optionElements.size(); i++) {
508                                            value[i] = optionElements.get(i).getText();
509                                    }
510                            }
511    
512                            if (elIndexType.equals("keyword")) {
513                                    if (Validator.isNull(contentLocale)) {
514                                            document.addKeyword(name, value);
515                                    }
516                                    else {
517                                            if (defaultLocale.equals(contentLocale)) {
518                                                    document.addKeyword(name, value);
519                                            }
520    
521                                            document.addKeyword(
522                                                    name.concat(StringPool.UNDERLINE).concat(contentLocale),
523                                                    value);
524                                    }
525                            }
526                            else if (elIndexType.equals("text")) {
527                                    if (Validator.isNull(contentLocale)) {
528                                            document.addText(
529                                                    name, StringUtil.merge(value, StringPool.SPACE));
530                                    }
531                                    else {
532                                            if (defaultLocale.equals(contentLocale)) {
533                                                    document.addText(
534                                                            name, StringUtil.merge(value, StringPool.SPACE));
535                                            }
536    
537                                            document.addText(
538                                                    name.concat(StringPool.UNDERLINE).concat(contentLocale),
539                                                    StringUtil.merge(value, StringPool.SPACE));
540                                    }
541                            }
542                    }
543            }
544    
545            protected void processStructure(
546                            com.liferay.portal.kernel.xml.Document structureDocument,
547                            Document document, Element rootElement)
548                    throws Exception {
549    
550                    LinkedList<Element> queue = new LinkedList<Element>(
551                            rootElement.elements());
552    
553                    Element element = null;
554    
555                    while ((element = queue.poll()) != null) {
556                            String elName = element.attributeValue("name", StringPool.BLANK);
557                            String elType = element.attributeValue("type", StringPool.BLANK);
558                            String elIndexType = element.attributeValue(
559                                    "index-type", StringPool.BLANK);
560    
561                            if (structureDocument != null) {
562                                    String path = element.getPath();
563    
564                                    path = path.concat("[@name=").concat(
565                                            HtmlUtil.escapeXPathAttribute(elName)).concat("]");
566    
567                                    Node structureNode = structureDocument.selectSingleNode(path);
568    
569                                    if (structureNode != null) {
570                                            Element structureElement = (Element)structureNode;
571    
572                                            elType = structureElement.attributeValue(
573                                                    "type", StringPool.BLANK);
574                                            elIndexType = structureElement.attributeValue(
575                                                    "index-type", StringPool.BLANK);
576                                    }
577                            }
578    
579                            if (Validator.isNotNull(elType)) {
580                                    indexField(document, element, elType, elIndexType);
581                            }
582    
583                            queue.addAll(element.elements());
584                    }
585            }
586    
587            protected void processStructure(
588                    JournalStructure structure, Document document, String content) {
589    
590                    try {
591                            com.liferay.portal.kernel.xml.Document structureDocument = null;
592    
593                            if (structure != null) {
594                                    structureDocument = SAXReaderUtil.read(structure.getXsd());
595                            }
596    
597                            com.liferay.portal.kernel.xml.Document contentDocument =
598                                    SAXReaderUtil.read(content);
599    
600                            Element rootElement = contentDocument.getRootElement();
601    
602                            processStructure(structureDocument, document, rootElement);
603                    }
604                    catch (Exception e) {
605                            _log.error(e, e);
606                    }
607            }
608    
609            protected void reindexArticles(long companyId)
610                    throws PortalException, SystemException {
611    
612                    final Collection<Document> documents = new ArrayList<Document>();
613    
614                    ActionableDynamicQuery actionableDynamicQuery =
615                            new JournalArticleActionableDynamicQuery() {
616    
617                            @Override
618                            protected void addCriteria(DynamicQuery dynamicQuery) {
619                                    Junction junction = RestrictionsFactoryUtil.disjunction();
620    
621                                    Junction approvedArticlesJunction =
622                                            RestrictionsFactoryUtil.conjunction();
623    
624                                    Property statusProperty = PropertyFactoryUtil.forName("status");
625    
626                                    approvedArticlesJunction.add(
627                                            statusProperty.eq(WorkflowConstants.STATUS_APPROVED));
628    
629                                    junction.add(approvedArticlesJunction);
630    
631                                    Junction draftArticlesJunction =
632                                            RestrictionsFactoryUtil.conjunction();
633    
634                                    Property versionProperty = PropertyFactoryUtil.forName(
635                                            "version");
636    
637                                    draftArticlesJunction.add(
638                                            versionProperty.eq(
639                                                    JournalArticleConstants.VERSION_DEFAULT));
640    
641                                    draftArticlesJunction.add(
642                                            statusProperty.eq(WorkflowConstants.STATUS_DRAFT));
643    
644                                    junction.add(draftArticlesJunction);
645    
646                                    dynamicQuery.add(junction);
647    
648                                    Property indexableProperty = PropertyFactoryUtil.forName(
649                                            "indexable");
650    
651                                    dynamicQuery.add(indexableProperty.eq(true));
652                            }
653    
654                            @Override
655                            protected void performAction(Object object)
656                                    throws PortalException, SystemException {
657    
658                                    JournalArticle article = (JournalArticle)object;
659    
660                                    if (article.isApproved()) {
661                                            JournalArticle latestArticle =
662                                                    JournalArticleLocalServiceUtil.getLatestArticle(
663                                                            article.getResourcePrimKey(),
664                                                            WorkflowConstants.STATUS_APPROVED);
665    
666                                            if (!latestArticle.isIndexable()) {
667                                                    return;
668                                            }
669                                    }
670    
671                                    Document document = getDocument(article);
672    
673                                    documents.add(document);
674                            }
675    
676                    };
677    
678                    actionableDynamicQuery.setCompanyId(companyId);
679    
680                    actionableDynamicQuery.performActions();
681    
682                    SearchEngineUtil.updateDocuments(
683                            getSearchEngineId(), companyId, documents);
684            }
685    
686            private static final String _FIELD_NAMESPACE = "web_content";
687    
688            private static Log _log = LogFactoryUtil.getLog(JournalIndexer.class);
689    
690    }