001
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
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, "&", "&");
452 content = StringUtil.replace(content, "<", "<");
453 content = StringUtil.replace(content, ">", ">");
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 }