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