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