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