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