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