1   /**
2    * Copyright (c) 2000-2008 Liferay, Inc. All rights reserved.
3    *
4    * Permission is hereby granted, free of charge, to any person obtaining a copy
5    * of this software and associated documentation files (the "Software"), to deal
6    * in the Software without restriction, including without limitation the rights
7    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    * copies of the Software, and to permit persons to whom the Software is
9    * furnished to do so, subject to the following conditions:
10   *
11   * The above copyright notice and this permission notice shall be included in
12   * all copies or substantial portions of the Software.
13   *
14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20   * SOFTWARE.
21   */
22  
23  package com.liferay.portlet.blogs.service.impl;
24  
25  import com.liferay.portal.PortalException;
26  import com.liferay.portal.SystemException;
27  import com.liferay.portal.kernel.search.Hits;
28  import com.liferay.portal.kernel.util.ArrayUtil;
29  import com.liferay.portal.kernel.util.ContentTypes;
30  import com.liferay.portal.kernel.util.GetterUtil;
31  import com.liferay.portal.kernel.util.HttpUtil;
32  import com.liferay.portal.kernel.util.OrderByComparator;
33  import com.liferay.portal.kernel.util.StringMaker;
34  import com.liferay.portal.kernel.util.Validator;
35  import com.liferay.portal.lucene.LuceneFields;
36  import com.liferay.portal.lucene.LuceneUtil;
37  import com.liferay.portal.model.Group;
38  import com.liferay.portal.model.ResourceConstants;
39  import com.liferay.portal.model.User;
40  import com.liferay.portal.theme.ThemeDisplay;
41  import com.liferay.portal.util.PortalUtil;
42  import com.liferay.portlet.blogs.EntryContentException;
43  import com.liferay.portlet.blogs.EntryDisplayDateException;
44  import com.liferay.portlet.blogs.EntryTitleException;
45  import com.liferay.portlet.blogs.model.BlogsEntry;
46  import com.liferay.portlet.blogs.model.BlogsStatsUser;
47  import com.liferay.portlet.blogs.service.base.BlogsEntryLocalServiceBaseImpl;
48  import com.liferay.portlet.blogs.util.Indexer;
49  import com.liferay.util.Normalizer;
50  import com.liferay.util.lucene.HitsImpl;
51  
52  import java.io.IOException;
53  
54  import java.util.Date;
55  import java.util.List;
56  
57  import org.apache.commons.logging.Log;
58  import org.apache.commons.logging.LogFactory;
59  import org.apache.lucene.document.Document;
60  import org.apache.lucene.index.IndexWriter;
61  import org.apache.lucene.search.BooleanClause;
62  import org.apache.lucene.search.BooleanQuery;
63  import org.apache.lucene.search.Searcher;
64  
65  /**
66   * <a href="BlogsEntryLocalServiceImpl.java.html"><b><i>View Source</i></b>
67   * </a>
68   *
69   * @author Brian Wing Shun Chan
70   * @author Wilson S. Man
71   *
72   */
73  public class BlogsEntryLocalServiceImpl extends BlogsEntryLocalServiceBaseImpl {
74  
75      public BlogsEntry addEntry(
76              long userId, long plid, String title, String content,
77              int displayDateMonth, int displayDateDay, int displayDateYear,
78              int displayDateHour, int displayDateMinute, String[] tagsEntries,
79              boolean addCommunityPermissions, boolean addGuestPermissions,
80              ThemeDisplay themeDisplay)
81          throws PortalException, SystemException {
82  
83          return addEntry(
84              null, userId, plid, title, content, displayDateMonth,
85              displayDateDay, displayDateYear, displayDateHour, displayDateMinute,
86              tagsEntries, Boolean.valueOf(addCommunityPermissions),
87              Boolean.valueOf(addGuestPermissions), null, null, themeDisplay);
88      }
89  
90      public BlogsEntry addEntry(
91              String uuid, long userId, long plid, String title, String content,
92              int displayDateMonth, int displayDateDay, int displayDateYear,
93              int displayDateHour, int displayDateMinute, String[] tagsEntries,
94              boolean addCommunityPermissions, boolean addGuestPermissions,
95              ThemeDisplay themeDisplay)
96          throws PortalException, SystemException {
97  
98          return addEntry(
99              uuid, userId, plid, title, content, displayDateMonth,
100             displayDateDay, displayDateYear, displayDateHour, displayDateMinute,
101             tagsEntries, Boolean.valueOf(addCommunityPermissions),
102             Boolean.valueOf(addGuestPermissions), null, null, themeDisplay);
103     }
104 
105     public BlogsEntry addEntry(
106             long userId, long plid, String title, String content,
107             int displayDateMonth, int displayDateDay, int displayDateYear,
108             int displayDateHour, int displayDateMinute, String[] tagsEntries,
109             String[] communityPermissions, String[] guestPermissions,
110             ThemeDisplay themeDisplay)
111         throws PortalException, SystemException {
112 
113         return addEntry(
114             null, userId, plid, title, content, displayDateMonth,
115             displayDateDay, displayDateYear, displayDateHour, displayDateMinute,
116             tagsEntries, null, null, communityPermissions, guestPermissions,
117             themeDisplay);
118     }
119 
120     public BlogsEntry addEntry(
121             String uuid, long userId, long plid, String title, String content,
122             int displayDateMonth, int displayDateDay, int displayDateYear,
123             int displayDateHour, int displayDateMinute, String[] tagsEntries,
124             Boolean addCommunityPermissions, Boolean addGuestPermissions,
125             String[] communityPermissions, String[] guestPermissions,
126             ThemeDisplay themeDisplay)
127         throws PortalException, SystemException {
128 
129         // Entry
130 
131         User user = userPersistence.findByPrimaryKey(userId);
132         long groupId = PortalUtil.getPortletGroupId(plid);
133 
134         Date displayDate = PortalUtil.getDate(
135             displayDateMonth, displayDateDay, displayDateYear, displayDateHour,
136             displayDateMinute, user.getTimeZone(),
137             new EntryDisplayDateException());
138 
139         Date now = new Date();
140 
141         validate(title, content);
142 
143         long entryId = counterLocalService.increment();
144 
145         BlogsEntry entry = blogsEntryPersistence.create(entryId);
146 
147         entry.setUuid(uuid);
148         entry.setGroupId(groupId);
149         entry.setCompanyId(user.getCompanyId());
150         entry.setUserId(user.getUserId());
151         entry.setUserName(user.getFullName());
152         entry.setCreateDate(now);
153         entry.setModifiedDate(now);
154         entry.setTitle(title);
155         entry.setUrlTitle(getUniqueUrlTitle(entryId, groupId, title));
156         entry.setContent(content);
157         entry.setDisplayDate(displayDate);
158 
159         blogsEntryPersistence.update(entry, false);
160 
161         // Resources
162 
163         if ((addCommunityPermissions != null) &&
164             (addGuestPermissions != null)) {
165 
166             addEntryResources(
167                 entry, addCommunityPermissions.booleanValue(),
168                 addGuestPermissions.booleanValue());
169         }
170         else {
171             addEntryResources(entry, communityPermissions, guestPermissions);
172         }
173 
174         // Statistics
175 
176         blogsStatsUserLocalService.updateStatsUser(
177             entry.getGroupId(), userId, now);
178 
179         // Tags
180 
181         updateTagsAsset(userId, entry, tagsEntries);
182 
183         // Lucene
184 
185         try {
186             Indexer.addEntry(
187                 entry.getCompanyId(), entry.getGroupId(), userId, entryId,
188                 title, content, tagsEntries);
189         }
190         catch (IOException ioe) {
191             _log.error("Indexing " + entryId, ioe);
192         }
193 
194         // Google
195 
196         pingGoogle(entry, themeDisplay);
197 
198         return entry;
199     }
200 
201     public void addEntryResources(
202             long entryId, boolean addCommunityPermissions,
203             boolean addGuestPermissions)
204         throws PortalException, SystemException {
205 
206         BlogsEntry entry = blogsEntryPersistence.findByPrimaryKey(entryId);
207 
208         addEntryResources(entry, addCommunityPermissions, addGuestPermissions);
209     }
210 
211     public void addEntryResources(
212             BlogsEntry entry, boolean addCommunityPermissions,
213             boolean addGuestPermissions)
214         throws PortalException, SystemException {
215 
216         resourceLocalService.addResources(
217             entry.getCompanyId(), entry.getGroupId(), entry.getUserId(),
218             BlogsEntry.class.getName(), entry.getEntryId(), false,
219             addCommunityPermissions, addGuestPermissions);
220     }
221 
222     public void addEntryResources(
223             long entryId, String[] communityPermissions,
224             String[] guestPermissions)
225         throws PortalException, SystemException {
226 
227         BlogsEntry entry = blogsEntryPersistence.findByPrimaryKey(entryId);
228 
229         addEntryResources(entry, communityPermissions, guestPermissions);
230     }
231 
232     public void addEntryResources(
233             BlogsEntry entry, String[] communityPermissions,
234             String[] guestPermissions)
235         throws PortalException, SystemException {
236 
237         resourceLocalService.addModelResources(
238             entry.getCompanyId(), entry.getGroupId(), entry.getUserId(),
239             BlogsEntry.class.getName(), entry.getEntryId(),
240             communityPermissions, guestPermissions);
241     }
242 
243     public void deleteEntries(long groupId)
244         throws PortalException, SystemException {
245 
246         for (BlogsEntry entry : blogsEntryPersistence.findByGroupId(groupId)) {
247             deleteEntry(entry);
248         }
249     }
250 
251     public void deleteEntry(long entryId)
252         throws PortalException, SystemException {
253 
254         BlogsEntry entry = blogsEntryPersistence.findByPrimaryKey(entryId);
255 
256         deleteEntry(entry);
257     }
258 
259     public void deleteEntry(BlogsEntry entry)
260         throws PortalException, SystemException {
261 
262         // Lucene
263 
264         try {
265             Indexer.deleteEntry(entry.getCompanyId(), entry.getEntryId());
266         }
267         catch (IOException ioe) {
268             _log.error("Deleting index " + entry.getEntryId(), ioe);
269         }
270 
271         // Tags
272 
273         tagsAssetLocalService.deleteAsset(
274             BlogsEntry.class.getName(), entry.getEntryId());
275 
276         // Ratings
277 
278         ratingsStatsLocalService.deleteStats(
279             BlogsEntry.class.getName(), entry.getEntryId());
280 
281         // Message boards
282 
283         mbMessageLocalService.deleteDiscussionMessages(
284             BlogsEntry.class.getName(), entry.getEntryId());
285 
286         // Resources
287 
288         resourceLocalService.deleteResource(
289             entry.getCompanyId(), BlogsEntry.class.getName(),
290             ResourceConstants.SCOPE_INDIVIDUAL, entry.getEntryId());
291 
292         // Entry
293 
294         blogsEntryPersistence.remove(entry.getEntryId());
295     }
296 
297     public List<BlogsEntry> getCompanyEntries(
298             long companyId, int begin, int end)
299         throws SystemException {
300 
301         return blogsEntryPersistence.findByCompanyId(companyId, begin, end);
302     }
303 
304     public List<BlogsEntry> getCompanyEntries(
305             long companyId, int begin, int end, OrderByComparator obc)
306         throws SystemException {
307 
308         return blogsEntryPersistence.findByCompanyId(
309             companyId, begin, end, obc);
310     }
311 
312     public int getCompanyEntriesCount(long companyId) throws SystemException {
313         return blogsEntryPersistence.countByCompanyId(companyId);
314     }
315 
316     public BlogsEntry getEntry(long entryId)
317         throws PortalException, SystemException {
318 
319         return blogsEntryPersistence.findByPrimaryKey(entryId);
320     }
321 
322     public BlogsEntry getEntry(long groupId, String urlTitle)
323         throws PortalException, SystemException {
324 
325         return blogsEntryPersistence.findByG_UT(groupId, urlTitle);
326     }
327 
328     public List<BlogsEntry> getGroupEntries(long groupId, int begin, int end)
329         throws SystemException {
330 
331         return blogsEntryPersistence.findByGroupId(groupId, begin, end);
332     }
333 
334     public List<BlogsEntry> getGroupEntries(
335             long groupId, int begin, int end, OrderByComparator obc)
336         throws SystemException {
337 
338         return blogsEntryPersistence.findByGroupId(groupId, begin, end, obc);
339     }
340 
341     public int getGroupEntriesCount(long groupId) throws SystemException {
342         return blogsEntryPersistence.countByGroupId(groupId);
343     }
344 
345     public List<BlogsEntry> getGroupUserEntries(
346             long groupId, long userId, int begin, int end)
347         throws SystemException {
348 
349         return blogsEntryPersistence.findByG_U(groupId, userId, begin, end);
350     }
351 
352     public int getGroupUserEntriesCount(long groupId, long userId)
353         throws SystemException {
354 
355         return blogsEntryPersistence.countByG_U(groupId, userId);
356     }
357 
358     public List<BlogsEntry> getNoAssetEntries() throws SystemException {
359         return blogsEntryFinder.findByNoAssets();
360     }
361 
362     public List<BlogsEntry> getOrganizationEntries(
363             long organizationId, int begin, int end)
364         throws SystemException {
365 
366         return blogsEntryFinder.findByOrganizationId(
367             organizationId, begin, end);
368     }
369 
370     public int getOrganizationEntriesCount(long organizationId)
371         throws SystemException {
372 
373         return blogsEntryFinder.countByOrganizationId(
374             organizationId);
375     }
376 
377     public String getUrlTitle(long entryId, String title) {
378         String urlTitle = String.valueOf(entryId);
379 
380         title = title.trim().toLowerCase();
381 
382         if (Validator.isNull(title) || Validator.isNumber(title) ||
383             title.equals("rss")) {
384 
385             return urlTitle;
386         }
387 
388         title = Normalizer.normalizeToAscii(title);
389 
390         char[] urlTitleCharArray = title.toCharArray();
391 
392         for (int i = 0; i < urlTitleCharArray.length; i++) {
393             char oldChar = urlTitleCharArray[i];
394 
395             char newChar = oldChar;
396 
397             if ((oldChar == '_') || (Validator.isChar(oldChar)) ||
398                 (Validator.isDigit(oldChar))) {
399 
400             }
401             else if (ArrayUtil.contains(_URL_TITLE_REPLACE_CHARS, oldChar)) {
402                 newChar = '_';
403             }
404             else {
405                 return urlTitle;
406             }
407 
408             if (oldChar != newChar) {
409                 urlTitleCharArray[i] = newChar;
410             }
411         }
412 
413         urlTitle = new String(urlTitleCharArray);
414 
415         return urlTitle;
416     }
417 
418     public void reIndex(String[] ids) throws SystemException {
419         if (LuceneUtil.INDEX_READ_ONLY) {
420             return;
421         }
422 
423         long companyId = GetterUtil.getLong(ids[0]);
424 
425         IndexWriter writer = null;
426 
427         try {
428             writer = LuceneUtil.getWriter(companyId);
429 
430             for (BlogsEntry entry :
431                     blogsEntryPersistence.findByCompanyId(companyId)) {
432 
433                 long groupId = entry.getGroupId();
434                 long userId = entry.getUserId();
435                 long entryId = entry.getEntryId();
436                 String title = entry.getTitle();
437                 String content = entry.getContent();
438 
439                 String[] tagsEntries = tagsEntryLocalService.getEntryNames(
440                     BlogsEntry.class.getName(), entryId);
441 
442                 try {
443                     Document doc = Indexer.getAddEntryDocument(
444                         companyId, groupId, userId, entryId, title, content,
445                         tagsEntries);
446 
447                     writer.addDocument(doc);
448                 }
449                 catch (Exception e1) {
450                     _log.error("Reindexing " + entryId, e1);
451                 }
452             }
453         }
454         catch (SystemException se) {
455             throw se;
456         }
457         catch (Exception e2) {
458             throw new SystemException(e2);
459         }
460         finally {
461             try {
462                 if (writer != null) {
463                     LuceneUtil.write(companyId);
464                 }
465             }
466             catch (Exception e) {
467                 _log.error(e);
468             }
469         }
470     }
471 
472     public Hits search(
473             long companyId, long groupId, long userId, String keywords)
474         throws SystemException {
475 
476         Searcher searcher = null;
477 
478         try {
479             HitsImpl hits = new HitsImpl();
480 
481             BooleanQuery contextQuery = new BooleanQuery();
482 
483             LuceneUtil.addRequiredTerm(
484                 contextQuery, LuceneFields.PORTLET_ID, Indexer.PORTLET_ID);
485 
486             if (groupId > 0) {
487                 LuceneUtil.addRequiredTerm(
488                     contextQuery, LuceneFields.GROUP_ID, groupId);
489             }
490 
491             if (userId > 0) {
492                 LuceneUtil.addRequiredTerm(
493                     contextQuery, LuceneFields.USER_ID, userId);
494             }
495 
496             BooleanQuery searchQuery = new BooleanQuery();
497 
498             if (Validator.isNotNull(keywords)) {
499                 LuceneUtil.addTerm(searchQuery, LuceneFields.TITLE, keywords);
500                 LuceneUtil.addTerm(searchQuery, LuceneFields.CONTENT, keywords);
501                 LuceneUtil.addTerm(
502                     searchQuery, LuceneFields.TAG_ENTRY, keywords);
503             }
504 
505             BooleanQuery fullQuery = new BooleanQuery();
506 
507             fullQuery.add(contextQuery, BooleanClause.Occur.MUST);
508 
509             if (searchQuery.clauses().size() > 0) {
510                 fullQuery.add(searchQuery, BooleanClause.Occur.MUST);
511             }
512 
513             searcher = LuceneUtil.getSearcher(companyId);
514 
515             hits.recordHits(searcher.search(fullQuery), searcher);
516 
517             return hits;
518         }
519         catch (Exception e) {
520             return LuceneUtil.closeSearcher(searcher, keywords, e);
521         }
522     }
523 
524     public BlogsEntry updateEntry(
525             long userId, long entryId, String title, String content,
526             int displayDateMonth, int displayDateDay, int displayDateYear,
527             int displayDateHour, int displayDateMinute, String[] tagsEntries,
528             ThemeDisplay themeDisplay)
529         throws PortalException, SystemException {
530 
531         // Entry
532 
533         User user = userPersistence.findByPrimaryKey(userId);
534 
535         Date displayDate = PortalUtil.getDate(
536             displayDateMonth, displayDateDay, displayDateYear, displayDateHour,
537             displayDateMinute, user.getTimeZone(),
538             new EntryDisplayDateException());
539 
540         Date now = new Date();
541 
542         validate(title, content);
543 
544         BlogsEntry entry = blogsEntryPersistence.findByPrimaryKey(entryId);
545 
546         entry.setModifiedDate(now);
547         entry.setTitle(title);
548         entry.setUrlTitle(
549             getUniqueUrlTitle(entryId, entry.getGroupId(), title));
550         entry.setContent(content);
551         entry.setDisplayDate(displayDate);
552 
553         blogsEntryPersistence.update(entry, false);
554 
555         // Statistics
556 
557         BlogsStatsUser statsUser = blogsStatsUserPersistence.fetchByG_U(
558             entry.getGroupId(), entry.getUserId());
559 
560         if (statsUser != null) {
561             statsUser.setLastPostDate(now);
562 
563             blogsStatsUserPersistence.update(statsUser, false);
564         }
565 
566         // Tags
567 
568         updateTagsAsset(userId, entry, tagsEntries);
569 
570         // Lucene
571 
572         try {
573             Indexer.updateEntry(
574                 entry.getCompanyId(), entry.getGroupId(), userId, entryId,
575                 title, content, tagsEntries);
576         }
577         catch (IOException ioe) {
578             _log.error("Indexing " + entryId, ioe);
579         }
580 
581         // Google
582 
583         pingGoogle(entry, themeDisplay);
584 
585         return entry;
586     }
587 
588     public void updateTagsAsset(
589             long userId, BlogsEntry entry, String[] tagsEntries)
590         throws PortalException, SystemException {
591 
592         tagsAssetLocalService.updateAsset(
593             userId, entry.getGroupId(), BlogsEntry.class.getName(),
594             entry.getEntryId(), tagsEntries, null, null, null, null,
595             ContentTypes.TEXT_HTML, entry.getTitle(), null, null, null, 0, 0,
596             null, false);
597     }
598 
599     protected String getUniqueUrlTitle(
600             long entryId, long groupId, String title)
601         throws SystemException {
602 
603         String urlTitle = getUrlTitle(entryId, title);
604 
605         String newUrlTitle = new String(urlTitle);
606 
607         for (int i = 1;; i++) {
608             BlogsEntry entry = blogsEntryPersistence.fetchByG_UT(
609                 groupId, newUrlTitle);
610 
611             if ((entry == null) || (entry.getEntryId() == entryId)) {
612                 break;
613             }
614             else {
615                 newUrlTitle = urlTitle + "_" + i;
616             }
617         }
618 
619         return newUrlTitle;
620     }
621 
622     protected void pingGoogle(BlogsEntry entry, ThemeDisplay themeDisplay)
623         throws PortalException, SystemException {
624 
625         if (themeDisplay == null) {
626             return;
627         }
628 
629         Group group = groupPersistence.findByPrimaryKey(entry.getGroupId());
630 
631         String portalURL = PortalUtil.getPortalURL(themeDisplay);
632 
633         if ((portalURL.indexOf("://localhost") != -1) ||
634             (portalURL.indexOf("://127.0.0.1") != -1)) {
635 
636             return;
637         }
638 
639         String layoutURL = PortalUtil.getLayoutURL(themeDisplay);
640 
641         StringMaker sm = new StringMaker();
642 
643         String name = group.getDescriptiveName();
644         //String url = portalURL + layoutURL + "/blogs/" + entry.getUrlTitle();
645         String url = portalURL + layoutURL + "/blogs";
646         String changesURL = portalURL + layoutURL + "/blogs/rss";
647 
648         sm.append("http://blogsearch.google.com/ping?name=");
649         sm.append(HttpUtil.encodeURL(name));
650         sm.append("&url=");
651         sm.append(HttpUtil.encodeURL(url));
652         sm.append("&changesURL=");
653         sm.append(HttpUtil.encodeURL(changesURL));
654 
655         String location = sm.toString();
656 
657         if (_log.isInfoEnabled()) {
658             _log.info("Pinging Google at " + location);
659         }
660 
661         try {
662             String response = HttpUtil.URLtoString(sm.toString());
663 
664             if (_log.isInfoEnabled()) {
665                 _log.info("Google ping response: " + response);
666             }
667         }
668         catch (IOException ioe) {
669             _log.error("Unable to ping Google at " + location, ioe);
670         }
671     }
672 
673     protected void validate(String title, String content)
674         throws PortalException {
675 
676         if (Validator.isNull(title)) {
677             throw new EntryTitleException();
678         }
679         else if (Validator.isNull(content)) {
680             throw new EntryContentException();
681         }
682     }
683 
684     private static final char[] _URL_TITLE_REPLACE_CHARS = new char[] {
685         ' ',  '.',  '-',  ',',  '/',  '\\',  '\'',  '\"'
686     };
687 
688     private static Log _log =
689         LogFactory.getLog(BlogsEntryLocalServiceImpl.class);
690 
691 }