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.log.Log;
018    import com.liferay.portal.kernel.log.LogFactoryUtil;
019    import com.liferay.portal.kernel.search.BaseIndexer;
020    import com.liferay.portal.kernel.search.BooleanQuery;
021    import com.liferay.portal.kernel.search.Document;
022    import com.liferay.portal.kernel.search.Field;
023    import com.liferay.portal.kernel.search.Indexer;
024    import com.liferay.portal.kernel.search.SearchContext;
025    import com.liferay.portal.kernel.search.SearchEngineUtil;
026    import com.liferay.portal.kernel.search.Summary;
027    import com.liferay.portal.kernel.util.GetterUtil;
028    import com.liferay.portal.kernel.util.HtmlUtil;
029    import com.liferay.portal.kernel.util.LocaleUtil;
030    import com.liferay.portal.kernel.util.LocalizationUtil;
031    import com.liferay.portal.kernel.util.StringPool;
032    import com.liferay.portal.kernel.util.StringUtil;
033    import com.liferay.portal.kernel.util.Validator;
034    import com.liferay.portal.kernel.workflow.WorkflowConstants;
035    import com.liferay.portal.kernel.xml.Element;
036    import com.liferay.portal.kernel.xml.Node;
037    import com.liferay.portal.kernel.xml.SAXReaderUtil;
038    import com.liferay.portal.model.Group;
039    import com.liferay.portal.service.GroupLocalServiceUtil;
040    import com.liferay.portal.util.PortletKeys;
041    import com.liferay.portlet.journal.NoSuchStructureException;
042    import com.liferay.portlet.journal.model.JournalArticle;
043    import com.liferay.portlet.journal.model.JournalArticleConstants;
044    import com.liferay.portlet.journal.model.JournalStructure;
045    import com.liferay.portlet.journal.service.JournalArticleLocalServiceUtil;
046    import com.liferay.portlet.journal.service.JournalStructureLocalServiceUtil;
047    
048    import java.util.ArrayList;
049    import java.util.Collection;
050    import java.util.LinkedHashMap;
051    import java.util.LinkedList;
052    import java.util.List;
053    import java.util.Locale;
054    
055    import javax.portlet.PortletURL;
056    
057    /**
058     * @author Brian Wing Shun Chan
059     * @author Harry Mark
060     * @author Bruno Farache
061     * @author Raymond Augé
062     * @author Hugo Huijser
063     */
064    public class JournalIndexer extends BaseIndexer {
065    
066            public static final String[] CLASS_NAMES = {JournalArticle.class.getName()};
067    
068            public static final String PORTLET_ID = PortletKeys.JOURNAL;
069    
070            public String[] getClassNames() {
071                    return CLASS_NAMES;
072            }
073    
074            public String getPortletId() {
075                    return PORTLET_ID;
076            }
077    
078            @Override
079            public boolean isPermissionAware() {
080                    return _PERMISSION_AWARE;
081            }
082    
083            @Override
084            public void postProcessContextQuery(
085                            BooleanQuery contextQuery, SearchContext searchContext)
086                    throws Exception {
087    
088                    Long classNameId = (Long)searchContext.getAttribute(
089                            Field.CLASS_NAME_ID);
090    
091                    if (classNameId != null) {
092                            contextQuery.addRequiredTerm("classNameId", classNameId.toString());
093                    }
094    
095                    int status = GetterUtil.getInteger(
096                            searchContext.getAttribute(Field.STATUS),
097                            WorkflowConstants.STATUS_APPROVED);
098    
099                    if (status != WorkflowConstants.STATUS_ANY) {
100                            contextQuery.addRequiredTerm(Field.STATUS, status);
101                    }
102    
103                    String articleType = (String)searchContext.getAttribute("articleType");
104    
105                    if (Validator.isNotNull(articleType)) {
106                            contextQuery.addRequiredTerm(Field.TYPE, articleType);
107                    }
108    
109                    String structureId = (String)searchContext.getAttribute("structureId");
110    
111                    if (Validator.isNotNull(structureId)) {
112                            contextQuery.addRequiredTerm("structureId", structureId);
113                    }
114    
115                    String templateId = (String)searchContext.getAttribute("templateId");
116    
117                    if (Validator.isNotNull(templateId)) {
118                            contextQuery.addRequiredTerm("templateId", templateId);
119                    }
120            }
121    
122            @Override
123            public void postProcessSearchQuery(
124                            BooleanQuery searchQuery, SearchContext searchContext)
125                    throws Exception {
126    
127                    addSearchTerm(searchQuery, searchContext, Field.CLASS_PK, false);
128                    addSearchLocalizedTerm(
129                            searchQuery, searchContext, Field.CONTENT, false);
130                    addSearchLocalizedTerm(
131                            searchQuery, searchContext, Field.DESCRIPTION, false);
132                    addSearchTerm(searchQuery, searchContext, Field.ENTRY_CLASS_PK, false);
133                    addSearchLocalizedTerm(searchQuery, searchContext, Field.TITLE, false);
134                    addSearchTerm(searchQuery, searchContext, Field.TYPE, false);
135                    addSearchTerm(searchQuery, searchContext, Field.USER_NAME, false);
136    
137                    LinkedHashMap<String, Object> params =
138                            (LinkedHashMap<String, Object>)searchContext.getAttribute("params");
139    
140                    if (params != null) {
141                            String expandoAttributes = (String)params.get("expandoAttributes");
142    
143                            if (Validator.isNotNull(expandoAttributes)) {
144                                    addSearchExpando(searchQuery, searchContext, expandoAttributes);
145                            }
146                    }
147            }
148    
149            @Override
150            protected void doDelete(Object obj) throws Exception {
151                    JournalArticle article = (JournalArticle)obj;
152    
153                    deleteDocument(
154                            article.getCompanyId(), article.getGroupId(),
155                            article.getArticleId());
156            }
157    
158            @Override
159            protected Document doGetDocument(Object obj) throws Exception {
160                    JournalArticle article = (JournalArticle)obj;
161    
162                    Document document = getBaseModelDocument(PORTLET_ID, article);
163    
164                    document.addUID(
165                            PORTLET_ID, article.getGroupId(), article.getArticleId());
166    
167                    Locale defaultLocale = LocaleUtil.getDefault();
168    
169                    String defaultLangaugeId = LocaleUtil.toLanguageId(defaultLocale);
170    
171                    String[] languageIds = getLanguageIds(
172                            defaultLangaugeId, article.getContent());
173    
174                    for (String languageId : languageIds) {
175                            String content = extractContent(
176                                    article.getContentByLocale(languageId));
177    
178                            if (languageId.equals(defaultLangaugeId)) {
179                                    document.addText(Field.CONTENT, content);
180                            }
181    
182                            document.addText(
183                                    Field.CONTENT.concat(StringPool.UNDERLINE).concat(languageId),
184                                    content);
185                    }
186    
187                    document.addLocalizedText(
188                            Field.DESCRIPTION, article.getDescriptionMap());
189                    document.addLocalizedText(Field.TITLE, article.getTitleMap());
190                    document.addKeyword(Field.TYPE, article.getType());
191                    document.addKeyword(Field.VERSION, article.getVersion());
192    
193                    document.addKeyword("articleId", article.getArticleId());
194                    document.addDate("displayDate", article.getDisplayDate());
195                    document.addKeyword("layoutUuid", article.getLayoutUuid());
196                    document.addKeyword("structureId", article.getStructureId());
197                    document.addKeyword("templateId", article.getTemplateId());
198    
199                    JournalStructure structure = null;
200    
201                    if (Validator.isNotNull(article.getStructureId())) {
202                            try {
203                                    structure = JournalStructureLocalServiceUtil.getStructure(
204                                            article.getGroupId(), article.getStructureId());
205                            }
206                            catch (NoSuchStructureException nsse1) {
207                                    Group group = GroupLocalServiceUtil.getCompanyGroup(
208                                            article.getCompanyId());
209    
210                                    try {
211                                            structure = JournalStructureLocalServiceUtil.getStructure(
212                                                    group.getGroupId(), article.getStructureId());
213                                    }
214                                    catch (NoSuchStructureException nsse2) {
215                                    }
216                            }
217                    }
218    
219                    processStructure(structure, document, article.getContent());
220    
221                    return document;
222            }
223    
224            @Override
225            protected String doGetSortField(String orderByCol) {
226                    if (orderByCol.equals("display-date")) {
227                            return "displayDate";
228                    }
229                    else if (orderByCol.equals("id")) {
230                            return Field.ENTRY_CLASS_PK;
231                    }
232                    else if (orderByCol.equals("modified-date")) {
233                            return Field.MODIFIED_DATE;
234                    }
235                    else if (orderByCol.equals("title")) {
236                            return Field.TITLE;
237                    }
238                    else {
239                            return orderByCol;
240                    }
241            }
242    
243            @Override
244            protected Summary doGetSummary(
245                    Document document, Locale locale, String snippet,
246                    PortletURL portletURL) {
247    
248                    String title = document.get(locale, Field.TITLE);
249    
250                    String content = snippet;
251    
252                    if (Validator.isNull(snippet)) {
253                            content = StringUtil.shorten(
254                                    document.get(locale, Field.CONTENT), 200);
255                    }
256    
257                    String groupId = document.get(Field.GROUP_ID);
258                    String articleId = document.get("articleId");
259                    String version = document.get(Field.VERSION);
260    
261                    portletURL.setParameter("struts_action", "/journal/edit_article");
262                    portletURL.setParameter("groupId", groupId);
263                    portletURL.setParameter("articleId", articleId);
264                    portletURL.setParameter("version", version);
265    
266                    return new Summary(title, content, portletURL);
267            }
268    
269            @Override
270            protected void doReindex(Object obj) throws Exception {
271                    JournalArticle article = (JournalArticle)obj;
272    
273                    Document document = getDocument(article);
274    
275                    if (!article.isIndexable() ||
276                            (!article.isApproved() &&
277                             (article.getVersion() !=
278                                      JournalArticleConstants.VERSION_DEFAULT))) {
279    
280                            SearchEngineUtil.deleteDocument(
281                                    article.getCompanyId(), document.get(Field.UID));
282    
283                            return;
284                    }
285    
286                    SearchEngineUtil.updateDocument(article.getCompanyId(), document);
287            }
288    
289            @Override
290            protected void doReindex(String className, long classPK) throws Exception {
291                    JournalArticle article =
292                            JournalArticleLocalServiceUtil.getLatestArticle(
293                                    classPK, WorkflowConstants.STATUS_APPROVED);
294    
295                    doReindex(article);
296            }
297    
298            @Override
299            protected void doReindex(String[] ids) throws Exception {
300                    long companyId = GetterUtil.getLong(ids[0]);
301    
302                    reindexArticles(companyId);
303            }
304    
305            protected String encodeFieldName(String name) {
306                    return _FIELD_NAMESPACE.concat(StringPool.FORWARD_SLASH).concat(name);
307            }
308    
309            protected String extractContent(String content) {
310                    content = StringUtil.replace(content, "<![CDATA[", StringPool.BLANK);
311                    content = StringUtil.replace(content, "]]>", StringPool.BLANK);
312                    content = StringUtil.replace(content, "&amp;", "&");
313                    content = StringUtil.replace(content, "&lt;", "<");
314                    content = StringUtil.replace(content, "&gt;", ">");
315    
316                    content = HtmlUtil.extractText(content);
317    
318                    return content;
319            }
320    
321            protected String[] getLanguageIds(
322                    String defaultLangaugeId, String content) {
323    
324                    String[] languageIds = LocalizationUtil.getAvailableLocales(content);
325    
326                    if (languageIds.length == 0) {
327                            languageIds = new String[] {defaultLangaugeId};
328                    }
329    
330                    return languageIds;
331            }
332    
333            @Override
334            protected String getPortletId(SearchContext searchContext) {
335                    return PORTLET_ID;
336            }
337    
338            protected void indexField(
339                    Document document, Element element, String elType, String elIndexType) {
340    
341                    if (Validator.isNull(elIndexType)) {
342                            return;
343                    }
344    
345                    com.liferay.portal.kernel.xml.Document structureDocument =
346                            element.getDocument();
347    
348                    Element rootElement = structureDocument.getRootElement();
349    
350                    String defaultLocale = GetterUtil.getString(
351                            rootElement.attributeValue("default-locale"));
352    
353                    String name = encodeFieldName(element.attributeValue("name"));
354    
355                    List<Element> dynamicContentElements = element.elements(
356                            "dynamic-content");
357    
358                    for (Element dynamicContentElement : dynamicContentElements) {
359                            String contentLocale = GetterUtil.getString(
360                                    dynamicContentElement.attributeValue("language-id"));
361    
362                            String[] value = new String[] {dynamicContentElement.getText()};
363    
364                            if (elType.equals("multi-list")) {
365                                    List<Element> optionElements = dynamicContentElement.elements(
366                                            "option");
367    
368                                    value = new String[optionElements.size()];
369    
370                                    for (int i = 0; i < optionElements.size(); i++) {
371                                            value[i] = optionElements.get(i).getText();
372                                    }
373                            }
374    
375                            if (elIndexType.equals("keyword")) {
376                                    if (Validator.isNull(contentLocale)) {
377                                            document.addKeyword(name, value);
378                                    }
379                                    else {
380                                            if (defaultLocale.equals(contentLocale)) {
381                                                    document.addKeyword(name, value);
382                                            }
383    
384                                            document.addKeyword(
385                                                    name.concat(StringPool.UNDERLINE).concat(contentLocale),
386                                                    value);
387                                    }
388                            }
389                            else if (elIndexType.equals("text")) {
390                                    if (Validator.isNull(contentLocale)) {
391                                            document.addText(
392                                                    name, StringUtil.merge(value, StringPool.SPACE));
393                                    }
394                                    else {
395                                            if (defaultLocale.equals(contentLocale)) {
396                                                    document.addText(
397                                                            name, StringUtil.merge(value, StringPool.SPACE));
398                                            }
399    
400                                            document.addText(
401                                                    name.concat(StringPool.UNDERLINE).concat(contentLocale),
402                                                    StringUtil.merge(value, StringPool.SPACE));
403                                    }
404                            }
405                    }
406            }
407    
408            protected void processStructure(
409                            com.liferay.portal.kernel.xml.Document structureDocument,
410                            Document document, Element rootElement)
411                    throws Exception {
412    
413                    LinkedList<Element> queue = new LinkedList<Element>(
414                            rootElement.elements());
415    
416                    Element element = null;
417    
418                    while ((element = queue.poll()) != null) {
419                            String elName = element.attributeValue("name", StringPool.BLANK);
420                            String elType = element.attributeValue("type", StringPool.BLANK);
421                            String elIndexType = element.attributeValue(
422                                    "index-type", StringPool.BLANK);
423    
424                            if (structureDocument != null) {
425                                    String path = element.getPath().concat(
426                                            "[@name='").concat(elName).concat("']");
427    
428                                    Node structureNode = structureDocument.selectSingleNode(path);
429    
430                                    if (structureNode != null) {
431                                            Element structureElement = (Element)structureNode;
432    
433                                            elType = structureElement.attributeValue(
434                                                    "type", StringPool.BLANK);
435                                            elIndexType = structureElement.attributeValue(
436                                                    "index-type", StringPool.BLANK);
437                                    }
438                            }
439    
440                            if (Validator.isNotNull(elType)) {
441                                    indexField(document, element, elType, elIndexType);
442                            }
443    
444                            queue.addAll(element.elements());
445                    }
446            }
447    
448            protected void processStructure(
449                    JournalStructure structure, Document document, String content) {
450    
451                    try {
452                            com.liferay.portal.kernel.xml.Document structureDocument = null;
453    
454                            if (structure != null) {
455                                    structureDocument = SAXReaderUtil.read(structure.getXsd());
456                            }
457    
458                            com.liferay.portal.kernel.xml.Document contentDocument =
459                                    SAXReaderUtil.read(content);
460    
461                            Element rootElement = contentDocument.getRootElement();
462    
463                            processStructure(structureDocument, document, rootElement);
464                    }
465                    catch (Exception e) {
466                            _log.error(e, e);
467                    }
468            }
469    
470            protected void reindexArticles(long companyId) throws Exception {
471                    int count = JournalArticleLocalServiceUtil.getCompanyArticlesCount(
472                            companyId, WorkflowConstants.STATUS_APPROVED);
473    
474                    int pages = count / Indexer.DEFAULT_INTERVAL;
475    
476                    for (int i = 0; i <= pages; i++) {
477                            int start = (i * Indexer.DEFAULT_INTERVAL);
478                            int end = start + Indexer.DEFAULT_INTERVAL;
479    
480                            reindexArticles(companyId, start, end);
481                    }
482            }
483    
484            protected void reindexArticles(long companyId, int start, int end)
485                    throws Exception {
486    
487                    List<JournalArticle> articles = new ArrayList<JournalArticle>();
488    
489                    List<JournalArticle> approvedArticles =
490                            JournalArticleLocalServiceUtil.getCompanyArticles(
491                                    companyId, WorkflowConstants.STATUS_APPROVED, start, end);
492    
493                    articles.addAll(approvedArticles);
494    
495                    List<JournalArticle> draftArticles =
496                            JournalArticleLocalServiceUtil.getCompanyArticles(
497                                    companyId, JournalArticleConstants.VERSION_DEFAULT,
498                                    WorkflowConstants.STATUS_DRAFT, start, end);
499    
500                    articles.addAll(draftArticles);
501    
502                    if (articles.isEmpty()) {
503                            return;
504                    }
505    
506                    Collection<Document> documents = new ArrayList<Document>();
507    
508                    for (JournalArticle article : articles) {
509                            if (!article.isIndexable()) {
510                                    continue;
511                            }
512    
513                            if (article.isApproved()) {
514                                    JournalArticle latestArticle =
515                                            JournalArticleLocalServiceUtil.getLatestArticle(
516                                                    article.getResourcePrimKey(),
517                                                    WorkflowConstants.STATUS_APPROVED);
518    
519                                    if (!latestArticle.isIndexable()) {
520                                            continue;
521                                    }
522                            }
523    
524                            Document document = getDocument(article);
525    
526                            documents.add(document);
527                    }
528    
529                    SearchEngineUtil.updateDocuments(companyId, documents);
530            }
531    
532            private static final String _FIELD_NAMESPACE = "web_content";
533    
534            private static final boolean _PERMISSION_AWARE = true;
535    
536            private static Log _log = LogFactoryUtil.getLog(JournalIndexer.class);
537    
538    }