001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portlet.journal.service.impl;
016    
017    import com.liferay.portal.LocaleException;
018    import com.liferay.portal.NoSuchImageException;
019    import com.liferay.portal.kernel.dao.orm.QueryDefinition;
020    import com.liferay.portal.kernel.dao.orm.QueryUtil;
021    import com.liferay.portal.kernel.exception.PortalException;
022    import com.liferay.portal.kernel.exception.SystemException;
023    import com.liferay.portal.kernel.json.JSONFactoryUtil;
024    import com.liferay.portal.kernel.json.JSONObject;
025    import com.liferay.portal.kernel.language.LanguageUtil;
026    import com.liferay.portal.kernel.lar.ExportImportThreadLocal;
027    import com.liferay.portal.kernel.log.Log;
028    import com.liferay.portal.kernel.log.LogFactoryUtil;
029    import com.liferay.portal.kernel.sanitizer.SanitizerUtil;
030    import com.liferay.portal.kernel.search.Field;
031    import com.liferay.portal.kernel.search.Hits;
032    import com.liferay.portal.kernel.search.Indexable;
033    import com.liferay.portal.kernel.search.IndexableType;
034    import com.liferay.portal.kernel.search.Indexer;
035    import com.liferay.portal.kernel.search.IndexerRegistryUtil;
036    import com.liferay.portal.kernel.search.QueryConfig;
037    import com.liferay.portal.kernel.search.SearchContext;
038    import com.liferay.portal.kernel.search.Sort;
039    import com.liferay.portal.kernel.systemevent.SystemEvent;
040    import com.liferay.portal.kernel.systemevent.SystemEventHierarchyEntryThreadLocal;
041    import com.liferay.portal.kernel.util.ArrayUtil;
042    import com.liferay.portal.kernel.util.CalendarFactoryUtil;
043    import com.liferay.portal.kernel.util.CharPool;
044    import com.liferay.portal.kernel.util.Constants;
045    import com.liferay.portal.kernel.util.ContentTypes;
046    import com.liferay.portal.kernel.util.FileUtil;
047    import com.liferay.portal.kernel.util.GetterUtil;
048    import com.liferay.portal.kernel.util.HtmlUtil;
049    import com.liferay.portal.kernel.util.HttpUtil;
050    import com.liferay.portal.kernel.util.ListUtil;
051    import com.liferay.portal.kernel.util.LocaleUtil;
052    import com.liferay.portal.kernel.util.LocalizationUtil;
053    import com.liferay.portal.kernel.util.MathUtil;
054    import com.liferay.portal.kernel.util.ObjectValuePair;
055    import com.liferay.portal.kernel.util.OrderByComparator;
056    import com.liferay.portal.kernel.util.ParamUtil;
057    import com.liferay.portal.kernel.util.PropsKeys;
058    import com.liferay.portal.kernel.util.StringPool;
059    import com.liferay.portal.kernel.util.StringUtil;
060    import com.liferay.portal.kernel.util.Time;
061    import com.liferay.portal.kernel.util.UnicodeProperties;
062    import com.liferay.portal.kernel.util.Validator;
063    import com.liferay.portal.kernel.workflow.WorkflowConstants;
064    import com.liferay.portal.kernel.workflow.WorkflowHandlerRegistryUtil;
065    import com.liferay.portal.kernel.xml.Document;
066    import com.liferay.portal.kernel.xml.DocumentException;
067    import com.liferay.portal.kernel.xml.Element;
068    import com.liferay.portal.kernel.xml.Node;
069    import com.liferay.portal.kernel.xml.SAXReaderUtil;
070    import com.liferay.portal.kernel.xml.XPath;
071    import com.liferay.portal.model.Company;
072    import com.liferay.portal.model.Group;
073    import com.liferay.portal.model.Image;
074    import com.liferay.portal.model.ResourceConstants;
075    import com.liferay.portal.model.SystemEventConstants;
076    import com.liferay.portal.model.User;
077    import com.liferay.portal.service.ServiceContext;
078    import com.liferay.portal.service.ServiceContextUtil;
079    import com.liferay.portal.servlet.filters.cache.CacheUtil;
080    import com.liferay.portal.theme.ThemeDisplay;
081    import com.liferay.portal.util.PortalUtil;
082    import com.liferay.portal.util.PortletKeys;
083    import com.liferay.portal.util.PrefsPropsUtil;
084    import com.liferay.portal.util.PropsValues;
085    import com.liferay.portal.util.SubscriptionSender;
086    import com.liferay.portal.webserver.WebServerServletTokenUtil;
087    import com.liferay.portlet.asset.model.AssetEntry;
088    import com.liferay.portlet.asset.model.AssetLink;
089    import com.liferay.portlet.asset.model.AssetLinkConstants;
090    import com.liferay.portlet.dynamicdatamapping.NoSuchStructureException;
091    import com.liferay.portlet.dynamicdatamapping.NoSuchTemplateException;
092    import com.liferay.portlet.dynamicdatamapping.model.DDMStructure;
093    import com.liferay.portlet.dynamicdatamapping.model.DDMTemplate;
094    import com.liferay.portlet.dynamicdatamapping.storage.FieldConstants;
095    import com.liferay.portlet.dynamicdatamapping.util.DDMXMLUtil;
096    import com.liferay.portlet.journal.ArticleContentException;
097    import com.liferay.portlet.journal.ArticleDisplayDateException;
098    import com.liferay.portlet.journal.ArticleExpirationDateException;
099    import com.liferay.portlet.journal.ArticleIdException;
100    import com.liferay.portlet.journal.ArticleReviewDateException;
101    import com.liferay.portlet.journal.ArticleSmallImageNameException;
102    import com.liferay.portlet.journal.ArticleSmallImageSizeException;
103    import com.liferay.portlet.journal.ArticleTitleException;
104    import com.liferay.portlet.journal.ArticleTypeException;
105    import com.liferay.portlet.journal.ArticleVersionException;
106    import com.liferay.portlet.journal.DuplicateArticleIdException;
107    import com.liferay.portlet.journal.NoSuchArticleException;
108    import com.liferay.portlet.journal.StructureXsdException;
109    import com.liferay.portlet.journal.model.JournalArticle;
110    import com.liferay.portlet.journal.model.JournalArticleConstants;
111    import com.liferay.portlet.journal.model.JournalArticleDisplay;
112    import com.liferay.portlet.journal.model.JournalArticleResource;
113    import com.liferay.portlet.journal.model.JournalFolder;
114    import com.liferay.portlet.journal.model.impl.JournalArticleDisplayImpl;
115    import com.liferay.portlet.journal.service.base.JournalArticleLocalServiceBaseImpl;
116    import com.liferay.portlet.journal.social.JournalActivityKeys;
117    import com.liferay.portlet.journal.util.JournalUtil;
118    import com.liferay.portlet.journal.util.comparator.ArticleIDComparator;
119    import com.liferay.portlet.journal.util.comparator.ArticleVersionComparator;
120    import com.liferay.portlet.journalcontent.util.JournalContentUtil;
121    import com.liferay.portlet.social.model.SocialActivityConstants;
122    import com.liferay.portlet.trash.model.TrashEntry;
123    import com.liferay.portlet.trash.model.TrashVersion;
124    import com.liferay.portlet.trash.util.TrashUtil;
125    
126    import java.io.File;
127    import java.io.IOException;
128    import java.io.Serializable;
129    
130    import java.util.ArrayList;
131    import java.util.Calendar;
132    import java.util.Date;
133    import java.util.HashMap;
134    import java.util.HashSet;
135    import java.util.LinkedHashMap;
136    import java.util.List;
137    import java.util.Locale;
138    import java.util.Map;
139    import java.util.Set;
140    
141    import javax.portlet.PortletPreferences;
142    
143    /**
144     * Provides the local service for accessing, adding, deleting, and updating web
145     * content articles.
146     *
147     * <p>
148     * The web content articles hold HTML content wrapped in XML. The XML lets you
149     * specify the article's default locale and available locales. Here is a content
150     * example:
151     * </p>
152     *
153     * <p>
154     * <pre>
155     * <code>
156     * &lt;?xml version='1.0' encoding='UTF-8'?&gt;
157     * &lt;root default-locale="en_US" available-locales="en_US"&gt;
158     *      &lt;static-content language-id="en_US"&gt;
159     *              &lt;![CDATA[&lt;p&gt;&lt;b&gt;&lt;i&gt;test&lt;i&gt; content&lt;b&gt;&lt;/p&gt;]]&gt;
160     *      &lt;/static-content&gt;
161     * &lt;/root&gt;
162     * </code>
163     * </pre>
164     * </p>
165     *
166     * @author Brian Wing Shun Chan
167     * @author Raymond Aug??
168     * @author Bruno Farache
169     * @author Juan Fern??ndez
170     * @author Sergio Gonz??lez
171     */
172    public class JournalArticleLocalServiceImpl
173            extends JournalArticleLocalServiceBaseImpl {
174    
175            /**
176             * Adds a web content article with additional parameters.
177             *
178             * @param  userId the primary key of the web content article's creator/owner
179             * @param  groupId the primary key of the web content article's group
180             * @param  folderId the primary key of the web content article folder
181             * @param  classNameId the primary key of the DDMStructure class if the web
182             *         content article is related to a DDM structure, the primary key of
183             *         the class name associated with the article, or {@link
184             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
185             * @param  classPK the primary key of the DDM structure, if the primary key
186             *         of the DDMStructure class is given as the
187             *         <code>classNameId</code> parameter, the primary key of the class
188             *         associated with the web content article, or <code>0</code>
189             *         otherwise
190             * @param  articleId the primary key of the web content article
191             * @param  autoArticleId whether to auto generate the web content article ID
192             * @param  version the web content article's version
193             * @param  titleMap the web content article's locales and localized titles
194             * @param  descriptionMap the web content article's locales and localized
195             *         descriptions
196             * @param  content the HTML content wrapped in XML. For more information,
197             *         see the content example in the class description for {@link
198             *         JournalArticleLocalServiceImpl}.
199             * @param  type the structure's type, if the web content article is related
200             *         to a DDM structure. For more information, see {@link
201             *         com.liferay.portlet.dynamicdatamapping.model.DDMStructureConstants}.
202             * @param  ddmStructureKey the primary key of the web content article's DDM
203             *         structure, if the article is related to a DDM structure, or
204             *         <code>null</code> otherwise
205             * @param  ddmTemplateKey the primary key of the web content article's DDM
206             *         template (optionally <code>null</code>). If the article is
207             *         related to a DDM structure, the template's structure must match
208             *         it.
209             * @param  layoutUuid the unique string identifying the web content
210             *         article's display page
211             * @param  displayDateMonth the month the web content article is set to
212             *         display
213             * @param  displayDateDay the calendar day the web content article is set to
214             *         display
215             * @param  displayDateYear the year the web content article is set to
216             *         display
217             * @param  displayDateHour the hour the web content article is set to
218             *         display
219             * @param  displayDateMinute the minute the web content article is set to
220             *         display
221             * @param  expirationDateMonth the month the web content article is set to
222             *         expire
223             * @param  expirationDateDay the calendar day the web content article is set
224             *         to expire
225             * @param  expirationDateYear the year the web content article is set to
226             *         expire
227             * @param  expirationDateHour the hour the web content article is set to
228             *         expire
229             * @param  expirationDateMinute the minute the web content article is set to
230             *         expire
231             * @param  neverExpire whether the web content article is not set to auto
232             *         expire
233             * @param  reviewDateMonth the month the web content article is set for
234             *         review
235             * @param  reviewDateDay the calendar day the web content article is set for
236             *         review
237             * @param  reviewDateYear the year the web content article is set for review
238             * @param  reviewDateHour the hour the web content article is set for review
239             * @param  reviewDateMinute the minute the web content article is set for
240             *         review
241             * @param  neverReview whether the web content article is not set for review
242             * @param  indexable whether the web content article is searchable
243             * @param  smallImage whether the web content article has a small image
244             * @param  smallImageURL the web content article's small image URL
245             * @param  smallImageFile the web content article's small image file
246             * @param  images the web content's images
247             * @param  articleURL the web content article's accessible URL
248             * @param  serviceContext the service context to be applied. Can set the
249             *         UUID, creation date, modification date, expando bridge
250             *         attributes, guest permissions, group permissions, asset category
251             *         IDs, asset tag names, asset link entry IDs, the "urlTitle"
252             *         attribute, and workflow actions for the web content article. Can
253             *         also set whether to add the default guest and group permissions.
254             * @return the web content article
255             * @throws PortalException if a portal exception occurred
256             * @throws SystemException if a system exception occurred
257             */
258            @Indexable(type = IndexableType.REINDEX)
259            @Override
260            public JournalArticle addArticle(
261                            long userId, long groupId, long folderId, long classNameId,
262                            long classPK, String articleId, boolean autoArticleId,
263                            double version, Map<Locale, String> titleMap,
264                            Map<Locale, String> descriptionMap, String content, String type,
265                            String ddmStructureKey, String ddmTemplateKey, String layoutUuid,
266                            int displayDateMonth, int displayDateDay, int displayDateYear,
267                            int displayDateHour, int displayDateMinute, int expirationDateMonth,
268                            int expirationDateDay, int expirationDateYear,
269                            int expirationDateHour, int expirationDateMinute,
270                            boolean neverExpire, int reviewDateMonth, int reviewDateDay,
271                            int reviewDateYear, int reviewDateHour, int reviewDateMinute,
272                            boolean neverReview, boolean indexable, boolean smallImage,
273                            String smallImageURL, File smallImageFile,
274                            Map<String, byte[]> images, String articleURL,
275                            ServiceContext serviceContext)
276                    throws PortalException, SystemException {
277    
278                    // Article
279    
280                    User user = userPersistence.findByPrimaryKey(userId);
281                    articleId = StringUtil.toUpperCase(articleId.trim());
282    
283                    Date displayDate = null;
284                    Date expirationDate = null;
285                    Date reviewDate = null;
286    
287                    if (classNameId == JournalArticleConstants.CLASSNAME_ID_DEFAULT) {
288                            displayDate = PortalUtil.getDate(
289                                    displayDateMonth, displayDateDay, displayDateYear,
290                                    displayDateHour, displayDateMinute, user.getTimeZone(),
291                                    ArticleDisplayDateException.class);
292    
293                            if (!neverExpire) {
294                                    expirationDate = PortalUtil.getDate(
295                                            expirationDateMonth, expirationDateDay, expirationDateYear,
296                                            expirationDateHour, expirationDateMinute,
297                                            user.getTimeZone(), ArticleExpirationDateException.class);
298                            }
299    
300                            if (!neverReview) {
301                                    reviewDate = PortalUtil.getDate(
302                                            reviewDateMonth, reviewDateDay, reviewDateYear,
303                                            reviewDateHour, reviewDateMinute, user.getTimeZone(),
304                                            ArticleReviewDateException.class);
305                            }
306                    }
307    
308                    byte[] smallImageBytes = null;
309    
310                    try {
311                            smallImageBytes = FileUtil.getBytes(smallImageFile);
312                    }
313                    catch (IOException ioe) {
314                    }
315    
316                    Date now = new Date();
317    
318                    validate(
319                            user.getCompanyId(), groupId, classNameId, articleId, autoArticleId,
320                            version, titleMap, content, type, ddmStructureKey, ddmTemplateKey,
321                            expirationDate, smallImage, smallImageURL, smallImageFile,
322                            smallImageBytes);
323    
324                    if (autoArticleId) {
325                            articleId = String.valueOf(counterLocalService.increment());
326                    }
327    
328                    long id = counterLocalService.increment();
329    
330                    long resourcePrimKey =
331                            journalArticleResourceLocalService.getArticleResourcePrimKey(
332                                    serviceContext.getUuid(), groupId, articleId);
333    
334                    JournalArticle article = journalArticlePersistence.create(id);
335    
336                    Locale locale = LocaleUtil.getSiteDefault();
337    
338                    String defaultLanguageId = ParamUtil.getString(
339                            serviceContext, "defaultLanguageId");
340    
341                    if (Validator.isNull(defaultLanguageId)) {
342                            defaultLanguageId = LocalizationUtil.getDefaultLanguageId(content);
343                    }
344    
345                    if (Validator.isNotNull(defaultLanguageId)) {
346                            locale = LocaleUtil.fromLanguageId(defaultLanguageId);
347                    }
348    
349                    String title = titleMap.get(locale);
350    
351                    content = format(
352                            user, groupId, articleId, version, false, content, ddmStructureKey,
353                            images);
354    
355                    article.setResourcePrimKey(resourcePrimKey);
356                    article.setGroupId(groupId);
357                    article.setCompanyId(user.getCompanyId());
358                    article.setUserId(user.getUserId());
359                    article.setUserName(user.getFullName());
360                    article.setCreateDate(serviceContext.getCreateDate(now));
361                    article.setModifiedDate(serviceContext.getModifiedDate(now));
362                    article.setFolderId(folderId);
363                    article.setClassNameId(classNameId);
364                    article.setClassPK(classPK);
365                    article.setTreePath(article.buildTreePath());
366                    article.setArticleId(articleId);
367                    article.setVersion(version);
368                    article.setTitleMap(titleMap, locale);
369                    article.setUrlTitle(
370                            getUniqueUrlTitle(id, articleId, title, null, serviceContext));
371                    article.setDescriptionMap(descriptionMap, locale);
372                    article.setContent(content);
373                    article.setType(type);
374                    article.setStructureId(ddmStructureKey);
375                    article.setTemplateId(ddmTemplateKey);
376                    article.setLayoutUuid(layoutUuid);
377                    article.setDisplayDate(displayDate);
378                    article.setExpirationDate(expirationDate);
379                    article.setReviewDate(reviewDate);
380                    article.setIndexable(indexable);
381                    article.setSmallImage(smallImage);
382                    article.setSmallImageId(counterLocalService.increment());
383                    article.setSmallImageURL(smallImageURL);
384    
385                    if ((expirationDate == null) || expirationDate.after(now)) {
386                            article.setStatus(WorkflowConstants.STATUS_DRAFT);
387                    }
388                    else {
389                            article.setStatus(WorkflowConstants.STATUS_EXPIRED);
390                    }
391    
392                    article.setStatusByUserId(userId);
393                    article.setStatusDate(serviceContext.getModifiedDate(now));
394                    article.setExpandoBridgeAttributes(serviceContext);
395    
396                    journalArticlePersistence.update(article);
397    
398                    // Resources
399    
400                    if (serviceContext.isAddGroupPermissions() ||
401                            serviceContext.isAddGuestPermissions()) {
402    
403                            addArticleResources(
404                                    article, serviceContext.isAddGroupPermissions(),
405                                    serviceContext.isAddGuestPermissions());
406                    }
407                    else {
408                            addArticleResources(
409                                    article, serviceContext.getGroupPermissions(),
410                                    serviceContext.getGuestPermissions());
411                    }
412    
413                    // Small image
414    
415                    saveImages(
416                            smallImage, article.getSmallImageId(), smallImageFile,
417                            smallImageBytes);
418    
419                    // Asset
420    
421                    updateAsset(
422                            userId, article, serviceContext.getAssetCategoryIds(),
423                            serviceContext.getAssetTagNames(),
424                            serviceContext.getAssetLinkEntryIds());
425    
426                    // Dynamic data mapping
427    
428                    if (PortalUtil.getClassNameId(DDMStructure.class) == classNameId) {
429                            updateDDMStructureXSD(classPK, content, serviceContext);
430                    }
431    
432                    // Message boards
433    
434                    if (PropsValues.JOURNAL_ARTICLE_COMMENTS_ENABLED) {
435                            mbMessageLocalService.addDiscussionMessage(
436                                    userId, article.getUserName(), groupId,
437                                    JournalArticle.class.getName(), resourcePrimKey,
438                                    WorkflowConstants.ACTION_PUBLISH);
439                    }
440    
441                    // Email
442    
443                    PortletPreferences preferences =
444                            ServiceContextUtil.getPortletPreferences(serviceContext);
445    
446                    sendEmail(
447                            article, articleURL, preferences, "requested", serviceContext);
448    
449                    // Workflow
450    
451                    if (classNameId == JournalArticleConstants.CLASSNAME_ID_DEFAULT) {
452                            WorkflowHandlerRegistryUtil.startWorkflowInstance(
453                                    user.getCompanyId(), groupId, userId,
454                                    JournalArticle.class.getName(), article.getId(), article,
455                                    serviceContext);
456                    }
457                    else {
458                            updateStatus(
459                                    userId, article, WorkflowConstants.STATUS_APPROVED, null,
460                                    new HashMap<String, Serializable>(), serviceContext);
461                    }
462    
463                    return journalArticlePersistence.findByPrimaryKey(article.getId());
464            }
465    
466            /**
467             * Adds a web content article.
468             *
469             * @param  userId the primary key of the web content article's creator/owner
470             * @param  groupId the primary key of the web content article's group
471             * @param  folderId the primary key of the web content article folder
472             * @param  titleMap the web content article's locales and localized titles
473             * @param  descriptionMap the web content article's locales and localized
474             *         descriptions
475             * @param  content the HTML content wrapped in XML. For more information,
476             *         see the content example in the class description for {@link
477             *         JournalArticleLocalServiceImpl}.
478             * @param  ddmStructureKey the primary key of the web content article's DDM
479             *         structure, if the article is related to a DDM structure, or
480             *         <code>null</code> otherwise
481             * @param  ddmTemplateKey the primary key of the web content article's DDM
482             *         template (optionally <code>null</code>). If the article is
483             *         related to a DDM structure, the template's structure must match
484             *         it.
485             * @param  serviceContext the service context to be applied. Can set the
486             *         UUID, creation date, modification date, expando bridge
487             *         attributes, guest permissions, group permissions, asset category
488             *         IDs, asset tag names, asset link entry IDs, the "urlTitle"
489             *         attribute, and workflow actions for the web content article. Can
490             *         also set whether to add the default guest and group permissions.
491             * @return the web content article
492             * @throws PortalException if a portal exception occurred
493             * @throws SystemException if a system exception occurred
494             */
495            @Override
496            public JournalArticle addArticle(
497                            long userId, long groupId, long folderId,
498                            Map<Locale, String> titleMap, Map<Locale, String> descriptionMap,
499                            String content, String ddmStructureKey, String ddmTemplateKey,
500                            ServiceContext serviceContext)
501                    throws PortalException, SystemException {
502    
503                    Calendar calendar = CalendarFactoryUtil.getCalendar();
504    
505                    int displayDateMonth = calendar.get(Calendar.MONTH);
506                    int displayDateDay = calendar.get(Calendar.DAY_OF_MONTH);
507                    int displayDateYear = calendar.get(Calendar.YEAR);
508                    int displayDateHour = calendar.get(Calendar.HOUR_OF_DAY);
509                    int displayDateMinute = calendar.get(Calendar.MINUTE);
510    
511                    return journalArticleLocalService.addArticle(
512                            userId, groupId, folderId,
513                            JournalArticleConstants.CLASSNAME_ID_DEFAULT, 0, StringPool.BLANK,
514                            true, 1, titleMap, descriptionMap, content, "general",
515                            ddmStructureKey, ddmTemplateKey, null, displayDateMonth,
516                            displayDateDay, displayDateYear, displayDateHour, displayDateMinute,
517                            0, 0, 0, 0, 0, true, 0, 0, 0, 0, 0, true, true, false, null, null,
518                            null, null, serviceContext);
519            }
520    
521            /**
522             * Adds the resources to the web content article.
523             *
524             * @param  article the web content article
525             * @param  addGroupPermissions whether to add group permissions
526             * @param  addGuestPermissions whether to add guest permissions
527             * @throws PortalException if no portal actions could be found associated
528             *         with the web content article or if a portal exception occurred
529             * @throws SystemException if a system exception occurred
530             */
531            @Override
532            public void addArticleResources(
533                            JournalArticle article, boolean addGroupPermissions,
534                            boolean addGuestPermissions)
535                    throws PortalException, SystemException {
536    
537                    resourceLocalService.addResources(
538                            article.getCompanyId(), article.getGroupId(), article.getUserId(),
539                            JournalArticle.class.getName(), article.getResourcePrimKey(), false,
540                            addGroupPermissions, addGuestPermissions);
541            }
542    
543            /**
544             * Adds the model resources with the permissions to the web content article.
545             *
546             * @param  article the web content article to add resources to
547             * @param  groupPermissions the group permissions to be added
548             * @param  guestPermissions the guest permissions to be added
549             * @throws PortalException if a portal exception occurred
550             * @throws SystemException if a system exception occurred
551             */
552            @Override
553            public void addArticleResources(
554                            JournalArticle article, String[] groupPermissions,
555                            String[] guestPermissions)
556                    throws PortalException, SystemException {
557    
558                    resourceLocalService.addModelResources(
559                            article.getCompanyId(), article.getGroupId(), article.getUserId(),
560                            JournalArticle.class.getName(), article.getResourcePrimKey(),
561                            groupPermissions, guestPermissions);
562            }
563    
564            /**
565             * Adds the resources to the most recently created web content article.
566             *
567             * @param  groupId the primary key of the web content article's group
568             * @param  articleId the primary key of the web content article
569             * @param  addGroupPermissions whether to add group permissions
570             * @param  addGuestPermissions whether to add guest permissions
571             * @throws PortalException if a portal exception occurred
572             * @throws SystemException if a system exception occurred
573             */
574            @Override
575            public void addArticleResources(
576                            long groupId, String articleId, boolean addGroupPermissions,
577                            boolean addGuestPermissions)
578                    throws PortalException, SystemException {
579    
580                    JournalArticle article = getLatestArticle(groupId, articleId);
581    
582                    addArticleResources(article, addGroupPermissions, addGuestPermissions);
583            }
584    
585            /**
586             * Adds the resources with the permissions to the most recently created web
587             * content article.
588             *
589             * @param  groupId the primary key of the web content article's group
590             * @param  articleId the primary key of the web content article
591             * @param  groupPermissions the group permissions to be added
592             * @param  guestPermissions the guest permissions to be added
593             * @throws PortalException if a portal exception occurred
594             * @throws SystemException if a system exception occurred
595             */
596            @Override
597            public void addArticleResources(
598                            long groupId, String articleId, String[] groupPermissions,
599                            String[] guestPermissions)
600                    throws PortalException, SystemException {
601    
602                    JournalArticle article = getLatestArticle(groupId, articleId);
603    
604                    addArticleResources(article, groupPermissions, guestPermissions);
605            }
606    
607            /**
608             * Returns the web content article with the group, article ID, and version.
609             * This method checks for the article's resource primary key and, if not
610             * found, creates a new one.
611             *
612             * @param  groupId the primary key of the web content article's group
613             * @param  articleId the primary key of the web content article
614             * @param  version the web content article's version
615             * @return the matching web content article
616             * @throws PortalException if a matching web content article could not be
617             *         found
618             * @throws SystemException if a system exception occurred
619             */
620            @Override
621            public JournalArticle checkArticleResourcePrimKey(
622                            long groupId, String articleId, double version)
623                    throws PortalException, SystemException {
624    
625                    JournalArticle article = journalArticlePersistence.findByG_A_V(
626                            groupId, articleId, version);
627    
628                    if (article.getResourcePrimKey() > 0) {
629                            return article;
630                    }
631    
632                    long resourcePrimKey =
633                            journalArticleResourceLocalService.getArticleResourcePrimKey(
634                                    groupId, articleId);
635    
636                    article.setResourcePrimKey(resourcePrimKey);
637    
638                    journalArticlePersistence.update(article);
639    
640                    return article;
641            }
642    
643            /**
644             * Checks all web content articles by handling their expirations and sending
645             * review notifications based on their current workflow.
646             *
647             * @throws PortalException if a portal exception occurred
648             * @throws SystemException if a system exception occurred
649             */
650            @Override
651            public void checkArticles() throws PortalException, SystemException {
652                    Date now = new Date();
653    
654                    checkArticlesByExpirationDate(now);
655    
656                    checkArticlesByReviewDate(now);
657    
658                    checkArticlesByDisplayDate(now);
659    
660                    _previousCheckDate = now;
661            }
662    
663            /**
664             * Checks the web content article matching the group, article ID, and
665             * version, replacing escaped newline and return characters with non-escaped
666             * newline and return characters.
667             *
668             * @param  groupId the primary key of the web content article's group
669             * @param  articleId the primary key of the web content article
670             * @param  version the web content article's version
671             * @throws PortalException if a matching web content article could not be
672             *         found
673             * @throws SystemException if a system exception occurred
674             */
675            @Override
676            public void checkNewLine(long groupId, String articleId, double version)
677                    throws PortalException, SystemException {
678    
679                    JournalArticle article = journalArticlePersistence.findByG_A_V(
680                            groupId, articleId, version);
681    
682                    String content = GetterUtil.getString(article.getContent());
683    
684                    if (content.contains("\\n")) {
685                            content = StringUtil.replace(
686                                    content, new String[] {"\\n", "\\r"},
687                                    new String[] {"\n", "\r"});
688    
689                            article.setContent(content);
690    
691                            journalArticlePersistence.update(article);
692                    }
693            }
694    
695            /**
696             * Checks the web content article matching the group, article ID, and
697             * version for an associated structure. If no structure is associated,
698             * return; otherwise check that the article and structure match.
699             *
700             * @param  groupId the primary key of the web content article's group
701             * @param  articleId the primary key of the web content article
702             * @param  version the web content article's version
703             * @throws PortalException if a matching web content article could not be
704             *         found, if the article's structure does not match it, or if a
705             *         portal exception occurred
706             * @throws SystemException if a system exception occurred
707             */
708            @Override
709            public void checkStructure(long groupId, String articleId, double version)
710                    throws PortalException, SystemException {
711    
712                    JournalArticle article = journalArticlePersistence.findByG_A_V(
713                            groupId, articleId, version);
714    
715                    if (Validator.isNull(article.getStructureId())) {
716                            return;
717                    }
718    
719                    checkStructure(article);
720            }
721    
722            /**
723             * Copies the web content article matching the group, article ID, and
724             * version. This method creates a new article, extracting all the values
725             * from the old one and updating its article ID.
726             *
727             * @param  userId the primary key of the web content article's creator/owner
728             * @param  groupId the primary key of the web content article's group
729             * @param  oldArticleId the primary key of the old web content article
730             * @param  newArticleId the primary key of the new web content article
731             * @param  autoArticleId whether to auto-generate the web content article ID
732             * @param  version the web content article's version
733             * @return the new web content article
734             * @throws PortalException if a matching web content article could not be
735             *         found or if a portal exception occurred
736             * @throws SystemException if a system exception occurred
737             */
738            @Indexable(type = IndexableType.REINDEX)
739            @Override
740            public JournalArticle copyArticle(
741                            long userId, long groupId, String oldArticleId, String newArticleId,
742                            boolean autoArticleId, double version)
743                    throws PortalException, SystemException {
744    
745                    // Article
746    
747                    User user = userPersistence.findByPrimaryKey(userId);
748                    oldArticleId = StringUtil.toUpperCase(oldArticleId.trim());
749                    newArticleId = StringUtil.toUpperCase(newArticleId.trim());
750                    Date now = new Date();
751    
752                    JournalArticle oldArticle = journalArticlePersistence.findByG_A_V(
753                            groupId, oldArticleId, version);
754    
755                    if (autoArticleId) {
756                            newArticleId = String.valueOf(counterLocalService.increment());
757                    }
758                    else {
759                            validate(newArticleId);
760    
761                            if (journalArticlePersistence.countByG_A(
762                                            groupId, newArticleId) > 0) {
763    
764                                    throw new DuplicateArticleIdException();
765                            }
766                    }
767    
768                    long id = counterLocalService.increment();
769    
770                    long resourcePrimKey =
771                            journalArticleResourceLocalService.getArticleResourcePrimKey(
772                                    groupId, newArticleId);
773    
774                    JournalArticle newArticle = journalArticlePersistence.create(id);
775    
776                    newArticle.setResourcePrimKey(resourcePrimKey);
777                    newArticle.setGroupId(groupId);
778                    newArticle.setCompanyId(user.getCompanyId());
779                    newArticle.setUserId(user.getUserId());
780                    newArticle.setUserName(user.getFullName());
781                    newArticle.setCreateDate(now);
782                    newArticle.setModifiedDate(now);
783                    newArticle.setFolderId(oldArticle.getFolderId());
784                    newArticle.setTreePath(oldArticle.getTreePath());
785                    newArticle.setArticleId(newArticleId);
786                    newArticle.setVersion(JournalArticleConstants.VERSION_DEFAULT);
787                    newArticle.setTitle(oldArticle.getTitle());
788                    newArticle.setUrlTitle(
789                            getUniqueUrlTitle(
790                                    id, groupId, newArticleId, oldArticle.getTitleCurrentValue()));
791                    newArticle.setDescription(oldArticle.getDescription());
792    
793                    try {
794                            copyArticleImages(oldArticle, newArticle);
795                    }
796                    catch (Exception e) {
797                            newArticle.setContent(oldArticle.getContent());
798                    }
799    
800                    newArticle.setType(oldArticle.getType());
801                    newArticle.setStructureId(oldArticle.getStructureId());
802                    newArticle.setTemplateId(oldArticle.getTemplateId());
803                    newArticle.setLayoutUuid(oldArticle.getLayoutUuid());
804                    newArticle.setDisplayDate(oldArticle.getDisplayDate());
805                    newArticle.setExpirationDate(oldArticle.getExpirationDate());
806                    newArticle.setReviewDate(oldArticle.getReviewDate());
807                    newArticle.setIndexable(oldArticle.isIndexable());
808                    newArticle.setSmallImage(oldArticle.isSmallImage());
809                    newArticle.setSmallImageId(counterLocalService.increment());
810                    newArticle.setSmallImageURL(oldArticle.getSmallImageURL());
811    
812                    if (oldArticle.isPending() ||
813                            workflowDefinitionLinkLocalService.hasWorkflowDefinitionLink(
814                                    user.getCompanyId(), groupId, JournalArticle.class.getName())) {
815    
816                            newArticle.setStatus(WorkflowConstants.STATUS_DRAFT);
817                    }
818                    else {
819                            newArticle.setStatus(oldArticle.getStatus());
820                    }
821    
822                    newArticle.setExpandoBridgeAttributes(oldArticle);
823    
824                    journalArticlePersistence.update(newArticle);
825    
826                    // Resources
827    
828                    addArticleResources(newArticle, true, true);
829    
830                    // Small image
831    
832                    if (oldArticle.getSmallImage()) {
833                            Image image = imageLocalService.getImage(
834                                    oldArticle.getSmallImageId());
835    
836                            byte[] smallImageBytes = image.getTextObj();
837    
838                            imageLocalService.updateImage(
839                                    newArticle.getSmallImageId(), smallImageBytes);
840                    }
841    
842                    // Asset
843    
844                    long[] assetCategoryIds = assetCategoryLocalService.getCategoryIds(
845                            JournalArticle.class.getName(), oldArticle.getResourcePrimKey());
846                    String[] assetTagNames = assetTagLocalService.getTagNames(
847                            JournalArticle.class.getName(), oldArticle.getResourcePrimKey());
848    
849                    updateAsset(userId, newArticle, assetCategoryIds, assetTagNames, null);
850    
851                    return newArticle;
852            }
853    
854            /**
855             * Deletes the web content article and its resources.
856             *
857             * @param  article the web content article
858             * @throws PortalException if a portal exception occurred
859             * @throws SystemException if a system exception occurred
860             */
861            @Override
862            @SystemEvent(
863                    action = SystemEventConstants.ACTION_SKIP, send = false)
864            public JournalArticle deleteArticle(JournalArticle article)
865                    throws PortalException, SystemException {
866    
867                    return journalArticleLocalService.deleteArticle(
868                            article, StringPool.BLANK, null);
869            }
870    
871            /**
872             * Deletes the web content article and its resources, optionally sending
873             * email notifying denial of the article if it had not yet been approved.
874             *
875             * @param  article the web content article
876             * @param  articleURL the web content article's accessible URL to include in
877             *         email notifications (optionally <code>null</code>)
878             * @param  serviceContext the service context to be applied (optionally
879             *         <code>null</code>). Can set the portlet preferences that include
880             *         email information to notify recipients of the unapproved web
881             *         content's denial.
882             * @throws PortalException if a portal exception occurred
883             * @throws SystemException if a system exception occurred
884             */
885            @Indexable(type = IndexableType.DELETE)
886            @Override
887            @SystemEvent(
888                    action = SystemEventConstants.ACTION_SKIP, send = false)
889            public JournalArticle deleteArticle(
890                            JournalArticle article, String articleURL,
891                            ServiceContext serviceContext)
892                    throws PortalException, SystemException {
893    
894                    JournalArticleResource articleResource =
895                            journalArticleResourceLocalService.fetchArticleResource(
896                                    article.getGroupId(), article.getArticleId());
897    
898                    if (article.isApproved() &&
899                            isLatestVersion(
900                                    article.getGroupId(), article.getArticleId(),
901                                    article.getVersion(), WorkflowConstants.STATUS_APPROVED)) {
902    
903                            updatePreviousApprovedArticle(article);
904                    }
905    
906                    // Email
907    
908                    if ((serviceContext != null) && Validator.isNotNull(articleURL)) {
909                            PortletPreferences preferences =
910                                    ServiceContextUtil.getPortletPreferences(serviceContext);
911    
912                            if ((preferences != null) && !article.isApproved() &&
913                                    isLatestVersion(
914                                            article.getGroupId(), article.getArticleId(),
915                                            article.getVersion())) {
916    
917                                    sendEmail(
918                                            article, articleURL, preferences, "denied", serviceContext);
919                            }
920                    }
921    
922                    // Images
923    
924                    journalArticleImageLocalService.deleteImages(
925                            article.getGroupId(), article.getArticleId(), article.getVersion());
926    
927                    // Expando
928    
929                    expandoRowLocalService.deleteRows(article.getId());
930    
931                    // Workflow
932    
933                    if (!article.isDraft()) {
934                            workflowInstanceLinkLocalService.deleteWorkflowInstanceLink(
935                                    article.getCompanyId(), article.getGroupId(),
936                                    JournalArticle.class.getName(), article.getId());
937                    }
938    
939                    int articlesCount = journalArticlePersistence.countByG_A(
940                            article.getGroupId(), article.getArticleId());
941    
942                    if (articlesCount == 1) {
943    
944                            // Subscriptions
945    
946                            subscriptionLocalService.deleteSubscriptions(
947                                    article.getCompanyId(), JournalArticle.class.getName(),
948                                    article.getResourcePrimKey());
949    
950                            // Ratings
951    
952                            ratingsStatsLocalService.deleteStats(
953                                    JournalArticle.class.getName(), article.getResourcePrimKey());
954    
955                            // Message boards
956    
957                            mbMessageLocalService.deleteDiscussionMessages(
958                                    JournalArticle.class.getName(), article.getResourcePrimKey());
959    
960                            // Asset
961    
962                            assetEntryLocalService.deleteEntry(
963                                    JournalArticle.class.getName(), article.getResourcePrimKey());
964    
965                            // Content searches
966    
967                            journalContentSearchLocalService.deleteArticleContentSearches(
968                                    article.getGroupId(), article.getArticleId());
969    
970                            // Small image
971    
972                            imageLocalService.deleteImage(article.getSmallImageId());
973    
974                            // Trash
975    
976                            trashEntryLocalService.deleteEntry(
977                                    JournalArticle.class.getName(), article.getResourcePrimKey());
978    
979                            // Resources
980    
981                            resourceLocalService.deleteResource(
982                                    article.getCompanyId(), JournalArticle.class.getName(),
983                                    ResourceConstants.SCOPE_INDIVIDUAL,
984                                    article.getResourcePrimKey());
985    
986                            // Resource
987    
988                            if (articleResource != null) {
989                                    journalArticleResourceLocalService.deleteJournalArticleResource(
990                                            articleResource);
991                            }
992                    }
993    
994                    // Article
995    
996                    journalArticlePersistence.remove(article);
997    
998                    // System event
999    
1000                    if (articleResource != null) {
1001                            JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();
1002    
1003                            extraDataJSONObject.put("version", article.getVersion());
1004    
1005                            systemEventLocalService.addSystemEvent(
1006                                    0, article.getGroupId(), article.getModelClassName(),
1007                                    article.getPrimaryKey(), articleResource.getUuid(), null,
1008                                    SystemEventConstants.TYPE_DELETE,
1009                                    extraDataJSONObject.toString());
1010                    }
1011    
1012                    return article;
1013            }
1014    
1015            /**
1016             * Deletes the web content article and its resources matching the group,
1017             * article ID, and version, optionally sending email notifying denial of the
1018             * web content article if it had not yet been approved.
1019             *
1020             * @param  groupId the primary key of the web content article's group
1021             * @param  articleId the primary key of the web content article
1022             * @param  version the web content article's version
1023             * @param  articleURL the web content article's accessible URL
1024             * @param  serviceContext the service context to be applied. Can set the
1025             *         portlet preferences that include email information to notify
1026             *         recipients of the unapproved web content article's denial.
1027             * @throws PortalException if a matching web content article could not be
1028             *         found or if a portal exception occurred
1029             * @throws SystemException if a system exception occurred
1030             */
1031            @Override
1032            public JournalArticle deleteArticle(
1033                            long groupId, String articleId, double version, String articleURL,
1034                            ServiceContext serviceContext)
1035                    throws PortalException, SystemException {
1036    
1037                    JournalArticle article = journalArticlePersistence.findByG_A_V(
1038                            groupId, articleId, version);
1039    
1040                    return journalArticleLocalService.deleteArticle(
1041                            article, articleURL, serviceContext);
1042            }
1043    
1044            /**
1045             * Deletes all web content articles and their resources matching the group
1046             * and article ID, optionally sending email notifying denial of article if
1047             * it had not yet been approved.
1048             *
1049             * @param  groupId the primary key of the web content article's group
1050             * @param  articleId the primary key of the web content article
1051             * @param  serviceContext the service context to be applied. Can set the
1052             *         portlet preferences that include email information to notify
1053             *         recipients of the unapproved web content article's denial.
1054             * @throws PortalException if a portal exception occurred
1055             * @throws SystemException if a system exception occurred
1056             */
1057            @Override
1058            public void deleteArticle(
1059                            long groupId, String articleId, ServiceContext serviceContext)
1060                    throws PortalException, SystemException {
1061    
1062                    SystemEventHierarchyEntryThreadLocal.push(JournalArticle.class);
1063    
1064                    JournalArticleResource articleResource =
1065                            journalArticleResourceLocalService.fetchArticleResource(
1066                                    groupId, articleId);
1067    
1068                    try {
1069                            List<JournalArticle> articles = journalArticlePersistence.findByG_A(
1070                                    groupId, articleId, QueryUtil.ALL_POS, QueryUtil.ALL_POS,
1071                                    new ArticleVersionComparator(true));
1072    
1073                            for (JournalArticle article : articles) {
1074                                    journalArticleLocalService.deleteArticle(
1075                                            article, null, serviceContext);
1076                            }
1077                    }
1078                    finally {
1079                            SystemEventHierarchyEntryThreadLocal.pop(JournalArticle.class);
1080                    }
1081    
1082                    if (articleResource != null) {
1083                            systemEventLocalService.addSystemEvent(
1084                                    0, groupId, JournalArticle.class.getName(),
1085                                    articleResource.getResourcePrimKey(), articleResource.getUuid(),
1086                                    null, SystemEventConstants.TYPE_DELETE, StringPool.BLANK);
1087                    }
1088            }
1089    
1090            /**
1091             * Deletes all the group's web content articles and resources.
1092             *
1093             * @param  groupId the primary key of the web content article's group
1094             * @throws PortalException if a portal exception occurred
1095             * @throws SystemException if a system exception occurred
1096             */
1097            @Override
1098            public void deleteArticles(long groupId)
1099                    throws PortalException, SystemException {
1100    
1101                    SystemEventHierarchyEntryThreadLocal.push(JournalArticle.class);
1102    
1103                    List<JournalArticleResource> articleResources =
1104                            new ArrayList<JournalArticleResource>();
1105    
1106                    try {
1107                            JournalArticleResource articleResource = null;
1108    
1109                            for (JournalArticle article :
1110                                            journalArticlePersistence.findByGroupId(groupId)) {
1111    
1112                                    if ((articleResource == null) ||
1113                                            (articleResource.getPrimaryKey() !=
1114                                                    article.getResourcePrimKey())) {
1115    
1116                                            articleResource =
1117                                                    journalArticleResourceLocalService.getArticleResource(
1118                                                            article.getResourcePrimKey());
1119    
1120                                            articleResources.add(articleResource);
1121                                    }
1122    
1123                                    journalArticleLocalService.deleteArticle(article, null, null);
1124                            }
1125                    }
1126                    finally {
1127                            SystemEventHierarchyEntryThreadLocal.pop(JournalArticle.class);
1128                    }
1129    
1130                    for (JournalArticleResource articleResource : articleResources) {
1131                            systemEventLocalService.addSystemEvent(
1132                                    0, groupId, JournalArticle.class.getName(),
1133                                    articleResource.getResourcePrimKey(), articleResource.getUuid(),
1134                                    null, SystemEventConstants.TYPE_DELETE, StringPool.BLANK);
1135                    }
1136            }
1137    
1138            /**
1139             * Deletes all the group's web content articles and resources in the folder,
1140             * including recycled articles.
1141             *
1142             * @param  groupId the primary key of the web content article's group
1143             * @param  folderId the primary key of the web content article folder
1144             * @throws PortalException if a portal exception occurred
1145             * @throws SystemException if a system exception occurred
1146             */
1147            @Override
1148            public void deleteArticles(long groupId, long folderId)
1149                    throws PortalException, SystemException {
1150    
1151                    deleteArticles(groupId, folderId, true);
1152            }
1153    
1154            /**
1155             * Deletes all the group's web content articles and resources in the folder,
1156             * optionally including recycled articles.
1157             *
1158             * @param  groupId the primary key of the web content article's group
1159             * @param  folderId the primary key of the web content article folder
1160             * @param  includeTrashedEntries whether to include recycled web content
1161             *         articles
1162             * @throws PortalException if a portal exception occurred
1163             * @throws SystemException if a system exception occurred
1164             */
1165            @Override
1166            public void deleteArticles(
1167                            long groupId, long folderId, boolean includeTrashedEntries)
1168                    throws PortalException, SystemException {
1169    
1170                    SystemEventHierarchyEntryThreadLocal.push(JournalArticle.class);
1171    
1172                    List<JournalArticleResource> articleResources =
1173                            new ArrayList<JournalArticleResource>();
1174    
1175                    try {
1176                            JournalArticleResource articleResource = null;
1177    
1178                            for (JournalArticle article :
1179                                            journalArticlePersistence.findByG_F(groupId, folderId)) {
1180    
1181                                    if ((articleResource == null) ||
1182                                            (articleResource.getPrimaryKey() !=
1183                                                    article.getResourcePrimKey())) {
1184    
1185                                            articleResource =
1186                                                    journalArticleResourceLocalService.getArticleResource(
1187                                                            article.getResourcePrimKey());
1188    
1189                                            articleResources.add(articleResource);
1190                                    }
1191    
1192                                    if (includeTrashedEntries || !article.isInTrash()) {
1193                                            journalArticleLocalService.deleteArticle(
1194                                                    article, null, null);
1195                                    }
1196                                    else {
1197                                            articleResources.remove(articleResource);
1198                                    }
1199                            }
1200                    }
1201                    finally {
1202                            SystemEventHierarchyEntryThreadLocal.pop(JournalArticle.class);
1203                    }
1204    
1205                    for (JournalArticleResource articleResource : articleResources) {
1206                            systemEventLocalService.addSystemEvent(
1207                                    0, groupId, JournalArticle.class.getName(),
1208                                    articleResource.getResourcePrimKey(), articleResource.getUuid(),
1209                                    null, SystemEventConstants.TYPE_DELETE, StringPool.BLANK);
1210                    }
1211            }
1212    
1213            /**
1214             * Deletes the layout's association with the web content articles for the
1215             * group.
1216             *
1217             * @param  groupId the primary key of the web content article's group
1218             * @param  layoutUuid the unique string identifying the web content
1219             *         article's display page
1220             * @throws SystemException if a system exception occurred
1221             */
1222            @Override
1223            public void deleteLayoutArticleReferences(long groupId, String layoutUuid)
1224                    throws SystemException {
1225    
1226                    List<JournalArticle> articles = journalArticlePersistence.findByG_L(
1227                            groupId, layoutUuid);
1228    
1229                    for (JournalArticle article : articles) {
1230                            article.setLayoutUuid(StringPool.BLANK);
1231    
1232                            journalArticlePersistence.update(article);
1233                    }
1234            }
1235    
1236            /**
1237             * Expires the web content article matching the group, article ID, and
1238             * version.
1239             *
1240             * @param  userId the primary key of the user updating the web content
1241             *         article
1242             * @param  groupId the primary key of the web content article's group
1243             * @param  articleId the primary key of the web content article
1244             * @param  version the web content article's version
1245             * @param  articleURL the web content article's accessible URL
1246             * @param  serviceContext the service context to be applied. Can set the
1247             *         modification date, status date, portlet preferences, and can set
1248             *         whether to add the default command update for the web content
1249             *         article. With respect to social activities, by setting the
1250             *         service context's command to {@link
1251             *         com.liferay.portal.kernel.util.Constants#UPDATE}, the invocation
1252             *         is considered a web content update activity; otherwise it is
1253             *         considered a web content add activity.
1254             * @return the web content article
1255             * @throws PortalException if a matching web content article could not be
1256             *         found or if a portal exception occurred
1257             * @throws SystemException if a system exception occurred
1258             */
1259            @Indexable(type = IndexableType.REINDEX)
1260            @Override
1261            public JournalArticle expireArticle(
1262                            long userId, long groupId, String articleId, double version,
1263                            String articleURL, ServiceContext serviceContext)
1264                    throws PortalException, SystemException {
1265    
1266                    return updateStatus(
1267                            userId, groupId, articleId, version,
1268                            WorkflowConstants.STATUS_EXPIRED, articleURL,
1269                            new HashMap<String, Serializable>(), serviceContext);
1270            }
1271    
1272            /**
1273             * Expires the web content article matching the group and article ID,
1274             * expiring all of its versions if the
1275             * <code>journal.article.expire.all.versions</code> portal property is
1276             * <code>true</code>, otherwise expiring only its latest approved version.
1277             *
1278             * @param  userId the primary key of the user updating the web content
1279             *         article
1280             * @param  groupId the primary key of the web content article's group
1281             * @param  articleId the primary key of the web content article
1282             * @param  articleURL the web content article's accessible URL
1283             * @param  serviceContext the service context to be applied. Can set the
1284             *         modification date, status date, portlet preferences, and can set
1285             *         whether to add the default command update for the web content
1286             *         article. With respect to social activities, by setting the
1287             *         service context's command to {@link
1288             *         com.liferay.portal.kernel.util.Constants#UPDATE}, the invocation
1289             *         is considered a web content update activity; otherwise it is
1290             *         considered a web content add activity.
1291             * @throws PortalException if a matching web content article could not be
1292             *         found or if a portal exception occurred
1293             * @throws SystemException if a system exception occurred
1294             */
1295            @Override
1296            public void expireArticle(
1297                            long userId, long groupId, String articleId, String articleURL,
1298                            ServiceContext serviceContext)
1299                    throws PortalException, SystemException {
1300    
1301                    if (PropsValues.JOURNAL_ARTICLE_EXPIRE_ALL_VERSIONS) {
1302                            List<JournalArticle> articles = journalArticlePersistence.findByG_A(
1303                                    groupId, articleId, QueryUtil.ALL_POS, QueryUtil.ALL_POS,
1304                                    new ArticleVersionComparator(true));
1305    
1306                            for (JournalArticle article : articles) {
1307                                    journalArticleLocalService.expireArticle(
1308                                            userId, groupId, article.getArticleId(),
1309                                            article.getVersion(), articleURL, serviceContext);
1310                            }
1311                    }
1312                    else {
1313                            JournalArticle article = getLatestArticle(
1314                                    groupId, articleId, WorkflowConstants.STATUS_APPROVED);
1315    
1316                            journalArticleLocalService.expireArticle(
1317                                    userId, groupId, article.getArticleId(), article.getVersion(),
1318                                    articleURL, serviceContext);
1319                    }
1320            }
1321    
1322            @Override
1323            public JournalArticle fetchArticle(
1324                            long groupId, String articleId, double version)
1325                    throws SystemException {
1326    
1327                    return journalArticlePersistence.fetchByG_A_V(
1328                            groupId, articleId, version);
1329            }
1330    
1331            @Override
1332            public JournalArticle fetchLatestArticle(
1333                            long resourcePrimKey, int status, boolean preferApproved)
1334                    throws SystemException {
1335    
1336                    JournalArticle article = null;
1337    
1338                    OrderByComparator orderByComparator = new ArticleVersionComparator();
1339    
1340                    if (status == WorkflowConstants.STATUS_ANY) {
1341                            if (preferApproved) {
1342                                    article = journalArticlePersistence.fetchByR_ST_First(
1343                                            resourcePrimKey, WorkflowConstants.STATUS_APPROVED,
1344                                            orderByComparator);
1345                            }
1346    
1347                            if (article == null) {
1348                                    article =
1349                                            journalArticlePersistence.fetchByResourcePrimKey_First(
1350                                                    resourcePrimKey, orderByComparator);
1351                            }
1352                    }
1353                    else {
1354                            article = journalArticlePersistence.fetchByR_ST_First(
1355                                    resourcePrimKey, status, orderByComparator);
1356                    }
1357    
1358                    return article;
1359            }
1360    
1361            @Override
1362            public JournalArticle fetchLatestArticle(
1363                            long groupId, String articleId, int status)
1364                    throws SystemException {
1365    
1366                    OrderByComparator orderByComparator = new ArticleVersionComparator();
1367    
1368                    if (status == WorkflowConstants.STATUS_ANY) {
1369                            return journalArticlePersistence.fetchByG_A_First(
1370                                    groupId, articleId, orderByComparator);
1371                    }
1372    
1373                    return journalArticlePersistence.fetchByG_A_ST_First(
1374                            groupId, articleId, status, orderByComparator);
1375            }
1376    
1377            @Override
1378            public JournalArticle fetchLatestIndexableArticle(long resourcePrimKey)
1379                    throws SystemException {
1380    
1381                    OrderByComparator orderByComparator = new ArticleVersionComparator();
1382    
1383                    int[] statuses = new int[] {
1384                            WorkflowConstants.STATUS_APPROVED, WorkflowConstants.STATUS_IN_TRASH
1385                    };
1386    
1387                    List<JournalArticle> articles =
1388                            journalArticlePersistence.findByR_I_S(
1389                                    resourcePrimKey, true, statuses, 0, 1, orderByComparator);
1390    
1391                    if (articles.isEmpty()) {
1392                            return null;
1393                    }
1394    
1395                    return articles.get(0);
1396            }
1397    
1398            /**
1399             * Returns the web content article with the ID.
1400             *
1401             * @param  id the primary key of the web content article
1402             * @return the web content article with the ID
1403             * @throws PortalException if a matching web content article could not be
1404             *         found
1405             * @throws SystemException if a system exception occurred
1406             */
1407            @Override
1408            public JournalArticle getArticle(long id)
1409                    throws PortalException, SystemException {
1410    
1411                    return journalArticlePersistence.findByPrimaryKey(id);
1412            }
1413    
1414            /**
1415             * Returns the latest approved web content article, or the latest unapproved
1416             * article if none are approved. Both approved and unapproved articles must
1417             * match the group and article ID.
1418             *
1419             * @param  groupId the primary key of the web content article's group
1420             * @param  articleId the primary key of the web content article
1421             * @return the matching web content article
1422             * @throws PortalException if a matching web content article could not be
1423             *         found
1424             * @throws SystemException if a system exception occurred
1425             */
1426            @Override
1427            public JournalArticle getArticle(long groupId, String articleId)
1428                    throws PortalException, SystemException {
1429    
1430                    // Get the latest article that is approved, if none are approved, get
1431                    // the latest unapproved article
1432    
1433                    try {
1434                            return getLatestArticle(
1435                                    groupId, articleId, WorkflowConstants.STATUS_APPROVED);
1436                    }
1437                    catch (NoSuchArticleException nsae) {
1438                            return getLatestArticle(
1439                                    groupId, articleId, WorkflowConstants.STATUS_ANY);
1440                    }
1441            }
1442    
1443            /**
1444             * Returns the web content article matching the group, article ID, and
1445             * version.
1446             *
1447             * @param  groupId the primary key of the web content article's group
1448             * @param  articleId the primary key of the web content article
1449             * @param  version the web content article's version
1450             * @return the matching web content article
1451             * @throws PortalException if a matching web content article could not be
1452             *         found
1453             * @throws SystemException if a system exception occurred
1454             */
1455            @Override
1456            public JournalArticle getArticle(
1457                            long groupId, String articleId, double version)
1458                    throws PortalException, SystemException {
1459    
1460                    return journalArticlePersistence.findByG_A_V(
1461                            groupId, articleId, version);
1462            }
1463    
1464            /**
1465             * Returns the web content article matching the group, class name, and class
1466             * PK.
1467             *
1468             * @param  groupId the primary key of the web content article's group
1469             * @param  className the DDMStructure class name if the web content article
1470             *         is related to a DDM structure, the primary key of the class name
1471             *         associated with the article, or {@link
1472             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
1473             * @param  classPK the primary key of the DDM structure, if the the
1474             *         DDMStructure class name is given as the <code>className</code>
1475             *         parameter, the primary key of the class associated with the web
1476             *         content article, or <code>0</code> otherwise
1477             * @return the matching web content article
1478             * @throws PortalException if a matching web content article could not be
1479             *         found
1480             * @throws SystemException if a system exception occurred
1481             */
1482            @Override
1483            public JournalArticle getArticle(
1484                            long groupId, String className, long classPK)
1485                    throws PortalException, SystemException {
1486    
1487                    long classNameId = PortalUtil.getClassNameId(className);
1488    
1489                    List<JournalArticle> articles = journalArticlePersistence.findByG_C_C(
1490                            groupId, classNameId, classPK);
1491    
1492                    if (articles.isEmpty()) {
1493                            throw new NoSuchArticleException(
1494                                    "No approved JournalArticle exists with the key {groupId=" +
1495                                            groupId + ", className=" + className + ", classPK=" +
1496                                                    classPK + "}");
1497                    }
1498    
1499                    return articles.get(0);
1500            }
1501    
1502            /**
1503             * Returns the latest web content article that is approved, or the latest
1504             * unapproved article if none are approved. Both approved and unapproved
1505             * articles must match the group and URL title.
1506             *
1507             * @param  groupId the primary key of the web content article's group
1508             * @param  urlTitle the web content article's accessible URL title
1509             * @return the matching web content article
1510             * @throws PortalException if a portal exception occurred
1511             * @throws SystemException if a system exception occurred
1512             */
1513            @Override
1514            public JournalArticle getArticleByUrlTitle(long groupId, String urlTitle)
1515                    throws PortalException, SystemException {
1516    
1517                    // Get the latest article that is approved, if none are approved, get
1518                    // the latest unapproved article
1519    
1520                    try {
1521                            return getLatestArticleByUrlTitle(
1522                                    groupId, urlTitle, WorkflowConstants.STATUS_APPROVED);
1523                    }
1524                    catch (NoSuchArticleException nsae) {
1525                            return getLatestArticleByUrlTitle(
1526                                    groupId, urlTitle, WorkflowConstants.STATUS_PENDING);
1527                    }
1528            }
1529    
1530            /**
1531             * Returns the web content associated with the web content article and DDM
1532             * template.
1533             *
1534             * @param  article the web content article
1535             * @param  ddmTemplateKey the primary key of the web content article's DDM
1536             *         template (optionally <code>null</code>). If the article is
1537             *         related to a DDM structure, the template's structure must match
1538             *         it.
1539             * @param  viewMode the mode in which the web content is being viewed
1540             * @param  languageId the primary key of the language translation to get
1541             * @param  themeDisplay the theme display
1542             * @return the web content associated with the DDM template
1543             * @throws PortalException if a matching DDM template could not be found or
1544             *         if a portal exception occurred
1545             * @throws SystemException if a system exception occurred
1546             */
1547            @Override
1548            public String getArticleContent(
1549                            JournalArticle article, String ddmTemplateKey, String viewMode,
1550                            String languageId, ThemeDisplay themeDisplay)
1551                    throws PortalException, SystemException {
1552    
1553                    JournalArticleDisplay articleDisplay = getArticleDisplay(
1554                            article, ddmTemplateKey, viewMode, languageId, 1, null,
1555                            themeDisplay);
1556    
1557                    if (articleDisplay == null) {
1558                            return StringPool.BLANK;
1559                    }
1560                    else {
1561                            return articleDisplay.getContent();
1562                    }
1563            }
1564    
1565            /**
1566             * Returns the web content matching the group, article ID, and version, and
1567             * associated with the DDM template.
1568             *
1569             * @param  groupId the primary key of the web content article's group
1570             * @param  articleId the primary key of the web content article
1571             * @param  version the web content article's version
1572             * @param  viewMode the mode in which the web content is being viewed
1573             * @param  ddmTemplateKey the primary key of the web content article's DDM
1574             *         template (optionally <code>null</code>). If the article is
1575             *         related to a DDM structure, the template's structure must match
1576             *         it.
1577             * @param  languageId the primary key of the language translation to get
1578             * @param  themeDisplay the theme display
1579             * @return the matching web content
1580             * @throws PortalException if a matching web content article or DDM template
1581             *         could not be found, or if a portal exception occurred
1582             * @throws SystemException if a system exception occurred
1583             */
1584            @Override
1585            public String getArticleContent(
1586                            long groupId, String articleId, double version, String viewMode,
1587                            String ddmTemplateKey, String languageId, ThemeDisplay themeDisplay)
1588                    throws PortalException, SystemException {
1589    
1590                    JournalArticleDisplay articleDisplay = getArticleDisplay(
1591                            groupId, articleId, version, ddmTemplateKey, viewMode, languageId,
1592                            themeDisplay);
1593    
1594                    if (articleDisplay == null) {
1595                            return StringPool.BLANK;
1596                    }
1597                    else {
1598                            return articleDisplay.getContent();
1599                    }
1600            }
1601    
1602            /**
1603             * Returns the web content matching the group, article ID, and version.
1604             *
1605             * @param  groupId the primary key of the web content article's group
1606             * @param  articleId the primary key of the web content article
1607             * @param  version the web content article's version
1608             * @param  viewMode the mode in which the web content is being viewed
1609             * @param  languageId the primary key of the language translation to get
1610             * @param  themeDisplay the theme display
1611             * @return the matching web content
1612             * @throws PortalException if a matching web content article or DDM template
1613             *         could not be found, or if a portal exception occurred
1614             * @throws SystemException if a system exception occurred
1615             */
1616            @Override
1617            public String getArticleContent(
1618                            long groupId, String articleId, double version, String viewMode,
1619                            String languageId, ThemeDisplay themeDisplay)
1620                    throws PortalException, SystemException {
1621    
1622                    return getArticleContent(
1623                            groupId, articleId, version, viewMode, null, languageId,
1624                            themeDisplay);
1625            }
1626    
1627            /**
1628             * Returns the latest web content matching the group and article ID, and
1629             * associated with DDM template key.
1630             *
1631             * @param  groupId the primary key of the web content article's group
1632             * @param  articleId the primary key of the web content article
1633             * @param  viewMode the mode in which the web content is being viewed
1634             * @param  ddmTemplateKey the primary key of the web content article's DDM
1635             *         template (optionally <code>null</code>). If the article is
1636             *         related to a DDM structure, the template's structure must match
1637             *         it.
1638             * @param  languageId the primary key of the language translation to get
1639             * @param  themeDisplay the theme display
1640             * @return the matching web content
1641             * @throws PortalException if a matching web content article or DDM template
1642             *         could not be found, or if a portal exception occurred
1643             * @throws SystemException if a system exception occurred
1644             */
1645            @Override
1646            public String getArticleContent(
1647                            long groupId, String articleId, String viewMode,
1648                            String ddmTemplateKey, String languageId, ThemeDisplay themeDisplay)
1649                    throws PortalException, SystemException {
1650    
1651                    JournalArticleDisplay articleDisplay = getArticleDisplay(
1652                            groupId, articleId, ddmTemplateKey, viewMode, languageId,
1653                            themeDisplay);
1654    
1655                    return articleDisplay.getContent();
1656            }
1657    
1658            /**
1659             * Returns the latest web content matching the group and article ID.
1660             *
1661             * @param  groupId the primary key of the web content article's group
1662             * @param  articleId the primary key of the web content article
1663             * @param  viewMode the mode in which the web content is being viewed
1664             * @param  languageId the primary key of the language translation to get
1665             * @param  themeDisplay the theme display
1666             * @return the matching web content
1667             * @throws PortalException if a matching web content article or DDM template
1668             *         could not be found, or if a portal exception occurred
1669             * @throws SystemException if a system exception occurred
1670             */
1671            @Override
1672            public String getArticleContent(
1673                            long groupId, String articleId, String viewMode, String languageId,
1674                            ThemeDisplay themeDisplay)
1675                    throws PortalException, SystemException {
1676    
1677                    return getArticleContent(
1678                            groupId, articleId, viewMode, null, languageId, themeDisplay);
1679            }
1680    
1681            /**
1682             * Returns a web content article display for the specified page of the
1683             * latest version of the web content article, optionally based on the DDM
1684             * template if the article is template driven. If the article is template
1685             * driven, web content transformation tokens are added from the theme
1686             * display (if not <code>null</code>) or the XML request otherwise.
1687             *
1688             * @param  article the web content article
1689             * @param  ddmTemplateKey the primary key of the web content article's DDM
1690             *         template (optionally <code>null</code>). If the article is
1691             *         related to a DDM structure, the template's structure must match
1692             *         it.
1693             * @param  viewMode the mode in which the web content is being viewed
1694             * @param  languageId the primary key of the language translation to get
1695             * @param  page the web content's page number. Page numbers start at
1696             *         <code>1</code>.
1697             * @param  xmlRequest the request that serializes the web content into a
1698             *         hierarchical hash map (optionally <code>null</code>)
1699             * @param  themeDisplay the theme display
1700             * @return the web content article display
1701             * @throws PortalException if a matching DDM template could not be found or
1702             *         if a portal exception occurred
1703             * @throws SystemException if a system exception occurred
1704             */
1705            @Override
1706            public JournalArticleDisplay getArticleDisplay(
1707                            JournalArticle article, String ddmTemplateKey, String viewMode,
1708                            String languageId, int page, String xmlRequest,
1709                            ThemeDisplay themeDisplay)
1710                    throws PortalException, SystemException {
1711    
1712                    String content = null;
1713    
1714                    if (page < 1) {
1715                            page = 1;
1716                    }
1717    
1718                    int numberOfPages = 1;
1719                    boolean paginate = false;
1720                    boolean pageFlow = false;
1721    
1722                    boolean cacheable = true;
1723    
1724                    if (Validator.isNull(xmlRequest)) {
1725                            xmlRequest = "<request />";
1726                    }
1727    
1728                    Map<String, String> tokens = JournalUtil.getTokens(
1729                            article.getGroupId(), themeDisplay, xmlRequest);
1730    
1731                    if ((themeDisplay == null) && xmlRequest.equals("<request />")) {
1732                            tokens.put("company_id", String.valueOf(article.getCompanyId()));
1733    
1734                            Group companyGroup = groupLocalService.getCompanyGroup(
1735                                    article.getCompanyId());
1736    
1737                            tokens.put(
1738                                    "article_group_id", String.valueOf(article.getGroupId()));
1739                            tokens.put(
1740                                    "company_group_id", String.valueOf(companyGroup.getGroupId()));
1741    
1742                            // Deprecated tokens
1743    
1744                            tokens.put("group_id", String.valueOf(article.getGroupId()));
1745                    }
1746    
1747                    tokens.put(
1748                            "article_resource_pk",
1749                            String.valueOf(article.getResourcePrimKey()));
1750    
1751                    String defaultDDMTemplateKey = article.getTemplateId();
1752    
1753                    if (article.isTemplateDriven()) {
1754                            if (Validator.isNull(ddmTemplateKey)) {
1755                                    ddmTemplateKey = defaultDDMTemplateKey;
1756                            }
1757    
1758                            tokens.put("structure_id", article.getStructureId());
1759                            tokens.put("template_id", ddmTemplateKey);
1760                    }
1761    
1762                    String xml = article.getContent();
1763    
1764                    try {
1765                            Document document = null;
1766    
1767                            Element rootElement = null;
1768    
1769                            if (article.isTemplateDriven()) {
1770                                    document = SAXReaderUtil.read(xml);
1771    
1772                                    rootElement = document.getRootElement();
1773    
1774                                    Document requestDocument = SAXReaderUtil.read(xmlRequest);
1775    
1776                                    List<Element> pages = rootElement.elements("page");
1777    
1778                                    if (!pages.isEmpty()) {
1779                                            pageFlow = true;
1780    
1781                                            String targetPage = requestDocument.valueOf(
1782                                                    "/request/parameters/parameter[name='targetPage']/" +
1783                                                            "value");
1784    
1785                                            Element pageElement = null;
1786    
1787                                            if (Validator.isNotNull(targetPage)) {
1788                                                    targetPage = HtmlUtil.escapeXPathAttribute(targetPage);
1789    
1790                                                    XPath xPathSelector = SAXReaderUtil.createXPath(
1791                                                            "/root/page[@id = " + targetPage + "]");
1792    
1793                                                    pageElement = (Element)xPathSelector.selectSingleNode(
1794                                                            document);
1795                                            }
1796    
1797                                            if (pageElement != null) {
1798                                                    document = SAXReaderUtil.createDocument(pageElement);
1799    
1800                                                    rootElement = document.getRootElement();
1801    
1802                                                    numberOfPages = pages.size();
1803                                            }
1804                                            else {
1805                                                    if (page > pages.size()) {
1806                                                            page = 1;
1807                                                    }
1808    
1809                                                    pageElement = pages.get(page - 1);
1810    
1811                                                    document = SAXReaderUtil.createDocument(pageElement);
1812    
1813                                                    rootElement = document.getRootElement();
1814    
1815                                                    numberOfPages = pages.size();
1816                                                    paginate = true;
1817                                            }
1818                                    }
1819    
1820                                    rootElement.add(requestDocument.getRootElement().createCopy());
1821    
1822                                    JournalUtil.addAllReservedEls(
1823                                            rootElement, tokens, article, languageId, themeDisplay);
1824    
1825                                    xml = DDMXMLUtil.formatXML(document);
1826                            }
1827                    }
1828                    catch (DocumentException de) {
1829                            throw new SystemException(de);
1830                    }
1831    
1832                    try {
1833                            if (_log.isDebugEnabled()) {
1834                                    _log.debug(
1835                                            "Transforming " + article.getArticleId() + " " +
1836                                                    article.getVersion() + " " + languageId);
1837                            }
1838    
1839                            String script = null;
1840                            String langType = null;
1841    
1842                            if (article.isTemplateDriven()) {
1843    
1844                                    // Try with specified template first (in the current group and
1845                                    // the global group). If a template is not specified, use the
1846                                    // default one. If the specified template does not exist, use
1847                                    // the default one. If the default one does not exist, throw an
1848                                    // exception.
1849    
1850                                    DDMTemplate ddmTemplate = null;
1851    
1852                                    try {
1853                                            ddmTemplate = ddmTemplatePersistence.findByG_C_T(
1854                                                    article.getGroupId(),
1855                                                    PortalUtil.getClassNameId(DDMStructure.class),
1856                                                    ddmTemplateKey);
1857                                    }
1858                                    catch (NoSuchTemplateException nste1) {
1859                                            try {
1860                                                    Group companyGroup = groupLocalService.getCompanyGroup(
1861                                                            article.getCompanyId());
1862    
1863                                                    ddmTemplate = ddmTemplatePersistence.findByG_C_T(
1864                                                            companyGroup.getGroupId(),
1865                                                            PortalUtil.getClassNameId(DDMStructure.class),
1866                                                            ddmTemplateKey);
1867    
1868                                                    tokens.put(
1869                                                            "company_group_id",
1870                                                            String.valueOf(companyGroup.getGroupId()));
1871                                            }
1872                                            catch (NoSuchTemplateException nste2) {
1873                                                    if (!defaultDDMTemplateKey.equals(ddmTemplateKey)) {
1874                                                            ddmTemplate = ddmTemplatePersistence.findByG_C_T(
1875                                                                    article.getGroupId(),
1876                                                                    PortalUtil.getClassNameId(DDMStructure.class),
1877                                                                    defaultDDMTemplateKey);
1878                                                    }
1879                                                    else {
1880                                                            throw nste1;
1881                                                    }
1882                                            }
1883                                    }
1884    
1885                                    script = ddmTemplate.getScript();
1886                                    langType = ddmTemplate.getLanguage();
1887                                    cacheable = ddmTemplate.isCacheable();
1888                            }
1889    
1890                            content = JournalUtil.transform(
1891                                    themeDisplay, tokens, viewMode, languageId, xml, script,
1892                                    langType);
1893    
1894                            if (!pageFlow) {
1895                                    String[] pieces = StringUtil.split(
1896                                            content, PropsValues.JOURNAL_ARTICLE_TOKEN_PAGE_BREAK);
1897    
1898                                    if (pieces.length > 1) {
1899                                            if (page > pieces.length) {
1900                                                    page = 1;
1901                                            }
1902    
1903                                            content = pieces[page - 1];
1904                                            numberOfPages = pieces.length;
1905                                            paginate = true;
1906                                    }
1907                            }
1908                    }
1909                    catch (Exception e) {
1910                            throw new SystemException(e);
1911                    }
1912    
1913                    return new JournalArticleDisplayImpl(
1914                            article.getCompanyId(), article.getId(),
1915                            article.getResourcePrimKey(), article.getGroupId(),
1916                            article.getUserId(), article.getArticleId(), article.getVersion(),
1917                            article.getTitle(languageId), article.getUrlTitle(),
1918                            article.getDescription(languageId),
1919                            article.getAvailableLanguageIds(), content, article.getType(),
1920                            article.getStructureId(), ddmTemplateKey, article.isSmallImage(),
1921                            article.getSmallImageId(), article.getSmallImageURL(),
1922                            numberOfPages, page, paginate, cacheable);
1923            }
1924    
1925            /**
1926             * Returns a web content article display for the first page of the specified
1927             * version of the web content article, optionally based on the DDM template
1928             * if the article is template driven. If the article is template driven, web
1929             * content transformation tokens are added from the theme display (if not
1930             * <code>null</code>) or the XML request otherwise.
1931             *
1932             * @param  groupId the primary key of the web content article's group
1933             * @param  articleId the primary key of the web content article
1934             * @param  version the web content article's version
1935             * @param  ddmTemplateKey the primary key of the web content article's DDM
1936             *         template (optionally <code>null</code>). If the article is
1937             *         related to a DDM structure, the template's structure must match
1938             *         it.
1939             * @param  viewMode the mode in which the web content is being viewed
1940             * @param  languageId the primary key of the language translation to get
1941             * @param  page the web content's page number
1942             * @param  xmlRequest the request that serializes the web content into a
1943             *         hierarchical hash map
1944             * @param  themeDisplay the theme display
1945             * @return the web content article display, or <code>null</code> if the
1946             *         article has expired or if article's display date/time is after
1947             *         the current date/time
1948             * @throws PortalException if a matching web content article or DDM template
1949             *         could not be found, or if a portal exception occurred
1950             * @throws SystemException if a system exception occurred
1951             */
1952            @Override
1953            public JournalArticleDisplay getArticleDisplay(
1954                            long groupId, String articleId, double version,
1955                            String ddmTemplateKey, String viewMode, String languageId, int page,
1956                            String xmlRequest, ThemeDisplay themeDisplay)
1957                    throws PortalException, SystemException {
1958    
1959                    Date now = new Date();
1960    
1961                    JournalArticle article = journalArticlePersistence.findByG_A_V(
1962                            groupId, articleId, version);
1963    
1964                    if (article.isExpired()) {
1965                            Date expirationDate = article.getExpirationDate();
1966    
1967                            if ((expirationDate != null) && expirationDate.before(now)) {
1968                                    return null;
1969                            }
1970                    }
1971    
1972                    Date displayDate = article.getDisplayDate();
1973    
1974                    if (displayDate.after(now)) {
1975                            return null;
1976                    }
1977    
1978                    return getArticleDisplay(
1979                            article, ddmTemplateKey, viewMode, languageId, page, xmlRequest,
1980                            themeDisplay);
1981            }
1982    
1983            /**
1984             * Returns a web content article display for the first page of the specified
1985             * version of the web content article matching the group and article ID,
1986             * optionally based on the DDM template if the article is template driven.
1987             * If the article is template driven, web content transformation tokens are
1988             * added from the theme display (if not <code>null</code>).
1989             *
1990             * @param  groupId the primary key of the web content article's group
1991             * @param  articleId the primary key of the web content article
1992             * @param  version the web content article's version
1993             * @param  ddmTemplateKey the primary key of the web content article's DDM
1994             *         template (optionally <code>null</code>). If the article is
1995             *         related to a DDM structure, the template's structure must match
1996             *         it.
1997             * @param  viewMode the mode in which the web content is being viewed
1998             * @param  languageId the primary key of the language translation to get
1999             * @param  themeDisplay the theme display
2000             * @return the web content article display, or <code>null</code> if the
2001             *         article has expired or if article's display date/time is after
2002             *         the current date/time
2003             * @throws PortalException if a matching web content article or DDM template
2004             *         could not be found, or if a portal exception occurred
2005             * @throws SystemException if a system exception occurred
2006             */
2007            @Override
2008            public JournalArticleDisplay getArticleDisplay(
2009                            long groupId, String articleId, double version,
2010                            String ddmTemplateKey, String viewMode, String languageId,
2011                            ThemeDisplay themeDisplay)
2012                    throws PortalException, SystemException {
2013    
2014                    return getArticleDisplay(
2015                            groupId, articleId, version, ddmTemplateKey, viewMode, languageId,
2016                            1, null, themeDisplay);
2017            }
2018    
2019            /**
2020             * Returns a web content article display for the first page of the latest
2021             * version of the web content article matching the group and article ID. If
2022             * the article is template driven, web content transformation tokens are
2023             * added from the theme display (if not <code>null</code>) or the XML
2024             * request otherwise.
2025             *
2026             * @param  groupId the primary key of the web content article's group
2027             * @param  articleId the primary key of the web content article
2028             * @param  viewMode the mode in which the web content is being viewed
2029             * @param  languageId the primary key of the language translation to get
2030             * @param  page the web content's page number
2031             * @param  xmlRequest the request that serializes the web content into a
2032             *         hierarchical hash map
2033             * @param  themeDisplay the theme display
2034             * @return the web content article display, or <code>null</code> if the
2035             *         article has expired or if article's display date/time is after
2036             *         the current date/time
2037             * @throws PortalException if a matching web content article or DDM template
2038             *         could not be found, or if a portal exception occurred
2039             * @throws SystemException if a system exception occurred
2040             */
2041            @Override
2042            public JournalArticleDisplay getArticleDisplay(
2043                            long groupId, String articleId, String viewMode, String languageId,
2044                            int page, String xmlRequest, ThemeDisplay themeDisplay)
2045                    throws PortalException, SystemException {
2046    
2047                    return getArticleDisplay(
2048                            groupId, articleId, null, viewMode, languageId, page, xmlRequest,
2049                            themeDisplay);
2050            }
2051    
2052            /**
2053             * Returns a web content article display for the specified page of the
2054             * latest version of the web content article matching the group and article
2055             * ID, optionally based on the DDM template if the article is template
2056             * driven. If the article is template driven, web content transformation
2057             * tokens are added from the theme display (if not <code>null</code>) or the
2058             * XML request otherwise.
2059             *
2060             * @param  groupId the primary key of the web content article's group
2061             * @param  articleId the primary key of the web content article
2062             * @param  ddmTemplateKey the primary key of the web content article's DDM
2063             *         template (optionally <code>null</code>). If the article is
2064             *         related to a DDM structure, the template's structure must match
2065             *         it.
2066             * @param  viewMode the mode in which the web content is being viewed
2067             * @param  languageId the primary key of the language translation to get
2068             * @param  page the web content's page number
2069             * @param  xmlRequest the request that serializes the web content into a
2070             *         hierarchical hash map
2071             * @param  themeDisplay the theme display
2072             * @return the web content article display, or <code>null</code> if the
2073             *         article has expired or if article's display date/time is after
2074             *         the current date/time
2075             * @throws PortalException if a matching web content article or DDM template
2076             *         could not be found, or if a portal exception occurred
2077             * @throws SystemException if a system exception occurred
2078             */
2079            @Override
2080            public JournalArticleDisplay getArticleDisplay(
2081                            long groupId, String articleId, String ddmTemplateKey,
2082                            String viewMode, String languageId, int page, String xmlRequest,
2083                            ThemeDisplay themeDisplay)
2084                    throws PortalException, SystemException {
2085    
2086                    JournalArticle article = getDisplayArticle(groupId, articleId);
2087    
2088                    return getArticleDisplay(
2089                            groupId, articleId, article.getVersion(), ddmTemplateKey, viewMode,
2090                            languageId, page, xmlRequest, themeDisplay);
2091            }
2092    
2093            /**
2094             * Returns a web content article display for the first page of the latest
2095             * version of the web content article matching the group and article ID,
2096             * optionally based on the DDM template if the article is template driven.
2097             * If the article is template driven, web content transformation tokens are
2098             * added from the theme display (if not <code>null</code>).
2099             *
2100             * @param  groupId the primary key of the web content article's group
2101             * @param  articleId the primary key of the web content article
2102             * @param  ddmTemplateKey the primary key of the web content article's DDM
2103             *         template (optionally <code>null</code>). If the article is
2104             *         related to a DDM structure, the template's structure must match
2105             *         it.
2106             * @param  viewMode the mode in which the web content is being viewed
2107             * @param  languageId the primary key of the language translation to get
2108             * @param  themeDisplay the theme display
2109             * @return the web content article display, or <code>null</code> if the
2110             *         article has expired or if article's display date/time is after
2111             *         the current date/time
2112             * @throws PortalException if a matching web content article or DDM template
2113             *         could not be found, or if a portal exception occurred
2114             * @throws SystemException if a system exception occurred
2115             */
2116            @Override
2117            public JournalArticleDisplay getArticleDisplay(
2118                            long groupId, String articleId, String ddmTemplateKey,
2119                            String viewMode, String languageId, ThemeDisplay themeDisplay)
2120                    throws PortalException, SystemException {
2121    
2122                    JournalArticle article = getDisplayArticle(groupId, articleId);
2123    
2124                    return getArticleDisplay(
2125                            groupId, articleId, article.getVersion(), ddmTemplateKey, viewMode,
2126                            languageId, themeDisplay);
2127            }
2128    
2129            /**
2130             * Returns a web content article display for the first page of the latest
2131             * version of the web content article matching the group and article ID. If
2132             * the article is template driven, web content transformation tokens are
2133             * added from the theme display (if not <code>null</code>).
2134             *
2135             * @param  groupId the primary key of the web content article's group
2136             * @param  articleId the primary key of the web content article
2137             * @param  viewMode the mode in which the web content is being viewed
2138             * @param  languageId the primary key of the language translation to get
2139             * @param  themeDisplay the theme display
2140             * @return the web content article display, or <code>null</code> if the
2141             *         article has expired or if article's display date/time is after
2142             *         the current date/time
2143             * @throws PortalException if a matching web content article or DDM template
2144             *         could not be found, or if a portal exception occurred
2145             * @throws SystemException if a system exception occurred
2146             */
2147            @Override
2148            public JournalArticleDisplay getArticleDisplay(
2149                            long groupId, String articleId, String viewMode, String languageId,
2150                            ThemeDisplay themeDisplay)
2151                    throws PortalException, SystemException {
2152    
2153                    return getArticleDisplay(
2154                            groupId, articleId, null, viewMode, languageId, themeDisplay);
2155            }
2156    
2157            /**
2158             * Returns all the web content articles present in the system.
2159             *
2160             * @return the web content articles present in the system
2161             * @throws SystemException if a system exception occurred
2162             */
2163            @Override
2164            public List<JournalArticle> getArticles() throws SystemException {
2165                    return journalArticlePersistence.findAll();
2166            }
2167    
2168            /**
2169             * Returns all the web content articles belonging to the group.
2170             *
2171             * @param  groupId the primary key of the web content article's group
2172             * @return the web content articles belonging to the group
2173             * @throws SystemException if a system exception occurred
2174             */
2175            @Override
2176            public List<JournalArticle> getArticles(long groupId)
2177                    throws SystemException {
2178    
2179                    return journalArticlePersistence.findByGroupId(groupId);
2180            }
2181    
2182            /**
2183             * Returns a range of all the web content articles belonging to the group.
2184             *
2185             * <p>
2186             * Useful when paginating results. Returns a maximum of <code>end -
2187             * start</code> instances. <code>start</code> and <code>end</code> are not
2188             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2189             * refers to the first result in the set. Setting both <code>start</code>
2190             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2191             * result set.
2192             * </p>
2193             *
2194             * @param  groupId the primary key of the web content article's group
2195             * @param  start the lower bound of the range of web content articles to
2196             *         return
2197             * @param  end the upper bound of the range of web content articles to
2198             *         return (not inclusive)
2199             * @return the range of matching web content articles
2200             * @throws SystemException if a system exception occurred
2201             */
2202            @Override
2203            public List<JournalArticle> getArticles(long groupId, int start, int end)
2204                    throws SystemException {
2205    
2206                    return journalArticlePersistence.findByGroupId(groupId, start, end);
2207            }
2208    
2209            /**
2210             * Returns an ordered range of all the web content articles belonging to the
2211             * group.
2212             *
2213             * <p>
2214             * Useful when paginating results. Returns a maximum of <code>end -
2215             * start</code> instances. <code>start</code> and <code>end</code> are not
2216             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2217             * refers to the first result in the set. Setting both <code>start</code>
2218             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2219             * result set.
2220             * </p>
2221             *
2222             * @param  groupId the primary key of the web content article's group
2223             * @param  start the lower bound of the range of web content articles to
2224             *         return
2225             * @param  end the upper bound of the range of web content articles to
2226             *         return (not inclusive)
2227             * @param  obc the comparator to order the web content articles
2228             * @return the range of matching web content articles ordered by the
2229             *         comparator
2230             * @throws SystemException if a system exception occurred
2231             */
2232            @Override
2233            public List<JournalArticle> getArticles(
2234                            long groupId, int start, int end, OrderByComparator obc)
2235                    throws SystemException {
2236    
2237                    return journalArticlePersistence.findByGroupId(
2238                            groupId, start, end, obc);
2239            }
2240    
2241            /**
2242             * Returns all the web content articles matching the group and folder.
2243             *
2244             * @param  groupId the primary key of the web content article's group
2245             * @param  folderId the primary key of the web content article folder
2246             * @return the matching web content articles
2247             * @throws SystemException if a system exception occurred
2248             */
2249            @Override
2250            public List<JournalArticle> getArticles(long groupId, long folderId)
2251                    throws SystemException {
2252    
2253                    return journalArticlePersistence.findByG_F(groupId, folderId);
2254            }
2255    
2256            /**
2257             * Returns a range of all the web content articles matching the group and
2258             * folder.
2259             *
2260             * <p>
2261             * Useful when paginating results. Returns a maximum of <code>end -
2262             * start</code> instances. <code>start</code> and <code>end</code> are not
2263             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2264             * refers to the first result in the set. Setting both <code>start</code>
2265             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2266             * result set.
2267             * </p>
2268             *
2269             * @param  groupId the primary key of the web content article's group
2270             * @param  folderId the primary key of the web content article's folder
2271             * @param  start the lower bound of the range of web content articles to
2272             *         return
2273             * @param  end the upper bound of the range of web content articles to
2274             *         return (not inclusive)
2275             * @return the range of matching web content articles
2276             * @throws SystemException if a system exception occurred
2277             */
2278            @Override
2279            public List<JournalArticle> getArticles(
2280                            long groupId, long folderId, int start, int end)
2281                    throws SystemException {
2282    
2283                    return journalArticlePersistence.findByG_F(
2284                            groupId, folderId, start, end);
2285            }
2286    
2287            /**
2288             * Returns an ordered range of all the web content articles matching the
2289             * group and folder.
2290             *
2291             * <p>
2292             * Useful when paginating results. Returns a maximum of <code>end -
2293             * start</code> instances. <code>start</code> and <code>end</code> are not
2294             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2295             * refers to the first result in the set. Setting both <code>start</code>
2296             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2297             * result set.
2298             * </p>
2299             *
2300             * @param  groupId the primary key of the web content article's group
2301             * @param  folderId the primary key of the web content article's folder
2302             * @param  start the lower bound of the range of web content articles to
2303             *         return
2304             * @param  end the upper bound of the range of web content articles to
2305             *         return (not inclusive)
2306             * @param  orderByComparator the comparator to order the web content
2307             *         articles
2308             * @return the range of matching web content articles ordered by the
2309             *         comparator
2310             * @throws SystemException if a system exception occurred
2311             */
2312            @Override
2313            public List<JournalArticle> getArticles(
2314                            long groupId, long folderId, int start, int end,
2315                            OrderByComparator orderByComparator)
2316                    throws SystemException {
2317    
2318                    return journalArticlePersistence.findByG_F(
2319                            groupId, folderId, start, end, orderByComparator);
2320            }
2321    
2322            /**
2323             * Returns all the web content articles matching the group and article ID.
2324             *
2325             * @param  groupId the primary key of the web content article's group
2326             * @param  articleId the primary key of the web content article
2327             * @return the matching web content articles
2328             * @throws SystemException if a system exception occurred
2329             */
2330            @Override
2331            public List<JournalArticle> getArticles(long groupId, String articleId)
2332                    throws SystemException {
2333    
2334                    return journalArticlePersistence.findByG_A(groupId, articleId);
2335            }
2336    
2337            @Override
2338            public List<JournalArticle> getArticlesByResourcePrimKey(
2339                            long resourcePrimKey)
2340                    throws SystemException {
2341    
2342                    return journalArticlePersistence.findByResourcePrimKey(resourcePrimKey);
2343            }
2344    
2345            /**
2346             * Returns all the web content articles matching the small image ID.
2347             *
2348             * @param  smallImageId the primary key of the web content article's small
2349             *         image
2350             * @return the web content articles matching the small image ID
2351             * @throws SystemException if a system exception occurred
2352             */
2353            @Override
2354            public List<JournalArticle> getArticlesBySmallImageId(long smallImageId)
2355                    throws SystemException {
2356    
2357                    return journalArticlePersistence.findBySmallImageId(smallImageId);
2358            }
2359    
2360            /**
2361             * Returns the number of web content articles belonging to the group.
2362             *
2363             * @param  groupId the primary key of the web content article's group
2364             * @return the number of web content articles belonging to the group
2365             * @throws SystemException if a system exception occurred
2366             */
2367            @Override
2368            public int getArticlesCount(long groupId) throws SystemException {
2369                    return journalArticlePersistence.countByGroupId(groupId);
2370            }
2371    
2372            /**
2373             * Returns the number of web content articles matching the group and folder.
2374             *
2375             * @param  groupId the primary key of the web content article's group
2376             * @param  folderId the primary key of the web content article's folder
2377             * @return the number of matching web content articles
2378             * @throws SystemException if a system exception occurred
2379             */
2380            @Override
2381            public int getArticlesCount(long groupId, long folderId)
2382                    throws SystemException {
2383    
2384                    return journalArticlePersistence.countByG_F(groupId, folderId);
2385            }
2386    
2387            /**
2388             * Returns an ordered range of all the web content articles matching the
2389             * company, version, and workflow status.
2390             *
2391             * <p>
2392             * Useful when paginating results. Returns a maximum of <code>end -
2393             * start</code> instances. <code>start</code> and <code>end</code> are not
2394             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2395             * refers to the first result in the set. Setting both <code>start</code>
2396             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2397             * result set.
2398             * </p>
2399             *
2400             * @param  companyId the primary key of the web content article's company
2401             * @param  version the web content article's version
2402             * @param  status the web content article's workflow status. For more
2403             *         information see {@link WorkflowConstants} for constants starting
2404             *         with the "STATUS_" prefix.
2405             * @param  start the lower bound of the range of web content articles to
2406             *         return
2407             * @param  end the upper bound of the range of web content articles to
2408             *         return (not inclusive)
2409             * @return the range of matching web content articles ordered by article ID
2410             * @throws SystemException if a system exception occurred
2411             */
2412            @Override
2413            public List<JournalArticle> getCompanyArticles(
2414                            long companyId, double version, int status, int start, int end)
2415                    throws SystemException {
2416    
2417                    if (status == WorkflowConstants.STATUS_ANY) {
2418                            return journalArticlePersistence.findByC_V(
2419                                    companyId, version, start, end, new ArticleIDComparator(true));
2420                    }
2421                    else {
2422                            return journalArticlePersistence.findByC_V_ST(
2423                                    companyId, version, status, start, end,
2424                                    new ArticleIDComparator(true));
2425                    }
2426            }
2427    
2428            /**
2429             * Returns an ordered range of all the web content articles matching the
2430             * company and workflow status.
2431             *
2432             * <p>
2433             * Useful when paginating results. Returns a maximum of <code>end -
2434             * start</code> instances. <code>start</code> and <code>end</code> are not
2435             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2436             * refers to the first result in the set. Setting both <code>start</code>
2437             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2438             * result set.
2439             * </p>
2440             *
2441             * @param  companyId the primary key of the web content article's company
2442             * @param  status the web content article's workflow status. For more
2443             *         information see {@link WorkflowConstants} for constants starting
2444             *         with the "STATUS_" prefix.
2445             * @param  start the lower bound of the range of web content articles to
2446             *         return
2447             * @param  end the upper bound of the range of web content articles to
2448             *         return (not inclusive)
2449             * @return the range of matching web content articles ordered by article ID
2450             * @throws SystemException if a system exception occurred
2451             */
2452            @Override
2453            public List<JournalArticle> getCompanyArticles(
2454                            long companyId, int status, int start, int end)
2455                    throws SystemException {
2456    
2457                    if (status == WorkflowConstants.STATUS_ANY) {
2458                            return journalArticlePersistence.findByCompanyId(
2459                                    companyId, start, end, new ArticleIDComparator(true));
2460                    }
2461                    else {
2462                            return journalArticlePersistence.findByC_ST(
2463                                    companyId, status, start, end, new ArticleIDComparator(true));
2464                    }
2465            }
2466    
2467            /**
2468             * Returns the number of web content articles matching the company, version,
2469             * and workflow status.
2470             *
2471             * <p>
2472             * Useful when paginating results. Returns a maximum of <code>end -
2473             * start</code> instances. <code>start</code> and <code>end</code> are not
2474             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2475             * refers to the first result in the set. Setting both <code>start</code>
2476             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2477             * result set.
2478             * </p>
2479             *
2480             * @param  companyId the primary key of the web content article's company
2481             * @param  version the web content article's version
2482             * @param  status the web content article's workflow status. For more
2483             *         information see {@link WorkflowConstants} for constants starting
2484             *         with the "STATUS_" prefix.
2485             * @param  start the lower bound of the range of web content articles to
2486             *         return
2487             * @param  end the upper bound of the range of web content articles to
2488             *         return (not inclusive)
2489             * @return the number of matching web content articles
2490             * @throws SystemException if a system exception occurred
2491             */
2492            @Override
2493            public int getCompanyArticlesCount(
2494                            long companyId, double version, int status, int start, int end)
2495                    throws SystemException {
2496    
2497                    if (status == WorkflowConstants.STATUS_ANY) {
2498                            return journalArticlePersistence.countByC_V(companyId, version);
2499                    }
2500                    else {
2501                            return journalArticlePersistence.countByC_V_ST(
2502                                    companyId, version, status);
2503                    }
2504            }
2505    
2506            /**
2507             * Returns the number of web content articles matching the company and
2508             * workflow status.
2509             *
2510             * @param  companyId the primary key of the web content article's company
2511             * @param  status the web content article's workflow status. For more
2512             *         information see {@link WorkflowConstants} for constants starting
2513             *         with the "STATUS_" prefix.
2514             * @return the number of matching web content articles
2515             * @throws SystemException if a system exception occurred
2516             */
2517            @Override
2518            public int getCompanyArticlesCount(long companyId, int status)
2519                    throws SystemException {
2520    
2521                    if (status == WorkflowConstants.STATUS_ANY) {
2522                            return journalArticlePersistence.countByCompanyId(companyId);
2523                    }
2524                    else {
2525                            return journalArticlePersistence.countByC_ST(companyId, status);
2526                    }
2527            }
2528    
2529            /**
2530             * Returns the matching web content article currently displayed or next to
2531             * be displayed if no article is currently displayed.
2532             *
2533             * @param  groupId the primary key of the web content article's group
2534             * @param  articleId the primary key of the web content article
2535             * @return the matching web content article currently displayed, or the next
2536             *         one to be displayed if no version of the article is currently
2537             *         displayed
2538             * @throws PortalException if no approved matching web content articles
2539             *         could be found
2540             * @throws SystemException if a system exception occurred
2541             */
2542            @Override
2543            public JournalArticle getDisplayArticle(long groupId, String articleId)
2544                    throws PortalException, SystemException {
2545    
2546                    List<JournalArticle> articles = journalArticlePersistence.findByG_A_ST(
2547                            groupId, articleId, WorkflowConstants.STATUS_APPROVED);
2548    
2549                    if (articles.isEmpty()) {
2550                            throw new NoSuchArticleException(
2551                                    "No approved JournalArticle exists with the key {groupId=" +
2552                                            groupId + ", " + "articleId=" + articleId + "}");
2553                    }
2554    
2555                    Date now = new Date();
2556    
2557                    for (int i = 0; i < articles.size(); i++) {
2558                            JournalArticle article = articles.get(i);
2559    
2560                            Date displayDate = article.getDisplayDate();
2561                            Date expirationDate = article.getExpirationDate();
2562    
2563                            if (((displayDate == null) || displayDate.before(now)) &&
2564                                    ((expirationDate == null) || expirationDate.after(now))) {
2565    
2566                                    return article;
2567                            }
2568                    }
2569    
2570                    return articles.get(0);
2571            }
2572    
2573            /**
2574             * Returns the web content article matching the URL title that is currently
2575             * displayed or next to be displayed if no article is currently displayed.
2576             *
2577             * @param  groupId the primary key of the web content article's group
2578             * @param  urlTitle the web content article's accessible URL title
2579             * @return the web content article matching the URL title that is currently
2580             *         displayed, or next one to be displayed if no version of the
2581             *         article is currently displayed
2582             * @throws PortalException if no approved matching web content articles
2583             *         could be found
2584             * @throws SystemException if a system exception occurred
2585             */
2586            @Override
2587            public JournalArticle getDisplayArticleByUrlTitle(
2588                            long groupId, String urlTitle)
2589                    throws PortalException, SystemException {
2590    
2591                    List<JournalArticle> articles = null;
2592    
2593                    OrderByComparator orderByComparator = new ArticleVersionComparator();
2594    
2595                    articles = journalArticlePersistence.findByG_UT_ST(
2596                            groupId, urlTitle, WorkflowConstants.STATUS_APPROVED,
2597                            QueryUtil.ALL_POS, QueryUtil.ALL_POS, orderByComparator);
2598    
2599                    if (articles.isEmpty()) {
2600                            throw new NoSuchArticleException(
2601                                    "No JournalArticle exists with the key {groupId=" + groupId +
2602                                            ", urlTitle=" + urlTitle + "}");
2603                    }
2604    
2605                    Date now = new Date();
2606    
2607                    for (JournalArticle article : articles) {
2608                            Date displayDate = article.getDisplayDate();
2609                            Date expirationDate = article.getExpirationDate();
2610    
2611                            if (((displayDate != null) && displayDate.before(now)) &&
2612                                    ((expirationDate == null) || expirationDate.after(now)) ) {
2613    
2614                                    return article;
2615                            }
2616                    }
2617    
2618                    return articles.get(0);
2619            }
2620    
2621            /**
2622             * Returns the latest web content article matching the resource primary key,
2623             * preferring articles with approved workflow status.
2624             *
2625             * @param  resourcePrimKey the primary key of the resource instance
2626             * @return the latest web content article matching the resource primary key,
2627             *         preferring articles with approved workflow status
2628             * @throws PortalException if a matching web content article could not be
2629             *         found
2630             * @throws SystemException if a system exception occurred
2631             */
2632            @Override
2633            public JournalArticle getLatestArticle(long resourcePrimKey)
2634                    throws PortalException, SystemException {
2635    
2636                    return getLatestArticle(resourcePrimKey, WorkflowConstants.STATUS_ANY);
2637            }
2638    
2639            /**
2640             * Returns the latest web content article matching the resource primary key
2641             * and workflow status, preferring articles with approved workflow status.
2642             *
2643             * @param  resourcePrimKey the primary key of the resource instance
2644             * @param  status the web content article's workflow status. For more
2645             *         information see {@link WorkflowConstants} for constants starting
2646             *         with the "STATUS_" prefix.
2647             * @return the latest web content article matching the resource primary key
2648             *         and workflow status, preferring articles with approved workflow
2649             *         status
2650             * @throws PortalException if a matching web content article could not be
2651             *         found
2652             * @throws SystemException if a system exception occurred
2653             */
2654            @Override
2655            public JournalArticle getLatestArticle(long resourcePrimKey, int status)
2656                    throws PortalException, SystemException {
2657    
2658                    return getLatestArticle(resourcePrimKey, status, true);
2659            }
2660    
2661            /**
2662             * Returns the latest web content article matching the resource primary key
2663             * and workflow status, optionally preferring articles with approved
2664             * workflow status.
2665             *
2666             * @param  resourcePrimKey the primary key of the resource instance
2667             * @param  status the web content article's workflow status. For more
2668             *         information see {@link WorkflowConstants} for constants starting
2669             *         with the "STATUS_" prefix.
2670             * @param  preferApproved whether to prefer returning the latest matching
2671             *         article that has workflow status {@link
2672             *         WorkflowConstants#STATUS_APPROVED} over returning one that has a
2673             *         different status
2674             * @return the latest web content article matching the resource primary key
2675             *         and workflow status, optionally preferring articles with approved
2676             *         workflow status
2677             * @throws PortalException if a matching web content article could not be
2678             *         found
2679             * @throws SystemException if a system exception occurred
2680             */
2681            @Override
2682            public JournalArticle getLatestArticle(
2683                            long resourcePrimKey, int status, boolean preferApproved)
2684                    throws PortalException, SystemException {
2685    
2686                    List<JournalArticle> articles = null;
2687    
2688                    OrderByComparator orderByComparator = new ArticleVersionComparator();
2689    
2690                    if (status == WorkflowConstants.STATUS_ANY) {
2691                            if (preferApproved) {
2692                                    articles = journalArticlePersistence.findByR_ST(
2693                                            resourcePrimKey, WorkflowConstants.STATUS_APPROVED, 0, 1,
2694                                            orderByComparator);
2695                            }
2696    
2697                            if ((articles == null) || (articles.size() == 0)) {
2698                                    articles = journalArticlePersistence.findByResourcePrimKey(
2699                                            resourcePrimKey, 0, 1, orderByComparator);
2700                            }
2701                    }
2702                    else {
2703                            articles = journalArticlePersistence.findByR_ST(
2704                                    resourcePrimKey, status, 0, 1, orderByComparator);
2705                    }
2706    
2707                    if (articles.isEmpty()) {
2708                            throw new NoSuchArticleException(
2709                                    "No JournalArticle exists with the key {resourcePrimKey=" +
2710                                            resourcePrimKey + "}");
2711                    }
2712    
2713                    return articles.get(0);
2714            }
2715    
2716            /**
2717             * Returns the latest web content article with the group and article ID.
2718             *
2719             * @param  groupId the primary key of the web content article's group
2720             * @param  articleId the primary key of the web content article
2721             * @return the latest matching web content article
2722             * @throws PortalException if a matching web content article could not be
2723             *         found
2724             * @throws SystemException if a system exception occurred
2725             */
2726            @Override
2727            public JournalArticle getLatestArticle(long groupId, String articleId)
2728                    throws PortalException, SystemException {
2729    
2730                    return getLatestArticle(
2731                            groupId, articleId, WorkflowConstants.STATUS_ANY);
2732            }
2733    
2734            /**
2735             * Returns the latest web content article matching the group, article ID,
2736             * and workflow status.
2737             *
2738             * @param  groupId the primary key of the web content article's group
2739             * @param  articleId the primary key of the web content article
2740             * @param  status the web content article's workflow status. For more
2741             *         information see {@link WorkflowConstants} for constants starting
2742             *         with the "STATUS_" prefix.
2743             * @return the latest matching web content article
2744             * @throws PortalException if a matching web content article could not be
2745             *         found
2746             * @throws SystemException if a system exception occurred
2747             */
2748            @Override
2749            public JournalArticle getLatestArticle(
2750                            long groupId, String articleId, int status)
2751                    throws PortalException, SystemException {
2752    
2753                    List<JournalArticle> articles = null;
2754    
2755                    OrderByComparator orderByComparator = new ArticleVersionComparator();
2756    
2757                    if (status == WorkflowConstants.STATUS_ANY) {
2758                            articles = journalArticlePersistence.findByG_A(
2759                                    groupId, articleId, 0, 1, orderByComparator);
2760                    }
2761                    else {
2762                            articles = journalArticlePersistence.findByG_A_ST(
2763                                    groupId, articleId, status, 0, 1, orderByComparator);
2764                    }
2765    
2766                    if (articles.isEmpty()) {
2767                            throw new NoSuchArticleException(
2768                                    "No JournalArticle exists with the key {groupId=" + groupId +
2769                                            ", articleId=" + articleId + ", status=" + status + "}");
2770                    }
2771    
2772                    return articles.get(0);
2773            }
2774    
2775            /**
2776             * Returns the latest web content article matching the group, class name ID,
2777             * and class PK.
2778             *
2779             * @param  groupId the primary key of the web content article's group
2780             * @param  className the DDMStructure class name if the web content article
2781             *         is related to a DDM structure, the class name associated with the
2782             *         article, or {@link JournalArticleConstants#CLASSNAME_ID_DEFAULT}
2783             *         otherwise
2784             * @param  classPK the primary key of the DDM structure, if the DDMStructure
2785             *         class name is given as the <code>className</code> parameter, the
2786             *         primary key of the class associated with the web content article,
2787             *         or <code>0</code> otherwise
2788             * @return the latest matching web content article
2789             * @throws PortalException if a matching web content article could not be
2790             *         found
2791             * @throws SystemException if a system exception occurred
2792             */
2793            @Override
2794            public JournalArticle getLatestArticle(
2795                            long groupId, String className, long classPK)
2796                    throws PortalException, SystemException {
2797    
2798                    long classNameId = PortalUtil.getClassNameId(className);
2799    
2800                    List<JournalArticle> articles = journalArticlePersistence.findByG_C_C(
2801                            groupId, classNameId, classPK, 0, 1,
2802                            new ArticleVersionComparator());
2803    
2804                    if (articles.isEmpty()) {
2805                            throw new NoSuchArticleException(
2806                                    "No JournalArticle exists with the key {groupId=" + groupId +
2807                                            ", className=" + className + ", classPK =" + classPK + "}");
2808                    }
2809    
2810                    return articles.get(0);
2811            }
2812    
2813            /**
2814             * Returns the latest web content article matching the group, URL title, and
2815             * workflow status.
2816             *
2817             * @param  groupId the primary key of the web content article's group
2818             * @param  urlTitle the web content article's accessible URL title
2819             * @param  status the web content article's workflow status. For more
2820             *         information see {@link WorkflowConstants} for constants starting
2821             *         with the "STATUS_" prefix.
2822             * @return the latest matching web content article
2823             * @throws PortalException if a matching web content article could not be
2824             *         found
2825             * @throws SystemException if a system exception occurred
2826             */
2827            @Override
2828            public JournalArticle getLatestArticleByUrlTitle(
2829                            long groupId, String urlTitle, int status)
2830                    throws PortalException, SystemException {
2831    
2832                    List<JournalArticle> articles = null;
2833    
2834                    OrderByComparator orderByComparator = new ArticleVersionComparator();
2835    
2836                    if (status == WorkflowConstants.STATUS_ANY) {
2837                            articles = journalArticlePersistence.findByG_UT(
2838                                    groupId, urlTitle, 0, 1, orderByComparator);
2839                    }
2840                    else {
2841                            articles = journalArticlePersistence.findByG_UT_ST(
2842                                    groupId, urlTitle, status, 0, 1, orderByComparator);
2843                    }
2844    
2845                    if (articles.isEmpty()) {
2846                            throw new NoSuchArticleException(
2847                                    "No JournalArticle exists with the key {groupId=" + groupId +
2848                                            ", urlTitle=" + urlTitle + ", status=" + status + "}");
2849                    }
2850    
2851                    return articles.get(0);
2852            }
2853    
2854            /**
2855             * Returns the latest version number of the web content with the group and
2856             * article ID.
2857             *
2858             * @param  groupId the primary key of the web content article's group
2859             * @param  articleId the primary key of the web content article
2860             * @return the latest version number of the matching web content
2861             * @throws PortalException if a matching web content article could not be
2862             *         found
2863             * @throws SystemException if a system exception occurred
2864             */
2865            @Override
2866            public double getLatestVersion(long groupId, String articleId)
2867                    throws PortalException, SystemException {
2868    
2869                    JournalArticle article = getLatestArticle(groupId, articleId);
2870    
2871                    return article.getVersion();
2872            }
2873    
2874            /**
2875             * Returns the latest version number of the web content with the group,
2876             * article ID, and workflow status.
2877             *
2878             * @param  groupId the primary key of the web content article's group
2879             * @param  articleId the primary key of the web content article
2880             * @param  status the web content article's workflow status. For more
2881             *         information see {@link WorkflowConstants} for constants starting
2882             *         with the "STATUS_" prefix.
2883             * @return the latest version number of the matching web content
2884             * @throws PortalException if a matching web content article could not be
2885             *         found
2886             * @throws SystemException if a system exception occurred
2887             */
2888            @Override
2889            public double getLatestVersion(long groupId, String articleId, int status)
2890                    throws PortalException, SystemException {
2891    
2892                    JournalArticle article = getLatestArticle(groupId, articleId, status);
2893    
2894                    return article.getVersion();
2895            }
2896    
2897            /**
2898             * Returns the number of web content articles that are not recycled.
2899             *
2900             * @param  groupId the primary key of the web content article's group
2901             * @param  folderId the primary key of the web content article folder
2902             * @return the number of web content articles that are not recycled
2903             * @throws SystemException if a system exception occurred
2904             */
2905            @Override
2906            public int getNotInTrashArticlesCount(long groupId, long folderId)
2907                    throws SystemException {
2908    
2909                    QueryDefinition queryDefinition = new QueryDefinition(
2910                            WorkflowConstants.STATUS_ANY);
2911    
2912                    List<Long> folderIds = new ArrayList<Long>();
2913    
2914                    folderIds.add(folderId);
2915    
2916                    return journalArticleFinder.countByG_F(
2917                            groupId, folderIds, queryDefinition);
2918            }
2919    
2920            /**
2921             * Returns the web content articles matching the group and DDM structure
2922             * key.
2923             *
2924             * @param  groupId the primary key of the web content article's group
2925             * @param  ddmStructureKey the primary key of the web content article's DDM
2926             *         structure
2927             * @return the matching web content articles
2928             * @throws SystemException if a system exception occurred
2929             */
2930            @Override
2931            public List<JournalArticle> getStructureArticles(
2932                            long groupId, String ddmStructureKey)
2933                    throws SystemException {
2934    
2935                    return journalArticlePersistence.findByG_S(groupId, ddmStructureKey);
2936            }
2937    
2938            /**
2939             * Returns an ordered range of all the web content articles matching the
2940             * group and DDM structure key.
2941             *
2942             * <p>
2943             * Useful when paginating results. Returns a maximum of <code>end -
2944             * start</code> instances. <code>start</code> and <code>end</code> are not
2945             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2946             * refers to the first result in the set. Setting both <code>start</code>
2947             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2948             * result set.
2949             * </p>
2950             *
2951             * @param  groupId the primary key of the web content article's group
2952             * @param  ddmStructureKey the primary key of the web content article's DDM
2953             *         structure
2954             * @param  start the lower bound of the range of web content articles to
2955             *         return
2956             * @param  end the upper bound of the range of web content articles to
2957             *         return (not inclusive)
2958             * @param  obc the comparator to order the web content articles
2959             * @return the range of matching web content articles ordered by the
2960             *         comparator
2961             * @throws SystemException if a system exception occurred
2962             */
2963            @Override
2964            public List<JournalArticle> getStructureArticles(
2965                            long groupId, String ddmStructureKey, int start, int end,
2966                            OrderByComparator obc)
2967                    throws SystemException {
2968    
2969                    return journalArticlePersistence.findByG_S(
2970                            groupId, ddmStructureKey, start, end, obc);
2971            }
2972    
2973            @Override
2974            public List<JournalArticle> getStructureArticles(String[] ddmStructureKeys)
2975                    throws SystemException {
2976    
2977                    return journalArticlePersistence.findByStructureId(ddmStructureKeys);
2978            }
2979    
2980            /**
2981             * Returns the number of web content articles matching the group and DDM
2982             * structure key.
2983             *
2984             * @param  groupId the primary key of the web content article's group
2985             * @param  ddmStructureKey the primary key of the web content article's DDM
2986             *         structure
2987             * @return the number of matching web content articles
2988             * @throws SystemException if a system exception occurred
2989             */
2990            @Override
2991            public int getStructureArticlesCount(long groupId, String ddmStructureKey)
2992                    throws SystemException {
2993    
2994                    return journalArticlePersistence.countByG_S(groupId, ddmStructureKey);
2995            }
2996    
2997            /**
2998             * Returns the web content articles matching the group and DDM template key.
2999             *
3000             * @param  groupId the primary key of the web content article's group
3001             * @param  ddmTemplateKey the primary key of the web content article's DDM
3002             *         template (optionally <code>null</code>). If the article is
3003             *         related to a DDM structure, the template's structure must match
3004             *         it.
3005             * @return the matching web content articles
3006             * @throws SystemException if a system exception occurred
3007             */
3008            @Override
3009            public List<JournalArticle> getTemplateArticles(
3010                            long groupId, String ddmTemplateKey)
3011                    throws SystemException {
3012    
3013                    return journalArticlePersistence.findByG_T(groupId, ddmTemplateKey);
3014            }
3015    
3016            /**
3017             * Returns an ordered range of all the web content articles matching the
3018             * group and DDM template key.
3019             *
3020             * <p>
3021             * Useful when paginating results. Returns a maximum of <code>end -
3022             * start</code> instances. <code>start</code> and <code>end</code> are not
3023             * primary keys, they are indexes in the result set. Thus, <code>0</code>
3024             * refers to the first result in the set. Setting both <code>start</code>
3025             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
3026             * result set.
3027             * </p>
3028             *
3029             * @param  groupId the primary key of the web content article's group
3030             * @param  ddmTemplateKey the primary key of the web content article's DDM
3031             *         template (optionally <code>null</code>). If the article is
3032             *         related to a DDM structure, the template's structure must match
3033             *         it.
3034             * @param  start the lower bound of the range of web content articles to
3035             *         return
3036             * @param  end the upper bound of the range of web content articles to
3037             *         return (not inclusive)
3038             * @param  obc the comparator to order the web content articles
3039             * @return the range of matching web content articles ordered by the
3040             *         comparator
3041             * @throws SystemException if a system exception occurred
3042             */
3043            @Override
3044            public List<JournalArticle> getTemplateArticles(
3045                            long groupId, String ddmTemplateKey, int start, int end,
3046                            OrderByComparator obc)
3047                    throws SystemException {
3048    
3049                    return journalArticlePersistence.findByG_T(
3050                            groupId, ddmTemplateKey, start, end, obc);
3051            }
3052    
3053            /**
3054             * Returns the number of web content articles matching the group and DDM
3055             * template key.
3056             *
3057             * @param  groupId the primary key of the web content article's group
3058             * @param  ddmTemplateKey the primary key of the web content article's DDM
3059             *         template (optionally <code>null</code>). If the article is
3060             *         related to a DDM structure, the template's structure must match
3061             *         it.
3062             * @return the number of matching web content articles
3063             * @throws SystemException if a system exception occurred
3064             */
3065            @Override
3066            public int getTemplateArticlesCount(long groupId, String ddmTemplateKey)
3067                    throws SystemException {
3068    
3069                    return journalArticlePersistence.countByG_T(groupId, ddmTemplateKey);
3070            }
3071    
3072            @Override
3073            public String getUniqueUrlTitle(
3074                            long groupId, String articleId, String urlTitle)
3075                    throws PortalException, SystemException {
3076    
3077                    for (int i = 1;; i++) {
3078                            JournalArticle article = null;
3079    
3080                            try {
3081                                    article = getArticleByUrlTitle(groupId, urlTitle);
3082                            }
3083                            catch (NoSuchArticleException nsae) {
3084                            }
3085    
3086                            if ((article == null) || articleId.equals(article.getArticleId())) {
3087                                    break;
3088                            }
3089                            else {
3090                                    String suffix = StringPool.DASH + i;
3091    
3092                                    String prefix = urlTitle;
3093    
3094                                    if (urlTitle.length() > suffix.length()) {
3095                                            prefix = urlTitle.substring(
3096                                                    0, urlTitle.length() - suffix.length());
3097                                    }
3098    
3099                                    urlTitle = prefix + suffix;
3100                            }
3101                    }
3102    
3103                    return urlTitle;
3104            }
3105    
3106            /**
3107             * Returns <code>true</code> if the specified web content article exists.
3108             *
3109             * @param  groupId the primary key of the group
3110             * @param  articleId the primary key of the web content article
3111             * @return <code>true</code> if the specified web content article exists;
3112             *         <code>false</code> otherwise
3113             * @throws SystemException if a system exception occurred
3114             */
3115            @Override
3116            public boolean hasArticle(long groupId, String articleId)
3117                    throws SystemException {
3118    
3119                    try {
3120                            getArticle(groupId, articleId);
3121    
3122                            return true;
3123                    }
3124                    catch (PortalException pe) {
3125                            return false;
3126                    }
3127            }
3128    
3129            /**
3130             * Returns <code>true</code> if the web content article, specified by group
3131             * and article ID, is the latest version.
3132             *
3133             * @param  groupId the primary key of the web content article's group
3134             * @param  articleId the primary key of the web content article
3135             * @param  version the web content article's version
3136             * @return <code>true</code> if the specified web content article is the
3137             *         latest version; <code>false</code> otherwise
3138             * @throws PortalException if a matching web content article could not be
3139             *         found
3140             * @throws SystemException if a system exception occurred
3141             */
3142            @Override
3143            public boolean isLatestVersion(
3144                            long groupId, String articleId, double version)
3145                    throws PortalException, SystemException {
3146    
3147                    if (getLatestVersion(groupId, articleId) == version) {
3148                            return true;
3149                    }
3150                    else {
3151                            return false;
3152                    }
3153            }
3154    
3155            /**
3156             * Returns <code>true</code> if the web content article, specified by group,
3157             * article ID, and workflow status, is the latest version.
3158             *
3159             * @param  groupId the primary key of the web content article's group
3160             * @param  articleId the primary key of the web content article
3161             * @param  version the web content article's version
3162             * @param  status the web content article's workflow status. For more
3163             *         information see {@link WorkflowConstants} for constants starting
3164             *         with the "STATUS_" prefix.
3165             * @return <code>true</code> if the specified web content article is the
3166             *         latest version; <code>false</code> otherwise
3167             * @throws PortalException if a matching web content article could not be
3168             *         found
3169             * @throws SystemException if a system exception occurred
3170             */
3171            @Override
3172            public boolean isLatestVersion(
3173                            long groupId, String articleId, double version, int status)
3174                    throws PortalException, SystemException {
3175    
3176                    if (getLatestVersion(groupId, articleId, status) == version) {
3177                            return true;
3178                    }
3179                    else {
3180                            return false;
3181                    }
3182            }
3183    
3184            /**
3185             * Moves the web content article matching the group and article ID to a new
3186             * folder.
3187             *
3188             * @param  groupId the primary key of the web content article's group
3189             * @param  articleId the primary key of the web content article
3190             * @param  newFolderId the primary key of the web content article's new
3191             *         folder
3192             * @return the updated web content article, which was moved to a new folder
3193             * @throws PortalException if a matching web content article could not be
3194             *         found
3195             * @throws SystemException if a system exception occurred
3196             */
3197            @Indexable(type = IndexableType.REINDEX)
3198            @Override
3199            public JournalArticle moveArticle(
3200                            long groupId, String articleId, long newFolderId)
3201                    throws PortalException, SystemException {
3202    
3203                    List<JournalArticle> articles = journalArticlePersistence.findByG_A(
3204                            groupId, articleId);
3205    
3206                    for (JournalArticle article : articles) {
3207                            article.setFolderId(newFolderId);
3208                            article.setTreePath(article.buildTreePath());
3209    
3210                            journalArticlePersistence.update(article);
3211                    }
3212    
3213                    return getArticle(groupId, articleId);
3214            }
3215    
3216            /**
3217             * Moves the web content article from the Recycle Bin to a new folder.
3218             *
3219             * @param  userId the primary key of the user updating the web content
3220             *         article
3221             * @param  groupId the primary key of the web content article's group
3222             * @param  article the web content article
3223             * @param  newFolderId the primary key of the web content article's new
3224             *         folder
3225             * @param  serviceContext the service context to be applied. Can set the
3226             *         modification date, portlet preferences, and can set whether to
3227             *         add the default command update for the web content article. With
3228             *         respect to social activities, by setting the service context's
3229             *         command to {@link
3230             *         com.liferay.portal.kernel.util.Constants#UPDATE}, the invocation
3231             *         is considered a web content update activity; otherwise it is
3232             *         considered a web content add activity.
3233             * @return the updated web content article, which was moved from the Recycle
3234             *         Bin to a new folder
3235             * @throws PortalException if a trashed web content article with the primary
3236             *         key could not be found or if a portal exception occurred
3237             * @throws SystemException if a system exception occurred
3238             */
3239            @Indexable(type = IndexableType.REINDEX)
3240            @Override
3241            public JournalArticle moveArticleFromTrash(
3242                            long userId, long groupId, JournalArticle article, long newFolderId,
3243                            ServiceContext serviceContext)
3244                    throws PortalException, SystemException {
3245    
3246                    if (article.isInTrash()) {
3247                            restoreArticleFromTrash(userId, article);
3248                    }
3249                    else {
3250                            updateStatus(
3251                                    userId, article, article.getStatus(), null,
3252                                    new HashMap<String, Serializable>(), serviceContext);
3253                    }
3254    
3255                    return moveArticle(groupId, article.getArticleId(), newFolderId);
3256            }
3257    
3258            /**
3259             * Moves the latest version of the web content article matching the group
3260             * and article ID to the recycle bin.
3261             *
3262             * @param  userId the primary key of the user updating the web content
3263             *         article
3264             * @param  article the web content article
3265             * @return the updated web content article, which was moved to the Recycle
3266             *         Bin
3267             * @throws PortalException if the user did not have permission to move the
3268             *         article to the Recycle Bin or if a portal exception occurred
3269             * @throws SystemException if a system exception occurred
3270             */
3271            @Indexable(type = IndexableType.REINDEX)
3272            @Override
3273            public JournalArticle moveArticleToTrash(
3274                            long userId, JournalArticle article)
3275                    throws PortalException, SystemException {
3276    
3277                    int oldStatus = article.getStatus();
3278    
3279                    if (oldStatus == WorkflowConstants.STATUS_PENDING) {
3280                            article.setStatus(WorkflowConstants.STATUS_DRAFT);
3281    
3282                            journalArticlePersistence.update(article);
3283                    }
3284    
3285                    List<JournalArticle> articleVersions =
3286                            journalArticlePersistence.findByG_A(
3287                                    article.getGroupId(), article.getArticleId());
3288    
3289                    articleVersions = ListUtil.sort(
3290                            articleVersions, new ArticleVersionComparator());
3291    
3292                    Map<String, Serializable> workflowContext =
3293                            new HashMap<String, Serializable>();
3294    
3295                    workflowContext.put("articleVersions", (Serializable)articleVersions);
3296    
3297                    article = updateStatus(
3298                            userId, article.getId(), WorkflowConstants.STATUS_IN_TRASH,
3299                            workflowContext, new ServiceContext());
3300    
3301                    TrashEntry trashEntry = trashEntryLocalService.getEntry(
3302                            JournalArticle.class.getName(), article.getResourcePrimKey());
3303    
3304                    String trashArticleId = TrashUtil.getTrashTitle(
3305                            trashEntry.getEntryId());
3306    
3307                    if (!articleVersions.isEmpty()) {
3308                            for (JournalArticle articleVersion : articleVersions) {
3309                                    articleVersion.setArticleId(trashArticleId);
3310    
3311                                    journalArticlePersistence.update(articleVersion);
3312                            }
3313                    }
3314    
3315                    JournalArticleResource articleResource =
3316                            journalArticleResourcePersistence.fetchByPrimaryKey(
3317                                    article.getResourcePrimKey());
3318    
3319                    articleResource.setArticleId(trashArticleId);
3320    
3321                    journalArticleResourcePersistence.update(articleResource);
3322    
3323                    article.setArticleId(trashArticleId);
3324    
3325                    article = journalArticlePersistence.update(article);
3326    
3327                    JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();
3328    
3329                    extraDataJSONObject.put("title", article.getTitle());
3330    
3331                    socialActivityLocalService.addActivity(
3332                            userId, article.getGroupId(), JournalArticle.class.getName(),
3333                            article.getResourcePrimKey(),
3334                            SocialActivityConstants.TYPE_MOVE_TO_TRASH,
3335                            extraDataJSONObject.toString(), 0);
3336    
3337                    if (!articleVersions.isEmpty()) {
3338                            Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
3339                                    JournalArticle.class);
3340    
3341                            for (JournalArticle articleVersion : articleVersions) {
3342                                    indexer.reindex(articleVersion);
3343                            }
3344                    }
3345    
3346                    if (oldStatus == WorkflowConstants.STATUS_PENDING) {
3347                            workflowInstanceLinkLocalService.deleteWorkflowInstanceLink(
3348                                    article.getCompanyId(), article.getGroupId(),
3349                                    JournalArticle.class.getName(), article.getId());
3350                    }
3351    
3352                    return article;
3353            }
3354    
3355            /**
3356             * Moves the latest version of the web content article matching the group
3357             * and article ID to the recycle bin.
3358             *
3359             * @param  userId the primary key of the user updating the web content
3360             *         article
3361             * @param  groupId the primary key of the web content article's group
3362             * @param  articleId the primary key of the web content article
3363             * @return the moved web content article or <code>null</code> if no matching
3364             *         article was found
3365             * @throws PortalException if the user did not have permission to move the
3366             *         article to the Recycle Bin or if a portal exception occurred
3367             * @throws SystemException if a system exception occurred
3368             */
3369            @Override
3370            public JournalArticle moveArticleToTrash(
3371                            long userId, long groupId, String articleId)
3372                    throws PortalException, SystemException {
3373    
3374                    List<JournalArticle> articles = journalArticlePersistence.findByG_A(
3375                            groupId, articleId, 0, 1, new ArticleVersionComparator());
3376    
3377                    if (!articles.isEmpty()) {
3378                            return journalArticleLocalService.moveArticleToTrash(
3379                                    userId, articles.get(0));
3380                    }
3381    
3382                    return null;
3383            }
3384    
3385            @Override
3386            public void rebuildTree(long companyId)
3387                    throws PortalException, SystemException {
3388    
3389                    List<JournalArticle> articles = journalArticlePersistence.findByC_NotST(
3390                            companyId, WorkflowConstants.STATUS_IN_TRASH);
3391    
3392                    for (JournalArticle article : articles) {
3393                            if (article.isInTrashContainer()) {
3394                                    continue;
3395                            }
3396    
3397                            article.setTreePath(article.buildTreePath());
3398    
3399                            journalArticlePersistence.update(article);
3400                    }
3401            }
3402    
3403            /**
3404             * Removes the web content of the web content article matching the group,
3405             * article ID, and version, and language.
3406             *
3407             * @param  groupId the primary key of the web content article's group
3408             * @param  articleId the primary key of the web content article
3409             * @param  version the web content article's version
3410             * @param  languageId the primary key of the language locale to remove
3411             * @return the updated web content article with the locale removed
3412             * @throws PortalException if a matching web content article could not be
3413             *         found
3414             * @throws SystemException if a system exception occurred
3415             */
3416            @Indexable(type = IndexableType.REINDEX)
3417            @Override
3418            public JournalArticle removeArticleLocale(
3419                            long groupId, String articleId, double version, String languageId)
3420                    throws PortalException, SystemException {
3421    
3422                    JournalArticle article = journalArticlePersistence.findByG_A_V(
3423                            groupId, articleId, version);
3424    
3425                    String title = article.getTitle();
3426    
3427                    title = LocalizationUtil.removeLocalization(
3428                            title, "static-content", languageId, true);
3429    
3430                    article.setTitle(title);
3431    
3432                    String description = article.getDescription();
3433    
3434                    description = LocalizationUtil.removeLocalization(
3435                            description, "static-content", languageId, true);
3436    
3437                    article.setDescription(description);
3438    
3439                    String content = article.getContent();
3440    
3441                    if (article.isTemplateDriven()) {
3442                            content = JournalUtil.removeArticleLocale(content, languageId);
3443                    }
3444                    else {
3445                            content = LocalizationUtil.removeLocalization(
3446                                    content, "static-content", languageId, true);
3447                    }
3448    
3449                    article.setContent(content);
3450    
3451                    journalArticlePersistence.update(article);
3452    
3453                    return article;
3454            }
3455    
3456            /**
3457             * Restores the web content article from the Recycle Bin.
3458             *
3459             * @param  userId the primary key of the user restoring the web content
3460             *         article
3461             * @param  article the web content article
3462             * @throws PortalException if the web content article with the primary key
3463             *         could not be found in the Recycle Bin, if the user did not have
3464             *         permission to restore the article, or if a portal exception
3465             *         occurred
3466             * @throws SystemException if a system exception occurred
3467             */
3468            @Indexable(type = IndexableType.REINDEX)
3469            @Override
3470            public JournalArticle restoreArticleFromTrash(
3471                            long userId, JournalArticle article)
3472                    throws PortalException, SystemException {
3473    
3474                    String trashArticleId = TrashUtil.getOriginalTitle(
3475                            article.getArticleId());
3476    
3477                    List<JournalArticle> articleVersions =
3478                            journalArticlePersistence.findByG_A(
3479                                    article.getGroupId(), article.getArticleId());
3480    
3481                    if (!articleVersions.isEmpty()) {
3482                            for (JournalArticle articleVersion : articleVersions) {
3483                                    articleVersion.setArticleId(trashArticleId);
3484    
3485                                    journalArticlePersistence.update(articleVersion);
3486                            }
3487                    }
3488    
3489                    article.setArticleId(trashArticleId);
3490    
3491                    journalArticlePersistence.update(article);
3492    
3493                    JournalArticleResource articleResource =
3494                            journalArticleResourcePersistence.fetchByPrimaryKey(
3495                                    article.getResourcePrimKey());
3496    
3497                    articleResource.setArticleId(trashArticleId);
3498    
3499                    journalArticleResourcePersistence.update(articleResource);
3500    
3501                    TrashEntry trashEntry = trashEntryLocalService.getEntry(
3502                            JournalArticle.class.getName(), article.getResourcePrimKey());
3503    
3504                    Map<String, Serializable> workflowContext =
3505                            new HashMap<String, Serializable>();
3506    
3507                    List<TrashVersion> trashVersions = trashVersionLocalService.getVersions(
3508                            trashEntry.getEntryId());
3509    
3510                    workflowContext.put("trashVersions", (Serializable)trashVersions);
3511    
3512                    ServiceContext serviceContext = new ServiceContext();
3513    
3514                    serviceContext.setScopeGroupId(article.getGroupId());
3515    
3516                    updateStatus(
3517                            userId, article, trashEntry.getStatus(), null, workflowContext,
3518                            serviceContext);
3519    
3520                    JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();
3521    
3522                    extraDataJSONObject.put("title", article.getTitle());
3523    
3524                    socialActivityLocalService.addActivity(
3525                            userId, article.getGroupId(), JournalArticle.class.getName(),
3526                            article.getResourcePrimKey(),
3527                            SocialActivityConstants.TYPE_RESTORE_FROM_TRASH,
3528                            extraDataJSONObject.toString(), 0);
3529    
3530                    if (!articleVersions.isEmpty()) {
3531                            Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
3532                                    JournalArticle.class);
3533    
3534                            for (JournalArticle articleVersion : articleVersions) {
3535                                    indexer.reindex(articleVersion);
3536                            }
3537                    }
3538    
3539                    return article;
3540            }
3541    
3542            /**
3543             * Returns an ordered range of all the web content articles matching the
3544             * parameters without using the indexer, including a keywords parameter for
3545             * matching with the article's ID, title, description, and content, a DDM
3546             * structure key parameter, and a DDM template key parameter. It is
3547             * preferable to use the indexed version {@link #search(long, long, List,
3548             * long, String, String, String, LinkedHashMap, int, int, Sort)} instead of
3549             * this method wherever possible for performance reasons.
3550             *
3551             * <p>
3552             * Useful when paginating results. Returns a maximum of <code>end -
3553             * start</code> instances. <code>start</code> and <code>end</code> are not
3554             * primary keys, they are indexes in the result set. Thus, <code>0</code>
3555             * refers to the first result in the set. Setting both <code>start</code>
3556             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
3557             * result set.
3558             * </p>
3559             *
3560             * @param  companyId the primary key of the web content article's company
3561             * @param  groupId the primary key of the group (optionally <code>0</code>)
3562             * @param  folderIds the primary keys of the web content article folders
3563             *         (optionally {@link java.util.Collections#EMPTY_LIST})
3564             * @param  classNameId the primary key of the DDMStructure class if the web
3565             *         content article is related to a DDM structure, the primary key of
3566             *         the class name associated with the article, or {@link
3567             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
3568             * @param  keywords the keywords (space separated), which may occur in the
3569             *         web content article ID, title, description, or content
3570             *         (optionally <code>null</code>). If the keywords value is not
3571             *         <code>null</code>, the search uses the OR operator in connecting
3572             *         query criteria; otherwise it uses the AND operator.
3573             * @param  version the web content article's version (optionally
3574             *         <code>null</code>)
3575             * @param  type the web content article's type (optionally
3576             *         <code>null</code>)
3577             * @param  ddmStructureKey the primary key of the web content article's DDM
3578             *         structure, if the article is related to a DDM structure, or
3579             *         <code>null</code> otherwise
3580             * @param  ddmTemplateKey the primary key of the web content article's DDM
3581             *         template (optionally <code>null</code>). If the article is
3582             *         related to a DDM structure, the template's structure must match
3583             *         it.
3584             * @param  displayDateGT the date after which a matching web content
3585             *         article's display date must be after (optionally
3586             *         <code>null</code>)
3587             * @param  displayDateLT the date before which a matching web content
3588             *         article's display date must be before (optionally
3589             *         <code>null</code>)
3590             * @param  status the web content article's workflow status. For more
3591             *         information see {@link WorkflowConstants} for constants starting
3592             *         with the "STATUS_" prefix.
3593             * @param  reviewDate the web content article's scheduled review date
3594             *         (optionally <code>null</code>)
3595             * @param  start the lower bound of the range of web content articles to
3596             *         return
3597             * @param  end the upper bound of the range of web content articles to
3598             *         return (not inclusive)
3599             * @param  obc the comparator to order the web content articles
3600             * @return the range of matching web content articles ordered by the
3601             *         comparator
3602             * @throws SystemException if a system exception occurred
3603             */
3604            @Override
3605            public List<JournalArticle> search(
3606                            long companyId, long groupId, List<Long> folderIds,
3607                            long classNameId, String keywords, Double version, String type,
3608                            String ddmStructureKey, String ddmTemplateKey, Date displayDateGT,
3609                            Date displayDateLT, int status, Date reviewDate, int start, int end,
3610                            OrderByComparator obc)
3611                    throws SystemException {
3612    
3613                    return journalArticleFinder.findByKeywords(
3614                            companyId, groupId, folderIds, classNameId, keywords, version, type,
3615                            ddmStructureKey, ddmTemplateKey, displayDateGT, displayDateLT,
3616                            status, reviewDate, start, end, obc);
3617            }
3618    
3619            /**
3620             * Returns an ordered range of all the web content articles matching the
3621             * parameters without using the indexer, including keyword parameters for
3622             * article ID, title, description, and content, a DDM structure key
3623             * parameter, a DDM template key parameter, and an AND operator switch. It
3624             * is preferable to use the indexed version {@link #search(long, long, List,
3625             * long, String, String, String, String, String, String, String, String,
3626             * LinkedHashMap, boolean, int, int, Sort)} instead of this method wherever
3627             * possible for performance reasons.
3628             *
3629             * <p>
3630             * Useful when paginating results. Returns a maximum of <code>end -
3631             * start</code> instances. <code>start</code> and <code>end</code> are not
3632             * primary keys, they are indexes in the result set. Thus, <code>0</code>
3633             * refers to the first result in the set. Setting both <code>start</code>
3634             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
3635             * result set.
3636             * </p>
3637             *
3638             * @param  companyId the primary key of the web content article's company
3639             * @param  groupId the primary key of the group (optionally <code>0</code>)
3640             * @param  folderIds the primary keys of the web content article folders
3641             *         (optionally {@link java.util.Collections#EMPTY_LIST})
3642             * @param  classNameId the primary key of the DDMStructure class if the web
3643             *         content article is related to a DDM structure, the primary key of
3644             *         the class name associated with the article, or {@link
3645             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
3646             * @param  articleId the article ID keywords (space separated, optionally
3647             *         <code>null</code>)
3648             * @param  version the web content article's version (optionally
3649             *         <code>null</code>)
3650             * @param  title the title keywords (space separated, optionally
3651             *         <code>null</code>)
3652             * @param  description the description keywords (space separated, optionally
3653             *         <code>null</code>)
3654             * @param  content the content keywords (space separated, optionally
3655             *         <code>null</code>)
3656             * @param  type the web content article's type (optionally
3657             *         <code>null</code>)
3658             * @param  ddmStructureKey the primary key of the web content article's DDM
3659             *         structure, if the article is related to a DDM structure, or
3660             *         <code>null</code> otherwise
3661             * @param  ddmTemplateKey the primary key of the web content article's DDM
3662             *         template (optionally <code>null</code>). If the article is
3663             *         related to a DDM structure, the template's structure must match
3664             *         it.
3665             * @param  displayDateGT the date after which a matching web content
3666             *         article's display date must be after (optionally
3667             *         <code>null</code>)
3668             * @param  displayDateLT the date before which a matching web content
3669             *         article's display date must be before (optionally
3670             *         <code>null</code>)
3671             * @param  status the web content article's workflow status. For more
3672             *         information see {@link WorkflowConstants} for constants starting
3673             *         with the "STATUS_" prefix.
3674             * @param  reviewDate the web content article's scheduled review date
3675             *         (optionally <code>null</code>)
3676             * @param  andOperator whether every field must match its value or keywords,
3677             *         or just one field must match. Company, group, folder IDs, class
3678             *         name ID, and status must all match their values.
3679             * @param  start the lower bound of the range of web content articles to
3680             *         return
3681             * @param  end the upper bound of the range of web content articles to
3682             *         return (not inclusive)
3683             * @param  obc the comparator to order the web content articles
3684             * @return the range of matching web content articles ordered by the
3685             *         comparator
3686             * @throws SystemException if a system exception occurred
3687             */
3688            @Override
3689            public List<JournalArticle> search(
3690                            long companyId, long groupId, List<Long> folderIds,
3691                            long classNameId, String articleId, Double version, String title,
3692                            String description, String content, String type,
3693                            String ddmStructureKey, String ddmTemplateKey, Date displayDateGT,
3694                            Date displayDateLT, int status, Date reviewDate,
3695                            boolean andOperator, int start, int end, OrderByComparator obc)
3696                    throws SystemException {
3697    
3698                    QueryDefinition queryDefinition = new QueryDefinition(
3699                            status, start, end, obc);
3700    
3701                    return journalArticleFinder.findByC_G_F_C_A_V_T_D_C_T_S_T_D_R(
3702                            companyId, groupId, folderIds, classNameId, articleId, version,
3703                            title, description, content, type, ddmStructureKey, ddmTemplateKey,
3704                            displayDateGT, displayDateLT, reviewDate, andOperator,
3705                            queryDefinition);
3706            }
3707    
3708            /**
3709             * Returns an ordered range of all the web content articles matching the
3710             * parameters without using the indexer, including keyword parameters for
3711             * article ID, title, description, and content, a DDM structure keys
3712             * (plural) parameter, a DDM template keys (plural) parameter, and an AND
3713             * operator switch.
3714             *
3715             * <p>
3716             * Useful when paginating results. Returns a maximum of <code>end -
3717             * start</code> instances. <code>start</code> and <code>end</code> are not
3718             * primary keys, they are indexes in the result set. Thus, <code>0</code>
3719             * refers to the first result in the set. Setting both <code>start</code>
3720             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
3721             * result set.
3722             * </p>
3723             *
3724             * @param  companyId the primary key of the web content article's company
3725             * @param  groupId the primary key of the group (optionally <code>0</code>)
3726             * @param  folderIds the primary keys of the web content article folders
3727             *         (optionally {@link java.util.Collections#EMPTY_LIST})
3728             * @param  classNameId the primary key of the DDMStructure class if the web
3729             *         content article is related to a DDM structure, the primary key of
3730             *         the class name associated with the article, or {@link
3731             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
3732             * @param  articleId the article ID keywords (space separated, optionally
3733             *         <code>null</code>)
3734             * @param  version the web content article's version (optionally
3735             *         <code>null</code>)
3736             * @param  title the title keywords (space separated, optionally
3737             *         <code>null</code>)
3738             * @param  description the description keywords (space separated, optionally
3739             *         <code>null</code>)
3740             * @param  content the content keywords (space separated, optionally
3741             *         <code>null</code>)
3742             * @param  type the web content article's type (optionally
3743             *         <code>null</code>)
3744             * @param  ddmStructureKeys the primary keys of the web content article's
3745             *         DDM structures, if the article is related to a DDM structure, or
3746             *         <code>null</code> otherwise
3747             * @param  ddmTemplateKeys the primary keys of the web content article's DDM
3748             *         templates (originally <code>null</code>). If the articles are
3749             *         related to a DDM structure, the template's structure must match
3750             *         it.
3751             * @param  displayDateGT the date after which a matching web content
3752             *         article's display date must be after (optionally
3753             *         <code>null</code>)
3754             * @param  displayDateLT the date before which a matching web content
3755             *         article's display date must be before (optionally
3756             *         <code>null</code>)
3757             * @param  status the web content article's workflow status. For more
3758             *         information see {@link WorkflowConstants} for constants starting
3759             *         with the "STATUS_" prefix.
3760             * @param  reviewDate the web content article's scheduled review date
3761             *         (optionally <code>null</code>)
3762             * @param  andOperator whether every field must match its value or keywords,
3763             *         or just one field must match.  Company, group, folder IDs, class
3764             *         name ID, and status must all match their values.
3765             * @param  start the lower bound of the range of web content articles to
3766             *         return
3767             * @param  end the upper bound of the range of web content articles to
3768             *         return (not inclusive)
3769             * @param  obc the comparator to order the web content articles
3770             * @return the range of matching web content articles ordered by the
3771             *         comparator
3772             * @throws SystemException if a system exception occurred
3773             */
3774            @Override
3775            public List<JournalArticle> search(
3776                            long companyId, long groupId, List<Long> folderIds,
3777                            long classNameId, String articleId, Double version, String title,
3778                            String description, String content, String type,
3779                            String[] ddmStructureKeys, String[] ddmTemplateKeys,
3780                            Date displayDateGT, Date displayDateLT, int status, Date reviewDate,
3781                            boolean andOperator, int start, int end, OrderByComparator obc)
3782                    throws SystemException {
3783    
3784                    QueryDefinition queryDefinition = new QueryDefinition(
3785                            status, start, end, obc);
3786    
3787                    return journalArticleFinder.findByC_G_F_C_A_V_T_D_C_T_S_T_D_R(
3788                            companyId, groupId, folderIds, classNameId, articleId, version,
3789                            title, description, content, type, ddmStructureKeys,
3790                            ddmTemplateKeys, displayDateGT, displayDateLT, reviewDate,
3791                            andOperator, queryDefinition);
3792            }
3793    
3794            /**
3795             * Returns an ordered range of all the web content articles matching the
3796             * parameters using the indexer, including a keywords parameter for matching
3797             * an article's ID, title, description, or content, a DDM structure key
3798             * parameter, a DDM template key parameter, and a finder hash map parameter.
3799             * It is preferable to use this method instead of the non-indexed version
3800             * whenever possible for performance reasons.
3801             *
3802             * <p>
3803             * Useful when paginating results. Returns a maximum of <code>end -
3804             * start</code> instances. <code>start</code> and <code>end</code> are not
3805             * primary keys, they are indexes in the result set. Thus, <code>0</code>
3806             * refers to the first result in the set. Setting both <code>start</code>
3807             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
3808             * result set.
3809             * </p>
3810             *
3811             * @param  companyId the primary key of the web content article's company
3812             * @param  groupId the primary key of the group (optionally <code>0</code>)
3813             * @param  folderIds the primary keys of the web content article folders
3814             *         (optionally {@link java.util.Collections#EMPTY_LIST})
3815             * @param  classNameId the primary key of the DDMStructure class if the web
3816             *         content article is related to a DDM structure, the primary key of
3817             *         the class name associated with the article, or {@link
3818             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
3819             * @param  ddmStructureKey the primary key of the web content article's DDM
3820             *         structure, if the article is related to a DDM structure, or
3821             *         <code>null</code> otherwise
3822             * @param  ddmTemplateKey the primary key of the web content article's DDM
3823             *         template (optionally <code>null</code>). If the article is
3824             *         related to a DDM structure, the template's structure must match
3825             *         it.
3826             * @param  keywords the keywords (space separated), which may occur in the
3827             *         web content article ID, title, description, or content
3828             *         (optionally <code>null</code>). If the keywords value is not
3829             *         <code>null</code>, the search uses the OR operator in connecting
3830             *         query criteria; otherwise it uses the AND operator.
3831             * @param  params the finder parameters (optionally <code>null</code>)
3832             * @param  start the lower bound of the range of web content articles to
3833             *         return
3834             * @param  end the upper bound of the range of web content articles to
3835             *         return (not inclusive)
3836             * @param  sort the field, type, and direction by which to sort (optionally
3837             *         <code>null</code>)
3838             * @return the matching web content articles ordered by <code>sort</code>
3839             * @throws SystemException if a system exception occurred
3840             */
3841            @Override
3842            public Hits search(
3843                            long companyId, long groupId, List<Long> folderIds,
3844                            long classNameId, String ddmStructureKey, String ddmTemplateKey,
3845                            String keywords, LinkedHashMap<String, Object> params, int start,
3846                            int end, Sort sort)
3847                    throws SystemException {
3848    
3849                    String articleId = null;
3850                    String title = null;
3851                    String description = null;
3852                    String content = null;
3853                    boolean andOperator = false;
3854    
3855                    if (Validator.isNotNull(keywords)) {
3856                            articleId = keywords;
3857                            title = keywords;
3858                            description = keywords;
3859                            content = keywords;
3860                    }
3861                    else {
3862                            andOperator = true;
3863                    }
3864    
3865                    String status = String.valueOf(WorkflowConstants.STATUS_ANY);
3866    
3867                    if (params != null) {
3868                            params.put("keywords", keywords);
3869                    }
3870    
3871                    return search(
3872                            companyId, groupId, folderIds, classNameId, articleId, title,
3873                            description, content, null, status, ddmStructureKey, ddmTemplateKey,
3874                            params, andOperator, start, end, sort);
3875            }
3876    
3877            /**
3878             * Returns an ordered range of all the web content articles matching the
3879             * parameters using the indexer, including a keywords parameter for matching
3880             * an article's ID, title, description, or content, a DDM structure key
3881             * parameter, a DDM template key parameter, an AND operator switch, and
3882             * parameters for type, status, a finder hash map. It is preferable to use
3883             * this method instead of the non-indexed version whenever possible for
3884             * performance reasons.
3885             *
3886             * <p>
3887             * Useful when paginating results. Returns a maximum of <code>end -
3888             * start</code> instances. <code>start</code> and <code>end</code> are not
3889             * primary keys, they are indexes in the result set. Thus, <code>0</code>
3890             * refers to the first result in the set. Setting both <code>start</code>
3891             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
3892             * result set.
3893             * </p>
3894             *
3895             * @param  companyId the primary key of the web content article's company
3896             * @param  groupId the primary key of the group (optionally <code>0</code>)
3897             * @param  folderIds the primary keys of the web content article folders
3898             *         (optionally {@link java.util.Collections#EMPTY_LIST})
3899             * @param  classNameId the primary key of the DDMStructure class if the web
3900             *         content article is related to a DDM structure, the primary key of
3901             *         the class name associated with the article, or {@link
3902             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
3903             * @param  articleId the article ID keywords (space separated, optionally
3904             *         <code>null</code>)
3905             * @param  title the title keywords (space separated, optionally
3906             *         <code>null</code>)
3907             * @param  description the description keywords (space separated, optionally
3908             *         <code>null</code>)
3909             * @param  content the content keywords (space separated, optionally
3910             *         <code>null</code>)
3911             * @param  type the web content article's type (optionally
3912             *         <code>null</code>)
3913             * @param  status the web content article's workflow status. For more
3914             *         information see {@link WorkflowConstants} for constants starting
3915             *         with the "STATUS_" prefix.
3916             * @param  ddmStructureKey the primary key of the web content article's DDM
3917             *         structure, if the article is related to a DDM structure, or
3918             *         <code>null</code> otherwise
3919             * @param  ddmTemplateKey the primary key of the web content article's DDM
3920             *         template (optionally <code>null</code>). If the article is
3921             *         related to a DDM structure, the template's structure must match
3922             *         it.
3923             * @param  params the finder parameters (optionally <code>null</code>). Can
3924             *         set parameter <code>"includeDiscussions"</code> to
3925             *         <code>true</code> to search for the keywords in the web content
3926             *         article discussions.
3927             * @param  andSearch whether every field must match its value or keywords,
3928             *         or just one field must match
3929             * @param  start the lower bound of the range of web content articles to
3930             *         return
3931             * @param  end the upper bound of the range of web content articles to
3932             *         return (not inclusive)
3933             * @param  sort the field, type, and direction by which to sort (optionally
3934             *         <code>null</code>)
3935             * @return the matching web content articles ordered by <code>sort</code>
3936             * @throws SystemException if a system exception occurred
3937             */
3938            @Override
3939            public Hits search(
3940                            long companyId, long groupId, List<Long> folderIds,
3941                            long classNameId, String articleId, String title,
3942                            String description, String content, String type, String status,
3943                            String ddmStructureKey, String ddmTemplateKey,
3944                            LinkedHashMap<String, Object> params, boolean andSearch, int start,
3945                            int end, Sort sort)
3946                    throws SystemException {
3947    
3948                    try {
3949                            SearchContext searchContext = new SearchContext();
3950    
3951                            searchContext.setAndSearch(andSearch);
3952    
3953                            Map<String, Serializable> attributes =
3954                                    new HashMap<String, Serializable>();
3955    
3956                            attributes.put(Field.CLASS_NAME_ID, classNameId);
3957                            attributes.put(Field.CONTENT, content);
3958                            attributes.put(Field.DESCRIPTION, description);
3959                            attributes.put(Field.STATUS, status);
3960                            attributes.put(Field.TITLE, title);
3961                            attributes.put(Field.TYPE, type);
3962                            attributes.put("articleId", articleId);
3963                            attributes.put("ddmStructureKey", ddmStructureKey);
3964                            attributes.put("ddmTemplateKey", ddmTemplateKey);
3965                            attributes.put("params", params);
3966    
3967                            searchContext.setAttributes(attributes);
3968    
3969                            searchContext.setCompanyId(companyId);
3970                            searchContext.setEnd(end);
3971                            searchContext.setFolderIds(folderIds);
3972                            searchContext.setGroupIds(new long[] {groupId});
3973                            searchContext.setIncludeDiscussions(
3974                                    GetterUtil.getBoolean(params.get("includeDiscussions")));
3975    
3976                            if (params != null) {
3977                                    String keywords = (String)params.remove("keywords");
3978    
3979                                    if (Validator.isNotNull(keywords)) {
3980                                            searchContext.setKeywords(keywords);
3981                                    }
3982                            }
3983    
3984                            QueryConfig queryConfig = new QueryConfig();
3985    
3986                            queryConfig.setHighlightEnabled(false);
3987                            queryConfig.setScoreEnabled(false);
3988    
3989                            searchContext.setQueryConfig(queryConfig);
3990    
3991                            if (sort != null) {
3992                                    searchContext.setSorts(sort);
3993                            }
3994    
3995                            searchContext.setStart(start);
3996    
3997                            Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
3998                                    JournalArticle.class);
3999    
4000                            return indexer.search(searchContext);
4001                    }
4002                    catch (Exception e) {
4003                            throw new SystemException(e);
4004                    }
4005            }
4006    
4007            @Override
4008            public Hits search(
4009                            long groupId, long userId, long creatorUserId, int status,
4010                            int start, int end)
4011                    throws PortalException, SystemException {
4012    
4013                    Indexer indexer = IndexerRegistryUtil.getIndexer(
4014                            JournalArticle.class.getName());
4015    
4016                    SearchContext searchContext = new SearchContext();
4017    
4018                    searchContext.setAttribute(Field.STATUS, status);
4019    
4020                    searchContext.setAttribute("paginationType", "none");
4021    
4022                    if (creatorUserId > 0) {
4023                            searchContext.setAttribute(
4024                                    Field.USER_ID, String.valueOf(creatorUserId));
4025                    }
4026    
4027                    Group group = groupLocalService.getGroup(groupId);
4028    
4029                    searchContext.setCompanyId(group.getCompanyId());
4030    
4031                    searchContext.setEnd(end);
4032                    searchContext.setGroupIds(new long[] {groupId});
4033                    searchContext.setSorts(new Sort(Field.MODIFIED_DATE, true));
4034                    searchContext.setStart(start);
4035                    searchContext.setUserId(userId);
4036    
4037                    return indexer.search(searchContext);
4038            }
4039    
4040            /**
4041             * Returns the number of web content articles matching the parameters,
4042             * including a keywords parameter for matching with the article's ID, title,
4043             * description, and content, a DDM structure key parameter, and a DDM
4044             * template key parameter.
4045             *
4046             * @param  companyId the primary key of the web content article's company
4047             * @param  groupId the primary key of the group (optionally <code>0</code>)
4048             * @param  folderIds the primary keys of the web content article folders
4049             *         (optionally {@link java.util.Collections#EMPTY_LIST})
4050             * @param  classNameId the primary key of the DDMStructure class if the web
4051             *         content article is related to a DDM structure, the primary key of
4052             *         the class name associated with the article, or {@link
4053             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
4054             * @param  keywords the keywords (space separated), which may occur in the
4055             *         web content article ID, title, description, or content
4056             *         (optionally <code>null</code>). If the keywords value is not
4057             *         <code>null</code>, the search uses the OR operator in connecting
4058             *         query criteria; otherwise it uses the AND operator.
4059             * @param  version the web content article's version (optionally
4060             *         <code>null</code>)
4061             * @param  type the web content article's type (optionally
4062             *         <code>null</code>)
4063             * @param  ddmStructureKey the primary key of the web content article's DDM
4064             *         structure, if the article is related to a DDM structure, or
4065             *         <code>null</code> otherwise
4066             * @param  ddmTemplateKey the primary key of the web content article's DDM
4067             *         template (optionally <code>null</code>). If the article is
4068             *         related to a DDM structure, the template's structure must match
4069             *         it.
4070             * @param  displayDateGT the date after which a matching web content
4071             *         article's display date must be after (optionally
4072             *         <code>null</code>)
4073             * @param  displayDateLT the date before which a matching web content
4074             *         article's display date must be before (optionally
4075             *         <code>null</code>)
4076             * @param  status the web content article's workflow status. For more
4077             *         information see {@link WorkflowConstants} for constants starting
4078             *         with the "STATUS_" prefix.
4079             * @param  reviewDate the web content article's scheduled review date
4080             *         (optionally <code>null</code>)
4081             * @return the number of matching web content articles
4082             * @throws SystemException if a system exception occurred
4083             */
4084            @Override
4085            public int searchCount(
4086                            long companyId, long groupId, List<Long> folderIds,
4087                            long classNameId, String keywords, Double version, String type,
4088                            String ddmStructureKey, String ddmTemplateKey, Date displayDateGT,
4089                            Date displayDateLT, int status, Date reviewDate)
4090                    throws SystemException {
4091    
4092                    return journalArticleFinder.countByKeywords(
4093                            companyId, groupId, folderIds, classNameId, keywords, version, type,
4094                            ddmStructureKey, ddmTemplateKey, displayDateGT, displayDateLT,
4095                            status, reviewDate);
4096            }
4097    
4098            /**
4099             * Returns the number of web content articles matching the parameters,
4100             * including keyword parameters for article ID, title, description, and
4101             * content, a DDM structure key parameter, a DDM template key parameter, and
4102             * an AND operator switch.
4103             *
4104             * @param  companyId the primary key of the web content article's company
4105             * @param  groupId the primary key of the group (optionally <code>0</code>)
4106             * @param  folderIds the primary keys of the web content article folders
4107             *         (optionally {@link java.util.Collections#EMPTY_LIST})
4108             * @param  classNameId the primary key of the DDMStructure class if the web
4109             *         content article is related to a DDM structure, the primary key of
4110             *         the class name associated with the article, or {@link
4111             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
4112             * @param  articleId the article ID keywords (space separated, optionally
4113             *         <code>null</code>)
4114             * @param  version the web content article's version (optionally
4115             *         <code>null</code>)
4116             * @param  title the title keywords (space separated, optionally
4117             *         <code>null</code>)
4118             * @param  description the description keywords (space separated, optionally
4119             *         <code>null</code>)
4120             * @param  content the content keywords (space separated, optionally
4121             *         <code>null</code>)
4122             * @param  type the web content article's type (optionally
4123             *         <code>null</code>)
4124             * @param  ddmStructureKey the primary key of the web content article's DDM
4125             *         structure, if the article is related to a DDM structure, or
4126             *         <code>null</code> otherwise
4127             * @param  ddmTemplateKey the primary key of the web content article's DDM
4128             *         template (optionally <code>null</code>). If the article is
4129             *         related to a DDM structure, the template's structure must match
4130             *         it.
4131             * @param  displayDateGT the date after which a matching web content
4132             *         article's display date must be after (optionally
4133             *         <code>null</code>)
4134             * @param  displayDateLT the date before which a matching web content
4135             *         article's display date must be before (optionally
4136             *         <code>null</code>)
4137             * @param  status the web content article's workflow status. For more
4138             *         information see {@link WorkflowConstants} for constants starting
4139             *         with the "STATUS_" prefix.
4140             * @param  reviewDate the web content article's scheduled review date
4141             *         (optionally <code>null</code>)
4142             * @param  andOperator whether every field must match its value or keywords,
4143             *         or just one field must match. Group, folder IDs, class name ID,
4144             *         and status must all match their values.
4145             * @return the number of matching web content articles
4146             * @throws SystemException if a system exception occurred
4147             */
4148            @Override
4149            public int searchCount(
4150                            long companyId, long groupId, List<Long> folderIds,
4151                            long classNameId, String articleId, Double version, String title,
4152                            String description, String content, String type,
4153                            String ddmStructureKey, String ddmTemplateKey, Date displayDateGT,
4154                            Date displayDateLT, int status, Date reviewDate,
4155                            boolean andOperator)
4156                    throws SystemException {
4157    
4158                    return journalArticleFinder.countByC_G_F_C_A_V_T_D_C_T_S_T_D_R(
4159                            companyId, groupId, folderIds, classNameId, articleId, version,
4160                            title, description, content, type, ddmStructureKey, ddmTemplateKey,
4161                            displayDateGT, displayDateLT, reviewDate, andOperator,
4162                            new QueryDefinition(status));
4163            }
4164    
4165            /**
4166             * Returns the number of web content articles matching the parameters,
4167             * including keyword parameters for article ID, title, description, and
4168             * content, a DDM structure keys (plural) parameter, a DDM template keys
4169             * (plural) parameter, and an AND operator switch.
4170             *
4171             * @param  companyId the primary key of the web content article's company
4172             * @param  groupId the primary key of the group (optionally <code>0</code>)
4173             * @param  folderIds the primary keys of the web content article folders
4174             *         (optionally {@link java.util.Collections#EMPTY_LIST})
4175             * @param  classNameId the primary key of the DDMStructure class if the web
4176             *         content article is related to a DDM structure, the primary key of
4177             *         the class name associated with the article, or {@link
4178             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
4179             * @param  articleId the article ID keywords (space separated, optionally
4180             *         <code>null</code>)
4181             * @param  version the web content article's version (optionally
4182             *         <code>null</code>)
4183             * @param  title the title keywords (space separated, optionally
4184             *         <code>null</code>)
4185             * @param  description the description keywords (space separated, optionally
4186             *         <code>null</code>)
4187             * @param  content the content keywords (space separated, optionally
4188             *         <code>null</code>)
4189             * @param  type the web content article's type (optionally
4190             *         <code>null</code>)
4191             * @param  ddmStructureKeys the primary keys of the web content article's
4192             *         DDM structures, if the article is related to a DDM structure, or
4193             *         <code>null</code> otherwise
4194             * @param  ddmTemplateKeys the primary keys of the web content article's DDM
4195             *         templates (originally <code>null</code>). If the articles are
4196             *         related to a DDM structure, the template's structure must match
4197             *         it.
4198             * @param  displayDateGT the date after which a matching web content
4199             *         article's display date must be after (optionally
4200             *         <code>null</code>)
4201             * @param  displayDateLT the date before which a matching web content
4202             *         article's display date must be before (optionally
4203             *         <code>null</code>)
4204             * @param  status the web content article's workflow status. For more
4205             *         information see {@link WorkflowConstants} for constants starting
4206             *         with the "STATUS_" prefix.
4207             * @param  reviewDate the web content article's scheduled review date
4208             *         (optionally <code>null</code>)
4209             * @param  andOperator whether every field must match its value or keywords,
4210             *         or just one field must match.  Group, folder IDs, class name ID,
4211             *         and status must all match their values.
4212             * @return the number of matching web content articles
4213             * @throws SystemException if a system exception occurred
4214             */
4215            @Override
4216            public int searchCount(
4217                            long companyId, long groupId, List<Long> folderIds,
4218                            long classNameId, String articleId, Double version, String title,
4219                            String description, String content, String type,
4220                            String[] ddmStructureKeys, String[] ddmTemplateKeys,
4221                            Date displayDateGT, Date displayDateLT, int status, Date reviewDate,
4222                            boolean andOperator)
4223                    throws SystemException {
4224    
4225                    return journalArticleFinder.countByC_G_F_C_A_V_T_D_C_T_S_T_D_R(
4226                            companyId, groupId, folderIds, classNameId, articleId, version,
4227                            title, description, content, type, ddmStructureKeys,
4228                            ddmTemplateKeys, displayDateGT, displayDateLT, reviewDate,
4229                            andOperator, new QueryDefinition(status));
4230            }
4231    
4232            /**
4233             * Subscribes the user to notifications for the web content article matching
4234             * the group, notifying him the instant versions of the article are created,
4235             * deleted, or modified.
4236             *
4237             * @param  userId the primary key of the user to subscribe
4238             * @param  groupId the primary key of the group
4239             * @throws PortalException if a matching user or group could not be found
4240             * @throws SystemException if a system exception occurred
4241             */
4242            @Override
4243            public void subscribe(long userId, long groupId)
4244                    throws PortalException, SystemException {
4245    
4246                    subscriptionLocalService.addSubscription(
4247                            userId, groupId, JournalArticle.class.getName(), groupId);
4248            }
4249    
4250            /**
4251             * Unsubscribes the user from notifications for the web content article
4252             * matching the group.
4253             *
4254             * @param  userId the primary key of the user to unsubscribe
4255             * @param  groupId the primary key of the group
4256             * @throws PortalException if a matching user or subscription could not be
4257             *         found
4258             * @throws SystemException if a system exception occurred
4259             */
4260            @Override
4261            public void unsubscribe(long userId, long groupId)
4262                    throws PortalException, SystemException {
4263    
4264                    subscriptionLocalService.deleteSubscription(
4265                            userId, JournalArticle.class.getName(), groupId);
4266            }
4267    
4268            /**
4269             * Updates the web content article matching the version, replacing its
4270             * folder, title, description, content, and layout UUID.
4271             *
4272             * @param  userId the primary key of the user updating the web content
4273             *         article
4274             * @param  groupId the primary key of the web content article's group
4275             * @param  folderId the primary key of the web content article folder
4276             * @param  articleId the primary key of the web content article
4277             * @param  version the web content article's version
4278             * @param  titleMap the web content article's locales and localized titles
4279             * @param  descriptionMap the web content article's locales and localized
4280             *         descriptions
4281             * @param  content the HTML content wrapped in XML. For more information,
4282             *         see the content example in the class description for {@link
4283             *         JournalArticleLocalServiceImpl}.
4284             * @param  layoutUuid the unique string identifying the web content
4285             *         article's display page
4286             * @param  serviceContext the service context to be applied. Can set the
4287             *         modification date, expando bridge attributes, asset category IDs,
4288             *         asset tag names, asset link entry IDs, workflow actions, the
4289             *         "defaultLanguageId" and "urlTitle" attributes, and can set
4290             *         whether to add the default command update for the web content
4291             *         article. With respect to social activities, by setting the
4292             *         service context's command to {@link
4293             *         com.liferay.portal.kernel.util.Constants#UPDATE}, the invocation
4294             *         is considered a web content update activity; otherwise it is
4295             *         considered a web content add activity.
4296             * @return the updated web content article
4297             * @throws PortalException if a user with the primary key or a matching web
4298             *         content article could not be found, or if a portal exception
4299             *         occurred
4300             * @throws SystemException if a system exception occurred
4301             */
4302            @Override
4303            public JournalArticle updateArticle(
4304                            long userId, long groupId, long folderId, String articleId,
4305                            double version, Map<Locale, String> titleMap,
4306                            Map<Locale, String> descriptionMap, String content,
4307                            String layoutUuid, ServiceContext serviceContext)
4308                    throws PortalException, SystemException {
4309    
4310                    User user = userPersistence.findByPrimaryKey(userId);
4311    
4312                    JournalArticle article = journalArticlePersistence.findByG_A_V(
4313                            groupId, articleId, version);
4314    
4315                    Date displayDate = article.getDisplayDate();
4316    
4317                    int displayDateMonth = 0;
4318                    int displayDateDay = 0;
4319                    int displayDateYear = 0;
4320                    int displayDateHour = 0;
4321                    int displayDateMinute = 0;
4322    
4323                    if (displayDate != null) {
4324                            Calendar displayCal = CalendarFactoryUtil.getCalendar(
4325                                    user.getTimeZone());
4326    
4327                            displayCal.setTime(displayDate);
4328    
4329                            displayDateMonth = displayCal.get(Calendar.MONTH);
4330                            displayDateDay = displayCal.get(Calendar.DATE);
4331                            displayDateYear = displayCal.get(Calendar.YEAR);
4332                            displayDateHour = displayCal.get(Calendar.HOUR);
4333                            displayDateMinute = displayCal.get(Calendar.MINUTE);
4334    
4335                            if (displayCal.get(Calendar.AM_PM) == Calendar.PM) {
4336                                    displayDateHour += 12;
4337                            }
4338                    }
4339    
4340                    Date expirationDate = article.getExpirationDate();
4341    
4342                    int expirationDateMonth = 0;
4343                    int expirationDateDay = 0;
4344                    int expirationDateYear = 0;
4345                    int expirationDateHour = 0;
4346                    int expirationDateMinute = 0;
4347                    boolean neverExpire = true;
4348    
4349                    if (expirationDate != null) {
4350                            Calendar expirationCal = CalendarFactoryUtil.getCalendar(
4351                                    user.getTimeZone());
4352    
4353                            expirationCal.setTime(expirationDate);
4354    
4355                            expirationDateMonth = expirationCal.get(Calendar.MONTH);
4356                            expirationDateDay = expirationCal.get(Calendar.DATE);
4357                            expirationDateYear = expirationCal.get(Calendar.YEAR);
4358                            expirationDateHour = expirationCal.get(Calendar.HOUR);
4359                            expirationDateMinute = expirationCal.get(Calendar.MINUTE);
4360                            neverExpire = false;
4361    
4362                            if (expirationCal.get(Calendar.AM_PM) == Calendar.PM) {
4363                                    expirationDateHour += 12;
4364                            }
4365                    }
4366    
4367                    Date reviewDate = article.getReviewDate();
4368    
4369                    int reviewDateMonth = 0;
4370                    int reviewDateDay = 0;
4371                    int reviewDateYear = 0;
4372                    int reviewDateHour = 0;
4373                    int reviewDateMinute = 0;
4374                    boolean neverReview = true;
4375    
4376                    if (reviewDate != null) {
4377                            Calendar reviewCal = CalendarFactoryUtil.getCalendar(
4378                                    user.getTimeZone());
4379    
4380                            reviewCal.setTime(reviewDate);
4381    
4382                            reviewDateMonth = reviewCal.get(Calendar.MONTH);
4383                            reviewDateDay = reviewCal.get(Calendar.DATE);
4384                            reviewDateYear = reviewCal.get(Calendar.YEAR);
4385                            reviewDateHour = reviewCal.get(Calendar.HOUR);
4386                            reviewDateMinute = reviewCal.get(Calendar.MINUTE);
4387                            neverReview = false;
4388    
4389                            if (reviewCal.get(Calendar.AM_PM) == Calendar.PM) {
4390                                    reviewDateHour += 12;
4391                            }
4392                    }
4393    
4394                    return journalArticleLocalService.updateArticle(
4395                            userId, groupId, folderId, articleId, version, titleMap,
4396                            descriptionMap, content, article.getType(),
4397                            article.getStructureId(), article.getTemplateId(), layoutUuid,
4398                            displayDateMonth, displayDateDay, displayDateYear, displayDateHour,
4399                            displayDateMinute, expirationDateMonth, expirationDateDay,
4400                            expirationDateYear, expirationDateHour, expirationDateMinute,
4401                            neverExpire, reviewDateMonth, reviewDateDay, reviewDateYear,
4402                            reviewDateHour, reviewDateMinute, neverReview,
4403                            article.getIndexable(), article.isSmallImage(),
4404                            article.getSmallImageURL(), null, null, null, serviceContext);
4405            }
4406    
4407            /**
4408             * Updates the web content article with additional parameters.
4409             *
4410             * @param  userId the primary key of the user updating the web content
4411             *         article
4412             * @param  groupId the primary key of the web content article's group
4413             * @param  folderId the primary key of the web content article folder
4414             * @param  articleId the primary key of the web content article
4415             * @param  version the web content article's version
4416             * @param  titleMap the web content article's locales and localized titles
4417             * @param  descriptionMap the web content article's locales and localized
4418             *         descriptions
4419             * @param  content the HTML content wrapped in XML. For more information,
4420             *         see the content example in the class description for {@link
4421             *         JournalArticleLocalServiceImpl}.
4422             * @param  type the structure's type, if the web content article is related
4423             *         to a DDM structure. For more information, see {@link
4424             *         com.liferay.portlet.dynamicdatamapping.model.DDMStructureConstants}.
4425             * @param  ddmStructureKey the primary key of the web content article's DDM
4426             *         structure, if the article is related to a DDM structure, or
4427             *         <code>null</code> otherwise
4428             * @param  ddmTemplateKey the primary key of the web content article's DDM
4429             *         template (optionally <code>null</code>). If the article is
4430             *         related to a DDM structure, the template's structure must match
4431             *         it.
4432             * @param  layoutUuid the unique string identifying the web content
4433             *         article's display page
4434             * @param  displayDateMonth the month the web content article is set to
4435             *         display
4436             * @param  displayDateDay the calendar day the web content article is set to
4437             *         display
4438             * @param  displayDateYear the year the web content article is set to
4439             *         display
4440             * @param  displayDateHour the hour the web content article is set to
4441             *         display
4442             * @param  displayDateMinute the minute the web content article is set to
4443             *         display
4444             * @param  expirationDateMonth the month the web content article is set to
4445             *         expire
4446             * @param  expirationDateDay the calendar day the web content article is set
4447             *         to expire
4448             * @param  expirationDateYear the year the web content article is set to
4449             *         expire
4450             * @param  expirationDateHour the hour the web content article is set to
4451             *         expire
4452             * @param  expirationDateMinute the minute the web content article is set to
4453             *         expire
4454             * @param  neverExpire whether the web content article is not set to auto
4455             *         expire
4456             * @param  reviewDateMonth the month the web content article is set for
4457             *         review
4458             * @param  reviewDateDay the calendar day the web content article is set for
4459             *         review
4460             * @param  reviewDateYear the year the web content article is set for review
4461             * @param  reviewDateHour the hour the web content article is set for review
4462             * @param  reviewDateMinute the minute the web content article is set for
4463             *         review
4464             * @param  neverReview whether the web content article is not set for review
4465             * @param  indexable whether the web content is searchable
4466             * @param  smallImage whether to update web content article's a small image.
4467             *         A file must be passed in as <code>smallImageFile</code> value,
4468             *         otherwise the current small image is deleted.
4469             * @param  smallImageURL the web content article's small image URL
4470             *         (optionally <code>null</code>)
4471             * @param  smallImageFile the web content article's new small image file
4472             *         (optionally <code>null</code>). Must pass in
4473             *         <code>smallImage</code> value of <code>true</code> to replace the
4474             *         article's small image file.
4475             * @param  images the web content's images (optionally <code>null</code>)
4476             * @param  articleURL the web content article's accessible URL (optionally
4477             *         <code>null</code>)
4478             * @param  serviceContext the service context to be applied. Can set the
4479             *         modification date, expando bridge attributes, asset category IDs,
4480             *         asset tag names, asset link entry IDs, workflow actions, the
4481             *         "defaultLanguageId" and "urlTitle" attributes, and can set
4482             *         whether to add the default command update for the web content
4483             *         article. With respect to social activities, by setting the
4484             *         service context's command to {@link
4485             *         com.liferay.portal.kernel.util.Constants#UPDATE}, the invocation
4486             *         is considered a web content update activity; otherwise it is
4487             *         considered a web content add activity.
4488             * @return the updated web content article
4489             * @throws PortalException if a user with the primary key or a matching web
4490             *         content article could not be found, or if a portal exception
4491             *         occurred
4492             * @throws SystemException if a system exception occurred
4493             */
4494            @Indexable(type = IndexableType.REINDEX)
4495            @Override
4496            public JournalArticle updateArticle(
4497                            long userId, long groupId, long folderId, String articleId,
4498                            double version, Map<Locale, String> titleMap,
4499                            Map<Locale, String> descriptionMap, String content, String type,
4500                            String ddmStructureKey, String ddmTemplateKey, String layoutUuid,
4501                            int displayDateMonth, int displayDateDay, int displayDateYear,
4502                            int displayDateHour, int displayDateMinute, int expirationDateMonth,
4503                            int expirationDateDay, int expirationDateYear,
4504                            int expirationDateHour, int expirationDateMinute,
4505                            boolean neverExpire, int reviewDateMonth, int reviewDateDay,
4506                            int reviewDateYear, int reviewDateHour, int reviewDateMinute,
4507                            boolean neverReview, boolean indexable, boolean smallImage,
4508                            String smallImageURL, File smallImageFile,
4509                            Map<String, byte[]> images, String articleURL,
4510                            ServiceContext serviceContext)
4511                    throws PortalException, SystemException {
4512    
4513                    // Article
4514    
4515                    User user = userPersistence.findByPrimaryKey(userId);
4516                    articleId = StringUtil.toUpperCase(articleId.trim());
4517    
4518                    byte[] smallImageBytes = null;
4519    
4520                    try {
4521                            smallImageBytes = FileUtil.getBytes(smallImageFile);
4522                    }
4523                    catch (IOException ioe) {
4524                    }
4525    
4526                    JournalArticle latestArticle = getLatestArticle(
4527                            groupId, articleId, WorkflowConstants.STATUS_ANY);
4528    
4529                    JournalArticle article = latestArticle;
4530    
4531                    boolean imported = ExportImportThreadLocal.isImportInProcess();
4532    
4533                    double latestVersion = latestArticle.getVersion();
4534    
4535                    boolean addNewVersion = false;
4536    
4537                    if (imported) {
4538                            if (latestVersion > version) {
4539                                    JournalArticle existingArticle =
4540                                            journalArticlePersistence.fetchByG_A_V(
4541                                                    groupId, articleId, version);
4542    
4543                                    if (existingArticle != null) {
4544                                            article = existingArticle;
4545                                    }
4546                                    else {
4547                                            addNewVersion = true;
4548                                    }
4549                            }
4550                            else if (latestVersion < version) {
4551                                    addNewVersion = true;
4552                            }
4553                    }
4554                    else {
4555                            if ((version > 0) && (version != latestVersion)) {
4556                                    throw new ArticleVersionException();
4557                            }
4558    
4559                            serviceContext.validateModifiedDate(
4560                                    latestArticle, ArticleVersionException.class);
4561    
4562                            if (latestArticle.isApproved() || latestArticle.isExpired() ||
4563                                    latestArticle.isScheduled()) {
4564    
4565                                    addNewVersion = true;
4566    
4567                                    version = MathUtil.format(latestVersion + 0.1, 1, 1);
4568                            }
4569                    }
4570    
4571                    Date displayDate = null;
4572                    Date expirationDate = null;
4573                    Date reviewDate = null;
4574    
4575                    if (article.getClassNameId() ==
4576                                    JournalArticleConstants.CLASSNAME_ID_DEFAULT) {
4577    
4578                            displayDate = PortalUtil.getDate(
4579                                    displayDateMonth, displayDateDay, displayDateYear,
4580                                    displayDateHour, displayDateMinute, user.getTimeZone(),
4581                                    ArticleDisplayDateException.class);
4582    
4583                            if (!neverExpire) {
4584                                    expirationDate = PortalUtil.getDate(
4585                                            expirationDateMonth, expirationDateDay, expirationDateYear,
4586                                            expirationDateHour, expirationDateMinute,
4587                                            user.getTimeZone(), ArticleExpirationDateException.class);
4588                            }
4589    
4590                            if (!neverReview) {
4591                                    reviewDate = PortalUtil.getDate(
4592                                            reviewDateMonth, reviewDateDay, reviewDateYear,
4593                                            reviewDateHour, reviewDateMinute, user.getTimeZone(),
4594                                            ArticleReviewDateException.class);
4595                            }
4596                    }
4597    
4598                    Date now = new Date();
4599    
4600                    boolean expired = false;
4601    
4602                    if ((expirationDate != null) && expirationDate.before(now)) {
4603                            expired = true;
4604                    }
4605    
4606                    validate(
4607                            user.getCompanyId(), groupId, latestArticle.getClassNameId(),
4608                            titleMap, content, type, ddmStructureKey, ddmTemplateKey,
4609                            expirationDate, smallImage, smallImageURL, smallImageFile,
4610                            smallImageBytes);
4611    
4612                    if (addNewVersion) {
4613                            long id = counterLocalService.increment();
4614    
4615                            article = journalArticlePersistence.create(id);
4616    
4617                            article.setResourcePrimKey(latestArticle.getResourcePrimKey());
4618                            article.setGroupId(latestArticle.getGroupId());
4619                            article.setCompanyId(latestArticle.getCompanyId());
4620                            article.setUserId(user.getUserId());
4621                            article.setUserName(user.getFullName());
4622                            article.setCreateDate(serviceContext.getModifiedDate(now));
4623                            article.setClassNameId(latestArticle.getClassNameId());
4624                            article.setClassPK(latestArticle.getClassPK());
4625                            article.setArticleId(articleId);
4626                            article.setVersion(version);
4627                            article.setSmallImageId(latestArticle.getSmallImageId());
4628                    }
4629    
4630                    Locale locale = LocaleUtil.getSiteDefault();
4631    
4632                    String defaultLanguageId = ParamUtil.getString(
4633                            serviceContext, "defaultLanguageId");
4634    
4635                    if (Validator.isNull(defaultLanguageId)) {
4636                            defaultLanguageId = LocalizationUtil.getDefaultLanguageId(content);
4637                    }
4638    
4639                    if (Validator.isNotNull(defaultLanguageId)) {
4640                            locale = LocaleUtil.fromLanguageId(defaultLanguageId);
4641                    }
4642    
4643                    String title = titleMap.get(locale);
4644    
4645                    content = format(
4646                            user, groupId, articleId, article.getVersion(), addNewVersion,
4647                            content, ddmStructureKey, images);
4648    
4649                    article.setModifiedDate(serviceContext.getModifiedDate(now));
4650                    article.setFolderId(folderId);
4651                    article.setTreePath(article.buildTreePath());
4652                    article.setTitleMap(titleMap, locale);
4653                    article.setUrlTitle(
4654                            getUniqueUrlTitle(
4655                                    article.getId(), article.getArticleId(), title,
4656                                    latestArticle.getUrlTitle(), serviceContext));
4657                    article.setDescriptionMap(descriptionMap, locale);
4658                    article.setContent(content);
4659                    article.setType(type);
4660                    article.setStructureId(ddmStructureKey);
4661                    article.setTemplateId(ddmTemplateKey);
4662                    article.setLayoutUuid(layoutUuid);
4663                    article.setDisplayDate(displayDate);
4664                    article.setExpirationDate(expirationDate);
4665                    article.setReviewDate(reviewDate);
4666                    article.setIndexable(indexable);
4667                    article.setSmallImage(smallImage);
4668    
4669                    if (smallImage) {
4670                            if ((smallImageFile != null) && (smallImageBytes != null)) {
4671                                    article.setSmallImageId(counterLocalService.increment());
4672                            }
4673                    }
4674                    else {
4675                            article.setSmallImageId(0);
4676                    }
4677    
4678                    article.setSmallImageURL(smallImageURL);
4679    
4680                    if (latestArticle.isPending()) {
4681                            article.setStatus(latestArticle.getStatus());
4682                    }
4683                    else if (!expired) {
4684                            article.setStatus(WorkflowConstants.STATUS_DRAFT);
4685                    }
4686                    else {
4687                            article.setStatus(WorkflowConstants.STATUS_EXPIRED);
4688                    }
4689    
4690                    article.setExpandoBridgeAttributes(serviceContext);
4691    
4692                    journalArticlePersistence.update(article);
4693    
4694                    // Asset
4695    
4696                    updateAsset(
4697                            userId, article, serviceContext.getAssetCategoryIds(),
4698                            serviceContext.getAssetTagNames(),
4699                            serviceContext.getAssetLinkEntryIds());
4700    
4701                    // Dynamic data mapping
4702    
4703                    if (PortalUtil.getClassNameId(DDMStructure.class) ==
4704                                    article.getClassNameId()) {
4705    
4706                            updateDDMStructureXSD(
4707                                    article.getClassPK(), content, serviceContext);
4708                    }
4709    
4710                    // Small image
4711    
4712                    saveImages(
4713                            smallImage, article.getSmallImageId(), smallImageFile,
4714                            smallImageBytes);
4715    
4716                    // Email
4717    
4718                    PortletPreferences preferences =
4719                            ServiceContextUtil.getPortletPreferences(serviceContext);
4720    
4721                    // Workflow
4722    
4723                    if (expired && imported) {
4724                            updateStatus(
4725                                    userId, article, article.getStatus(), articleURL,
4726                                    new HashMap<String, Serializable>(), serviceContext);
4727                    }
4728    
4729                    if (serviceContext.getWorkflowAction() ==
4730                                    WorkflowConstants.ACTION_PUBLISH) {
4731    
4732                            sendEmail(
4733                                    article, articleURL, preferences, "requested", serviceContext);
4734    
4735                            WorkflowHandlerRegistryUtil.startWorkflowInstance(
4736                                    user.getCompanyId(), groupId, userId,
4737                                    JournalArticle.class.getName(), article.getId(), article,
4738                                    serviceContext);
4739                    }
4740    
4741                    return journalArticlePersistence.findByPrimaryKey(article.getId());
4742            }
4743    
4744            /**
4745             * Updates the web content article matching the version, replacing its
4746             * folder and content.
4747             *
4748             * @param  userId the primary key of the user updating the web content
4749             *         article
4750             * @param  groupId the primary key of the web content article's group
4751             * @param  folderId the primary key of the web content article folder
4752             * @param  articleId the primary key of the web content article
4753             * @param  version the web content article's version
4754             * @param  content the HTML content wrapped in XML. For more information,
4755             *         see the content example in the class description for {@link
4756             *         JournalArticleLocalServiceImpl}.
4757             * @param  serviceContext the service context to be applied. Can set the
4758             *         modification date, expando bridge attributes, asset category IDs,
4759             *         asset tag names, asset link entry IDs, workflow actions, the
4760             *         "defaultLanguageId" and "urlTitle" attributes, and can set
4761             *         whether to add the default command update for the web content
4762             *         article. With respect to social activities, by setting the
4763             *         service context's command to {@link
4764             *         com.liferay.portal.kernel.util.Constants#UPDATE}, the invocation
4765             *         is considered a web content update activity; otherwise it is
4766             *         considered a web content add activity.
4767             * @return the updated web content article
4768             * @throws PortalException if a user with the primary key or a matching web
4769             *         content article could not be found, or if a portal exception
4770             *         occurred
4771             * @throws SystemException if a system exception occurred
4772             */
4773            @Override
4774            public JournalArticle updateArticle(
4775                            long userId, long groupId, long folderId, String articleId,
4776                            double version, String content, ServiceContext serviceContext)
4777                    throws PortalException, SystemException {
4778    
4779                    JournalArticle article = journalArticlePersistence.findByG_A_V(
4780                            groupId, articleId, version);
4781    
4782                    return journalArticleLocalService.updateArticle(
4783                            userId, groupId, folderId, articleId, version,
4784                            article.getTitleMap(), article.getDescriptionMap(), content,
4785                            article.getLayoutUuid(), serviceContext);
4786            }
4787    
4788            /**
4789             * @deprecated As of 6.2.0, replaced by {@link
4790             *             #updateArticleTranslation(long, String, double, Locale,
4791             *             String, String, String, Map, ServiceContext)}
4792             */
4793            @Override
4794            public JournalArticle updateArticleTranslation(
4795                            long groupId, String articleId, double version, Locale locale,
4796                            String title, String description, String content,
4797                            Map<String, byte[]> images)
4798                    throws PortalException, SystemException {
4799    
4800                    return journalArticleLocalService.updateArticleTranslation(
4801                            groupId, articleId, version, locale, title, description, content,
4802                            images, null);
4803            }
4804    
4805            /**
4806             * Updates the translation of the web content article.
4807             *
4808             * @param  groupId the primary key of the web content article's group
4809             * @param  articleId the primary key of the web content article
4810             * @param  version the web content article's version
4811             * @param  locale the locale of the web content article's display template
4812             * @param  title the translated web content article title
4813             * @param  description the translated web content article description
4814             * @param  content the HTML content wrapped in XML. For more information,
4815             *         see the content example in the class description for {@link
4816             *         JournalArticleLocalServiceImpl}.
4817             * @param  images the web content's images
4818             * @param  serviceContext the service context to be applied. Can set the
4819             *         modification date and "urlTitle" attribute for the web content
4820             *         article.
4821             * @return the updated web content article
4822             * @throws PortalException if a user with the primary key or a matching web
4823             *         content article could not be found, or if a portal exception
4824             *         occurred
4825             * @throws SystemException if a system exception occurred
4826             */
4827            @Indexable(type = IndexableType.REINDEX)
4828            @Override
4829            public JournalArticle updateArticleTranslation(
4830                            long groupId, String articleId, double version, Locale locale,
4831                            String title, String description, String content,
4832                            Map<String, byte[]> images, ServiceContext serviceContext)
4833                    throws PortalException, SystemException {
4834    
4835                    validateContent(content);
4836    
4837                    JournalArticle oldArticle = getLatestArticle(
4838                            groupId, articleId, WorkflowConstants.STATUS_ANY);
4839    
4840                    double oldVersion = oldArticle.getVersion();
4841    
4842                    if ((version > 0) && (version != oldVersion)) {
4843                            throw new ArticleVersionException();
4844                    }
4845    
4846                    boolean incrementVersion = false;
4847    
4848                    if (oldArticle.isApproved() || oldArticle.isExpired()) {
4849                            incrementVersion = true;
4850                    }
4851    
4852                    if (serviceContext != null) {
4853                            serviceContext.validateModifiedDate(
4854                                    oldArticle, ArticleVersionException.class);
4855                    }
4856    
4857                    JournalArticle article = null;
4858    
4859                    User user = userPersistence.findByPrimaryKey(oldArticle.getUserId());
4860    
4861                    if (incrementVersion) {
4862                            double newVersion = MathUtil.format(oldVersion + 0.1, 1, 1);
4863    
4864                            long id = counterLocalService.increment();
4865    
4866                            article = journalArticlePersistence.create(id);
4867    
4868                            article.setResourcePrimKey(oldArticle.getResourcePrimKey());
4869                            article.setGroupId(oldArticle.getGroupId());
4870                            article.setCompanyId(oldArticle.getCompanyId());
4871                            article.setUserId(oldArticle.getUserId());
4872                            article.setUserName(user.getFullName());
4873                            article.setCreateDate(new Date());
4874                            article.setModifiedDate(new Date());
4875                            article.setClassNameId(oldArticle.getClassNameId());
4876                            article.setClassPK(oldArticle.getClassPK());
4877                            article.setArticleId(articleId);
4878                            article.setVersion(newVersion);
4879                            article.setTitleMap(oldArticle.getTitleMap());
4880                            article.setUrlTitle(
4881                                    getUniqueUrlTitle(
4882                                            id, articleId, title, oldArticle.getUrlTitle(),
4883                                            serviceContext));
4884                            article.setDescriptionMap(oldArticle.getDescriptionMap());
4885                            article.setType(oldArticle.getType());
4886                            article.setStructureId(oldArticle.getStructureId());
4887                            article.setTemplateId(oldArticle.getTemplateId());
4888                            article.setLayoutUuid(oldArticle.getLayoutUuid());
4889                            article.setDisplayDate(oldArticle.getDisplayDate());
4890                            article.setExpirationDate(oldArticle.getExpirationDate());
4891                            article.setReviewDate(oldArticle.getReviewDate());
4892                            article.setIndexable(oldArticle.getIndexable());
4893                            article.setSmallImage(oldArticle.getSmallImage());
4894                            article.setSmallImageId(oldArticle.getSmallImageId());
4895    
4896                            if (article.getSmallImageId() == 0) {
4897                                    article.setSmallImageId(counterLocalService.increment());
4898                            }
4899    
4900                            article.setSmallImageURL(oldArticle.getSmallImageURL());
4901    
4902                            article.setStatus(WorkflowConstants.STATUS_DRAFT);
4903                            article.setStatusDate(new Date());
4904                    }
4905                    else {
4906                            article = oldArticle;
4907                    }
4908    
4909                    Map<Locale, String> titleMap = article.getTitleMap();
4910    
4911                    titleMap.put(locale, title);
4912    
4913                    article.setTitleMap(titleMap);
4914    
4915                    Map<Locale, String> descriptionMap = article.getDescriptionMap();
4916    
4917                    descriptionMap.put(locale, description);
4918    
4919                    article.setDescriptionMap(descriptionMap);
4920    
4921                    content = format(
4922                            user, groupId, articleId, article.getVersion(),
4923                            !oldArticle.isDraft(), content, oldArticle.getStructureId(),
4924                            images);
4925    
4926                    article.setContent(content);
4927    
4928                    journalArticlePersistence.update(article);
4929    
4930                    return article;
4931            }
4932    
4933            /**
4934             * Updates the web content article's asset with the new asset categories,
4935             * tag names, and link entries, removing and adding them as necessary.
4936             *
4937             * @param  userId the primary key of the user updating the web content
4938             *         article's asset
4939             * @param  article the web content article
4940             * @param  assetCategoryIds the primary keys of the new asset categories
4941             * @param  assetTagNames the new asset tag names
4942             * @param  assetLinkEntryIds the primary keys of the new asset link entries
4943             * @throws PortalException if a portal exception occurred
4944             * @throws SystemException if a system exception occurred
4945             */
4946            @Override
4947            public void updateAsset(
4948                            long userId, JournalArticle article, long[] assetCategoryIds,
4949                            String[] assetTagNames, long[] assetLinkEntryIds)
4950                    throws PortalException, SystemException {
4951    
4952                    boolean visible = article.isApproved();
4953    
4954                    if (article.getClassNameId() !=
4955                                    JournalArticleConstants.CLASSNAME_ID_DEFAULT) {
4956    
4957                            visible = false;
4958                    }
4959    
4960                    boolean addDraftAssetEntry = false;
4961    
4962                    if (!article.isApproved() &&
4963                            (article.getVersion() != JournalArticleConstants.VERSION_DEFAULT)) {
4964    
4965                            int approvedArticlesCount = journalArticlePersistence.countByG_A_ST(
4966                                    article.getGroupId(), article.getArticleId(),
4967                                    JournalArticleConstants.ASSET_ENTRY_CREATION_STATUSES);
4968    
4969                            if (approvedArticlesCount > 0) {
4970                                    addDraftAssetEntry = true;
4971                            }
4972                    }
4973    
4974                    AssetEntry assetEntry = null;
4975    
4976                    if (addDraftAssetEntry) {
4977                            assetEntry = assetEntryLocalService.updateEntry(
4978                                    userId, article.getGroupId(), article.getCreateDate(),
4979                                    article.getModifiedDate(), JournalArticle.class.getName(),
4980                                    article.getPrimaryKey(), article.getUuid(),
4981                                    getClassTypeId(article), assetCategoryIds, assetTagNames, false,
4982                                    null, null, null, ContentTypes.TEXT_HTML, article.getTitle(),
4983                                    article.getDescription(), article.getDescription(), null,
4984                                    article.getLayoutUuid(), 0, 0, null, false);
4985                    }
4986                    else {
4987                            JournalArticleResource journalArticleResource =
4988                                    journalArticleResourceLocalService.getArticleResource(
4989                                            article.getResourcePrimKey());
4990    
4991                            assetEntry = assetEntryLocalService.updateEntry(
4992                                    userId, article.getGroupId(), article.getCreateDate(),
4993                                    article.getModifiedDate(), JournalArticle.class.getName(),
4994                                    journalArticleResource.getResourcePrimKey(),
4995                                    journalArticleResource.getUuid(), getClassTypeId(article),
4996                                    assetCategoryIds, assetTagNames, visible, null, null, null,
4997                                    ContentTypes.TEXT_HTML, article.getTitle(),
4998                                    article.getDescription(), article.getDescription(), null,
4999                                    article.getLayoutUuid(), 0, 0, null, false);
5000                    }
5001    
5002                    assetLinkLocalService.updateLinks(
5003                            userId, assetEntry.getEntryId(), assetLinkEntryIds,
5004                            AssetLinkConstants.TYPE_RELATED);
5005            }
5006    
5007            /**
5008             * Updates the web content article matching the group, article ID, and
5009             * version, replacing its content.
5010             *
5011             * @param  groupId the primary key of the web content article's group
5012             * @param  articleId the primary key of the web content article
5013             * @param  version the web content article's version
5014             * @param  content the HTML content wrapped in XML. For more information,
5015             *         see the content example in the class description for {@link
5016             *         JournalArticleLocalServiceImpl}.
5017             * @return the updated web content article
5018             * @throws PortalException if a matching web content article could not be
5019             *         found
5020             * @throws SystemException if a system exception occurred
5021             */
5022            @Indexable(type = IndexableType.REINDEX)
5023            @Override
5024            public JournalArticle updateContent(
5025                            long groupId, String articleId, double version, String content)
5026                    throws PortalException, SystemException {
5027    
5028                    JournalArticle article = journalArticlePersistence.findByG_A_V(
5029                            groupId, articleId, version);
5030    
5031                    article.setContent(content);
5032    
5033                    journalArticlePersistence.update(article);
5034    
5035                    return article;
5036            }
5037    
5038            /**
5039             * Updates the workflow status of the web content article.
5040             *
5041             * @param  userId the primary key of the user updating the web content
5042             *         article's status
5043             * @param  article the web content article
5044             * @param  status the web content article's workflow status. For more
5045             *         information see {@link WorkflowConstants} for constants starting
5046             *         with the "STATUS_" prefix.
5047             * @param  articleURL the web content article's accessible URL
5048             * @param  workflowContext the web content article's configured workflow
5049             *         context
5050             * @param  serviceContext the service context to be applied. Can set the
5051             *         modification date, status date, and portlet preferences. With
5052             *         respect to social activities, by setting the service context's
5053             *         command to {@link
5054             *         com.liferay.portal.kernel.util.Constants#UPDATE}, the invocation
5055             *         is considered a web content update activity; otherwise it is
5056             *         considered a web content add activity.
5057             * @return the updated web content article
5058             * @throws PortalException if a portal exception occurred
5059             * @throws SystemException if a system exception occurred
5060             */
5061            @Indexable(type = IndexableType.REINDEX)
5062            @Override
5063            public JournalArticle updateStatus(
5064                            long userId, JournalArticle article, int status, String articleURL,
5065                            Map<String, Serializable> workflowContext,
5066                            ServiceContext serviceContext)
5067                    throws PortalException, SystemException {
5068    
5069                    // Article
5070    
5071                    User user = userPersistence.findByPrimaryKey(userId);
5072                    Date now = new Date();
5073    
5074                    if ((status == WorkflowConstants.STATUS_APPROVED) &&
5075                            (article.getClassNameId() ==
5076                                    JournalArticleConstants.CLASSNAME_ID_DEFAULT) &&
5077                            (article.getDisplayDate() != null) &&
5078                            now.before(article.getDisplayDate())) {
5079    
5080                            status = WorkflowConstants.STATUS_SCHEDULED;
5081                    }
5082    
5083                    int oldStatus = article.getStatus();
5084    
5085                    int oldArticleVersionStatus = WorkflowConstants.STATUS_ANY;
5086    
5087                    List<ObjectValuePair<Long, Integer>> articleVersionStatusOVPs =
5088                            new ArrayList<ObjectValuePair<Long, Integer>>();
5089    
5090                    List<JournalArticle> articleVersions =
5091                            (List<JournalArticle>)workflowContext.get("articleVersions");
5092    
5093                    if ((articleVersions != null) && !articleVersions.isEmpty()) {
5094                            JournalArticle oldArticleVersion = articleVersions.get(0);
5095    
5096                            oldArticleVersionStatus = oldArticleVersion.getStatus();
5097    
5098                            articleVersionStatusOVPs = getArticleVersionStatuses(
5099                                    articleVersions);
5100                    }
5101    
5102                    article.setModifiedDate(serviceContext.getModifiedDate(now));
5103    
5104                    boolean neverExpire = false;
5105    
5106                    if (status == WorkflowConstants.STATUS_APPROVED) {
5107                            Date expirationDate = article.getExpirationDate();
5108    
5109                            if ((expirationDate != null) && expirationDate.before(now)) {
5110                                    neverExpire = true;
5111    
5112                                    article.setExpirationDate(null);
5113                            }
5114                    }
5115    
5116                    if (status == WorkflowConstants.STATUS_EXPIRED) {
5117                            article.setExpirationDate(now);
5118                    }
5119    
5120                    article.setStatus(status);
5121                    article.setStatusByUserId(user.getUserId());
5122                    article.setStatusByUserName(user.getFullName());
5123                    article.setStatusDate(serviceContext.getModifiedDate(now));
5124    
5125                    journalArticlePersistence.update(article);
5126    
5127                    if (hasModifiedLatestApprovedVersion(
5128                                    article.getGroupId(), article.getArticleId(),
5129                                    article.getVersion())) {
5130    
5131                            if (status == WorkflowConstants.STATUS_APPROVED) {
5132                                    updateUrlTitles(
5133                                            article.getGroupId(), article.getArticleId(),
5134                                            article.getUrlTitle());
5135    
5136                                    // Asset
5137    
5138                                    if ((oldStatus != WorkflowConstants.STATUS_APPROVED) &&
5139                                            (article.getVersion() !=
5140                                                    JournalArticleConstants.VERSION_DEFAULT)) {
5141    
5142                                            AssetEntry draftAssetEntry =
5143                                                    assetEntryLocalService.fetchEntry(
5144                                                            JournalArticle.class.getName(),
5145                                                            article.getPrimaryKey());
5146    
5147                                            if (draftAssetEntry != null) {
5148                                                    long[] assetCategoryIds =
5149                                                            draftAssetEntry.getCategoryIds();
5150                                                    String[] assetTagNames = draftAssetEntry.getTagNames();
5151    
5152                                                    List<AssetLink> assetLinks =
5153                                                            assetLinkLocalService.getDirectLinks(
5154                                                                    draftAssetEntry.getEntryId(),
5155                                                                    AssetLinkConstants.TYPE_RELATED);
5156    
5157                                                    long[] assetLinkEntryIds = StringUtil.split(
5158                                                            ListUtil.toString(
5159                                                                    assetLinks, AssetLink.ENTRY_ID2_ACCESSOR), 0L);
5160    
5161                                                    AssetEntry assetEntry =
5162                                                            assetEntryLocalService.updateEntry(
5163                                                                    userId, article.getGroupId(),
5164                                                                    article.getCreateDate(),
5165                                                                    article.getModifiedDate(),
5166                                                                    JournalArticle.class.getName(),
5167                                                                    article.getResourcePrimKey(), article.getUuid(),
5168                                                                    getClassTypeId(article), assetCategoryIds,
5169                                                                    assetTagNames, false, null, null, null,
5170                                                                    ContentTypes.TEXT_HTML, article.getTitle(),
5171                                                                    article.getDescription(),
5172                                                                    article.getDescription(), null,
5173                                                                    article.getLayoutUuid(), 0, 0, null, false);
5174    
5175                                                    assetLinkLocalService.updateLinks(
5176                                                            userId, assetEntry.getEntryId(), assetLinkEntryIds,
5177                                                            AssetLinkConstants.TYPE_RELATED);
5178    
5179                                                    SystemEventHierarchyEntryThreadLocal.push(
5180                                                            JournalArticle.class);
5181    
5182                                                    try {
5183                                                            assetEntryLocalService.deleteEntry(
5184                                                                    JournalArticle.class.getName(),
5185                                                                    article.getPrimaryKey());
5186                                                    }
5187                                                    finally {
5188                                                            SystemEventHierarchyEntryThreadLocal.pop(
5189                                                                    JournalArticle.class);
5190                                                    }
5191                                            }
5192                                    }
5193    
5194                                    if (article.getClassNameId() ==
5195                                                    JournalArticleConstants.CLASSNAME_ID_DEFAULT) {
5196    
5197                                            // Get the earliest display date and latest expiration date
5198                                            // among all article versions
5199    
5200                                            Date[] dateInterval = getDateInterval(
5201                                                    article.getGroupId(), article.getArticleId(),
5202                                                    article.getDisplayDate(), article.getExpirationDate());
5203    
5204                                            Date displayDate = dateInterval[0];
5205                                            Date expirationDate = dateInterval[1];
5206    
5207                                            if (neverExpire) {
5208                                                    expirationDate = null;
5209                                            }
5210    
5211                                            assetEntryLocalService.updateEntry(
5212                                                    JournalArticle.class.getName(),
5213                                                    article.getResourcePrimKey(), displayDate,
5214                                                    expirationDate, true);
5215                                    }
5216    
5217                                    // Social
5218    
5219                                    JSONObject extraDataJSONObject =
5220                                            JSONFactoryUtil.createJSONObject();
5221    
5222                                    extraDataJSONObject.put("title", article.getTitle());
5223    
5224                                    if (serviceContext.isCommandUpdate()) {
5225                                            socialActivityLocalService.addActivity(
5226                                                    user.getUserId(), article.getGroupId(),
5227                                                    JournalArticle.class.getName(),
5228                                                    article.getResourcePrimKey(),
5229                                                    JournalActivityKeys.UPDATE_ARTICLE,
5230                                                    extraDataJSONObject.toString(), 0);
5231                                    }
5232                                    else {
5233                                            socialActivityLocalService.addUniqueActivity(
5234                                                    user.getUserId(), article.getGroupId(),
5235                                                    JournalArticle.class.getName(),
5236                                                    article.getResourcePrimKey(),
5237                                                    JournalActivityKeys.ADD_ARTICLE,
5238                                                    extraDataJSONObject.toString(), 0);
5239                                    }
5240                            }
5241                            else if (oldStatus == WorkflowConstants.STATUS_APPROVED) {
5242                                    updatePreviousApprovedArticle(article);
5243                            }
5244                    }
5245    
5246                    if (oldStatus == WorkflowConstants.STATUS_IN_TRASH) {
5247    
5248                            // Trash
5249    
5250                            List<TrashVersion> trashVersions =
5251                                    (List<TrashVersion>)workflowContext.get("trashVersions");
5252    
5253                            for (TrashVersion trashVersion : trashVersions) {
5254                                    JournalArticle trashArticleVersion =
5255                                            journalArticlePersistence.findByPrimaryKey(
5256                                                    trashVersion.getClassPK());
5257    
5258                                    trashArticleVersion.setStatus(trashVersion.getStatus());
5259    
5260                                    journalArticlePersistence.update(trashArticleVersion);
5261                            }
5262    
5263                            trashEntryLocalService.deleteEntry(
5264                                    JournalArticle.class.getName(), article.getResourcePrimKey());
5265                    }
5266                    else if (status == WorkflowConstants.STATUS_IN_TRASH) {
5267                            assetEntryLocalService.updateVisible(
5268                                    JournalArticle.class.getName(), article.getResourcePrimKey(),
5269                                    false);
5270    
5271                            // Trash
5272    
5273                            for (JournalArticle articleVersion : articleVersions) {
5274                                    articleVersion.setStatus(WorkflowConstants.STATUS_IN_TRASH);
5275    
5276                                    journalArticlePersistence.update(articleVersion);
5277                            }
5278    
5279                            JournalArticleResource articleResource =
5280                                    journalArticleResourceLocalService.getArticleResource(
5281                                            article.getResourcePrimKey());
5282    
5283                            UnicodeProperties typeSettingsProperties = new UnicodeProperties();
5284    
5285                            typeSettingsProperties.put("title", article.getArticleId());
5286    
5287                            trashEntryLocalService.addTrashEntry(
5288                                    userId, article.getGroupId(), JournalArticle.class.getName(),
5289                                    article.getResourcePrimKey(), articleResource.getUuid(), null,
5290                                    oldArticleVersionStatus, articleVersionStatusOVPs,
5291                                    typeSettingsProperties);
5292                    }
5293    
5294                    if ((article.getClassNameId() ==
5295                                    JournalArticleConstants.CLASSNAME_ID_DEFAULT) &&
5296                            (oldStatus != WorkflowConstants.STATUS_IN_TRASH) &&
5297                            (status != WorkflowConstants.STATUS_IN_TRASH)) {
5298    
5299                            // Email
5300    
5301                            if ((oldStatus == WorkflowConstants.STATUS_PENDING) &&
5302                                    ((status == WorkflowConstants.STATUS_APPROVED) ||
5303                                     (status == WorkflowConstants.STATUS_DENIED))) {
5304    
5305                                    String msg = "granted";
5306    
5307                                    if (status == WorkflowConstants.STATUS_DENIED) {
5308                                            msg = "denied";
5309                                    }
5310    
5311                                    try {
5312                                            PortletPreferences preferences =
5313                                                    ServiceContextUtil.getPortletPreferences(
5314                                                            serviceContext);
5315    
5316                                            sendEmail(
5317                                                    article, articleURL, preferences, msg, serviceContext);
5318                                    }
5319                                    catch (Exception e) {
5320                                            _log.error(
5321                                                    "Unable to send email to notify the change of status " +
5322                                                            " to " + msg + " for article " + article.getId() +
5323                                                                    ": " + e.getMessage());
5324                                    }
5325                            }
5326    
5327                            // Subscriptions
5328    
5329                            notifySubscribers(article, serviceContext);
5330                    }
5331    
5332                    return article;
5333            }
5334    
5335            /**
5336             * Updates the workflow status of the web content article matching the class
5337             * PK.
5338             *
5339             * @param  userId the primary key of the user updating the web content
5340             *         article's status
5341             * @param  classPK the primary key of the DDM structure, if the web content
5342             *         article is related to a DDM structure, the primary key of the
5343             *         class associated with the article, or <code>0</code> otherwise
5344             * @param  status the web content article's workflow status. For more
5345             *         information see {@link WorkflowConstants} for constants starting
5346             *         with the "STATUS_" prefix.
5347             * @param  workflowContext the web content article's configured workflow
5348             * @param  serviceContext the service context to be applied. Can set the
5349             *         modification date, portlet preferences, and can set whether to
5350             *         add the default command update for the web content article.
5351             * @return the updated web content article
5352             * @throws PortalException if a matching web content article could not be
5353             *         found or if a portal exception occurred
5354             * @throws SystemException if a system exception occurred
5355             */
5356            @Override
5357            public JournalArticle updateStatus(
5358                            long userId, long classPK, int status,
5359                            Map<String, Serializable> workflowContext,
5360                            ServiceContext serviceContext)
5361                    throws PortalException, SystemException {
5362    
5363                    JournalArticle article = getArticle(classPK);
5364    
5365                    return journalArticleLocalService.updateStatus(
5366                            userId, article, status, null, workflowContext, serviceContext);
5367            }
5368    
5369            /**
5370             * Updates the workflow status of the web content article matching the
5371             * group, article ID, and version.
5372             *
5373             * @param  userId the primary key of the user updating the web content
5374             *         article's status
5375             * @param  groupId the primary key of the web content article's group
5376             * @param  articleId the primary key of the web content article
5377             * @param  version the web content article's version
5378             * @param  status the web content article's workflow status. For more
5379             *         information see {@link WorkflowConstants} for constants starting
5380             *         with the "STATUS_" prefix.
5381             * @param  articleURL the web content article's accessible URL
5382             * @param  workflowContext the web content article's configured workflow
5383             * @param  serviceContext the service context to be applied. Can set the
5384             *         modification date, portlet preferences, and can set whether to
5385             *         add the default command update for the web content article.
5386             * @return the updated web content article
5387             * @throws PortalException if a matching web content article could not be
5388             *         found or if a portal exception occurred
5389             * @throws SystemException if a system exception occurred
5390             */
5391            @Override
5392            public JournalArticle updateStatus(
5393                            long userId, long groupId, String articleId, double version,
5394                            int status, String articleURL,
5395                            Map<String, Serializable> workflowContext,
5396                            ServiceContext serviceContext)
5397                    throws PortalException, SystemException {
5398    
5399                    JournalArticle article = journalArticlePersistence.findByG_A_V(
5400                            groupId, articleId, version);
5401    
5402                    return journalArticleLocalService.updateStatus(
5403                            userId, article, status, articleURL, workflowContext,
5404                            serviceContext);
5405            }
5406    
5407            /**
5408             * Updates the web content articles matching the group, class name ID, and
5409             * DDM template key, replacing the DDM template key with a new one.
5410             *
5411             * @param  groupId the primary key of the web content article's group
5412             * @param  classNameId the primary key of the DDMStructure class if the web
5413             *         content article is related to a DDM structure, the primary key of
5414             *         the class name associated with the article, or {@link
5415             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
5416             * @param  oldDDMTemplateKey the primary key of the web content article's
5417             *         old DDM template
5418             * @param  newDDMTemplateKey the primary key of the web content article's
5419             *         new DDM template
5420             * @throws SystemException if a system exception occurred
5421             */
5422            @Override
5423            public void updateTemplateId(
5424                            long groupId, long classNameId, String oldDDMTemplateKey,
5425                            String newDDMTemplateKey)
5426                    throws SystemException {
5427    
5428                    List<JournalArticle> articles = journalArticlePersistence.findByG_C_T(
5429                            groupId, classNameId, oldDDMTemplateKey);
5430    
5431                    for (JournalArticle article : articles) {
5432                            article.setTemplateId(newDDMTemplateKey);
5433    
5434                            journalArticlePersistence.update(article);
5435                    }
5436            }
5437    
5438            protected void checkArticlesByDisplayDate(Date displayDate)
5439                    throws PortalException, SystemException {
5440    
5441                    List<JournalArticle> articles = journalArticlePersistence.findByLtD_S(
5442                            displayDate, WorkflowConstants.STATUS_SCHEDULED);
5443    
5444                    for (JournalArticle article : articles) {
5445                            Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
5446                                    JournalArticle.class);
5447    
5448                            indexer.reindex(article);
5449    
5450                            ServiceContext serviceContext = new ServiceContext();
5451    
5452                            serviceContext.setCommand(Constants.UPDATE);
5453    
5454                            String layoutFullURL = PortalUtil.getLayoutFullURL(
5455                                    article.getGroupId(), PortletKeys.JOURNAL);
5456    
5457                            serviceContext.setLayoutFullURL(layoutFullURL);
5458    
5459                            serviceContext.setScopeGroupId(article.getGroupId());
5460    
5461                            updateStatus(
5462                                    article.getUserId(), article, WorkflowConstants.STATUS_APPROVED,
5463                                    null, new HashMap<String, Serializable>(), serviceContext);
5464                    }
5465            }
5466    
5467            protected void checkArticlesByExpirationDate(Date expirationDate)
5468                    throws PortalException, SystemException {
5469    
5470                    List<JournalArticle> articles =
5471                            journalArticleFinder.findByExpirationDate(
5472                                    JournalArticleConstants.CLASSNAME_ID_DEFAULT,
5473                                    new Date(
5474                                            expirationDate.getTime() + _JOURNAL_ARTICLE_CHECK_INTERVAL),
5475                                    new QueryDefinition(WorkflowConstants.STATUS_APPROVED));
5476    
5477                    if (_log.isDebugEnabled()) {
5478                            _log.debug("Expiring " + articles.size() + " articles");
5479                    }
5480    
5481                    Set<Long> companyIds = new HashSet<Long>();
5482    
5483                    for (JournalArticle article : articles) {
5484                            if (PropsValues.JOURNAL_ARTICLE_EXPIRE_ALL_VERSIONS) {
5485                                    List<JournalArticle> currentArticles =
5486                                            journalArticlePersistence.findByG_A(
5487                                                    article.getGroupId(), article.getArticleId(),
5488                                                    QueryUtil.ALL_POS, QueryUtil.ALL_POS,
5489                                                    new ArticleVersionComparator(true));
5490    
5491                                    for (JournalArticle currentArticle : currentArticles) {
5492                                            currentArticle.setExpirationDate(
5493                                                    article.getExpirationDate());
5494                                            currentArticle.setStatus(WorkflowConstants.STATUS_EXPIRED);
5495    
5496                                            journalArticlePersistence.update(currentArticle);
5497                                    }
5498                            }
5499                            else {
5500                                    article.setStatus(WorkflowConstants.STATUS_EXPIRED);
5501    
5502                                    journalArticlePersistence.update(article);
5503                            }
5504    
5505                            updatePreviousApprovedArticle(article);
5506    
5507                            Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
5508                                    JournalArticle.class);
5509    
5510                            indexer.reindex(article);
5511    
5512                            JournalContentUtil.clearCache(
5513                                    article.getGroupId(), article.getArticleId(),
5514                                    article.getTemplateId());
5515    
5516                            companyIds.add(article.getCompanyId());
5517                    }
5518    
5519                    for (long companyId : companyIds) {
5520                            CacheUtil.clearCache(companyId);
5521                    }
5522    
5523                    if (_previousCheckDate == null) {
5524                            _previousCheckDate = new Date(
5525                                    expirationDate.getTime() - _JOURNAL_ARTICLE_CHECK_INTERVAL);
5526                    }
5527            }
5528    
5529            protected void checkArticlesByReviewDate(Date reviewDate)
5530                    throws PortalException, SystemException {
5531    
5532                    List<JournalArticle> articles = journalArticleFinder.findByReviewDate(
5533                            JournalArticleConstants.CLASSNAME_ID_DEFAULT, reviewDate,
5534                            _previousCheckDate);
5535    
5536                    if (_log.isDebugEnabled()) {
5537                            _log.debug(
5538                                    "Sending review notifications for " + articles.size() +
5539                                            " articles");
5540                    }
5541    
5542                    for (JournalArticle article : articles) {
5543                            String articleURL = StringPool.BLANK;
5544    
5545                            long ownerId = article.getGroupId();
5546                            int ownerType = PortletKeys.PREFS_OWNER_TYPE_GROUP;
5547                            long plid = PortletKeys.PREFS_PLID_SHARED;
5548                            String portletId = PortletKeys.JOURNAL;
5549    
5550                            PortletPreferences preferences =
5551                                    portletPreferencesLocalService.getPreferences(
5552                                            article.getCompanyId(), ownerId, ownerType, plid,
5553                                            portletId);
5554    
5555                            sendEmail(
5556                                    article, articleURL, preferences, "review",
5557                                    new ServiceContext());
5558                    }
5559            }
5560    
5561            protected void checkStructure(Document contentDoc, Element root)
5562                    throws PortalException {
5563    
5564                    for (Element el : root.elements()) {
5565                            checkStructureField(el, contentDoc);
5566    
5567                            checkStructure(contentDoc, el);
5568                    }
5569            }
5570    
5571            protected void checkStructure(JournalArticle article)
5572                    throws PortalException, SystemException {
5573    
5574                    Group companyGroup = groupLocalService.getCompanyGroup(
5575                            article.getCompanyId());
5576    
5577                    DDMStructure structure = null;
5578    
5579                    try {
5580                            structure = ddmStructurePersistence.findByG_C_S(
5581                                    article.getGroupId(),
5582                                    PortalUtil.getClassNameId(JournalArticle.class),
5583                                    article.getStructureId());
5584                    }
5585                    catch (NoSuchStructureException nsse) {
5586                            structure = ddmStructurePersistence.findByG_C_S(
5587                                    companyGroup.getGroupId(),
5588                                    PortalUtil.getClassNameId(JournalArticle.class),
5589                                    article.getStructureId());
5590                    }
5591    
5592                    String content = GetterUtil.getString(article.getContent());
5593    
5594                    try {
5595                            Document contentDocument = SAXReaderUtil.read(content);
5596                            Document xsdDocument = SAXReaderUtil.read(structure.getXsd());
5597    
5598                            checkStructure(contentDocument, xsdDocument.getRootElement());
5599                    }
5600                    catch (DocumentException de) {
5601                            throw new SystemException(de);
5602                    }
5603                    catch (StructureXsdException sxsde) {
5604                            long groupId = article.getGroupId();
5605                            String articleId = article.getArticleId();
5606                            double version = article.getVersion();
5607    
5608                            if (_log.isWarnEnabled()) {
5609                                    _log.warn(
5610                                            "Article {groupId=" + groupId + ", articleId=" +
5611                                                    articleId + ", version=" + version +
5612                                                            "} has content that does not match its " +
5613                                                                    "structure: " + sxsde.getMessage());
5614                            }
5615                    }
5616            }
5617    
5618            protected void checkStructureField(Element el, Document contentDoc)
5619                    throws PortalException {
5620    
5621                    StringBuilder elPath = new StringBuilder();
5622    
5623                    elPath.append(el.attributeValue("name"));
5624    
5625                    Element elParent = el.getParent();
5626    
5627                    while (true) {
5628                            if ((elParent == null) || elParent.getName().equals("root")) {
5629                                    break;
5630                            }
5631    
5632                            elPath.insert(
5633                                    0, elParent.attributeValue("name") + StringPool.COMMA);
5634    
5635                            elParent = elParent.getParent();
5636                    }
5637    
5638                    String[] elPathNames = StringUtil.split(elPath.toString());
5639    
5640                    Element contentEl = contentDoc.getRootElement();
5641    
5642                    for (String _elPathName : elPathNames) {
5643                            boolean foundEl = false;
5644    
5645                            for (Element tempEl : contentEl.elements()) {
5646                                    if (_elPathName.equals(
5647                                                    tempEl.attributeValue("name", StringPool.BLANK))) {
5648    
5649                                            contentEl = tempEl;
5650                                            foundEl = true;
5651    
5652                                            break;
5653                                    }
5654                            }
5655    
5656                            if (!foundEl) {
5657                                    String elType = contentEl.attributeValue(
5658                                            "type", StringPool.BLANK);
5659    
5660                                    if (!elType.equals("list") && !elType.equals("multi-list")) {
5661                                            throw new StructureXsdException(elPath.toString());
5662                                    }
5663    
5664                                    break;
5665                            }
5666                    }
5667            }
5668    
5669            protected void copyArticleImages(
5670                            JournalArticle oldArticle, JournalArticle newArticle)
5671                    throws Exception {
5672    
5673                    Document contentDoc = SAXReaderUtil.read(oldArticle.getContent());
5674    
5675                    XPath xPathSelector = SAXReaderUtil.createXPath(
5676                            "//dynamic-element[@type='image']");
5677    
5678                    List<Node> imageNodes = xPathSelector.selectNodes(contentDoc);
5679    
5680                    for (Node imageNode : imageNodes) {
5681                            Element imageEl = (Element)imageNode;
5682    
5683                            String instanceId = imageEl.attributeValue("instance-id");
5684                            String name = imageEl.attributeValue("name");
5685    
5686                            List<Element> dynamicContentEls = imageEl.elements(
5687                                    "dynamic-content");
5688    
5689                            for (Element dynamicContentEl : dynamicContentEls) {
5690                                    long imageId = GetterUtil.getLong(
5691                                            dynamicContentEl.attributeValue("id"));
5692                                    String languageId = dynamicContentEl.attributeValue(
5693                                            "language-id");
5694    
5695                                    Image oldImage = null;
5696    
5697                                    try {
5698                                            oldImage = imageLocalService.getImage(imageId);
5699                                    }
5700                                    catch (NoSuchImageException nsie) {
5701                                            continue;
5702                                    }
5703    
5704                                    imageId = journalArticleImageLocalService.getArticleImageId(
5705                                            newArticle.getGroupId(), newArticle.getArticleId(),
5706                                            newArticle.getVersion(), instanceId, name, languageId);
5707    
5708                                    imageLocalService.updateImage(imageId, oldImage.getTextObj());
5709    
5710                                    String elContent =
5711                                            "/image/journal/article?img_id=" + imageId + "&t=" +
5712                                                    WebServerServletTokenUtil.getToken(imageId);
5713    
5714                                    dynamicContentEl.setText(elContent);
5715                                    dynamicContentEl.addAttribute("id", String.valueOf(imageId));
5716                            }
5717                    }
5718    
5719                    newArticle.setContent(contentDoc.formattedString());
5720            }
5721    
5722            protected void format(
5723                            User user, long groupId, String articleId, double version,
5724                            boolean incrementVersion, Element root, Map<String, byte[]> images)
5725                    throws PortalException, SystemException {
5726    
5727                    for (Element element : root.elements()) {
5728                            String elInstanceId = element.attributeValue(
5729                                    "instance-id", StringPool.BLANK);
5730                            String elType = element.attributeValue("type", StringPool.BLANK);
5731    
5732                            if (elType.equals("image")) {
5733                                    String elName = element.attributeValue(
5734                                            "name", StringPool.BLANK);
5735                                    String elIndex = element.attributeValue(
5736                                            "index", StringPool.BLANK);
5737    
5738                                    String name = elName + "_" + elIndex;
5739    
5740                                    formatImage(
5741                                            groupId, articleId, version, incrementVersion, element,
5742                                            elInstanceId, name, images);
5743                            }
5744                            else if (elType.equals("text_area") || elType.equals("text") ||
5745                                             elType.equals("text_box")) {
5746    
5747                                    List<Element> dynamicContentElements = element.elements(
5748                                            "dynamic-content");
5749    
5750                                    for (Element dynamicContentElement : dynamicContentElements) {
5751                                            String dynamicContent = dynamicContentElement.getText();
5752    
5753                                            if (Validator.isNotNull(dynamicContent)) {
5754                                                    String contentType = ContentTypes.TEXT_PLAIN;
5755    
5756                                                    if (elType.equals("text_area")) {
5757                                                            contentType = ContentTypes.TEXT_HTML;
5758                                                    }
5759    
5760                                                    dynamicContent = SanitizerUtil.sanitize(
5761                                                            user.getCompanyId(), groupId, user.getUserId(),
5762                                                            JournalArticle.class.getName(), 0, contentType,
5763                                                            dynamicContent);
5764    
5765                                                    dynamicContentElement.clearContent();
5766    
5767                                                    dynamicContentElement.addCDATA(dynamicContent);
5768                                            }
5769                                    }
5770                            }
5771    
5772                            format(
5773                                    user, groupId, articleId, version, incrementVersion, element,
5774                                    images);
5775                    }
5776            }
5777    
5778            protected String format(
5779                            User user, long groupId, String articleId, double version,
5780                            boolean incrementVersion, String content, String ddmStructureKey,
5781                            Map<String, byte[]> images)
5782                    throws PortalException, SystemException {
5783    
5784                    Document document = null;
5785    
5786                    try {
5787                            document = SAXReaderUtil.read(content);
5788    
5789                            Element rootElement = document.getRootElement();
5790    
5791                            if (Validator.isNotNull(ddmStructureKey)) {
5792                                    format(
5793                                            user, groupId, articleId, version, incrementVersion,
5794                                            rootElement, images);
5795                            }
5796                            else {
5797                                    List<Element> staticContentElements = rootElement.elements(
5798                                            "static-content");
5799    
5800                                    for (Element staticContentElement : staticContentElements) {
5801                                            String staticContent = staticContentElement.getText();
5802    
5803                                            staticContent = SanitizerUtil.sanitize(
5804                                                    user.getCompanyId(), groupId, user.getUserId(),
5805                                                    JournalArticle.class.getName(), 0,
5806                                                    ContentTypes.TEXT_HTML, staticContent);
5807    
5808                                            staticContentElement.clearContent();
5809    
5810                                            staticContentElement.addCDATA(staticContent);
5811                                    }
5812                            }
5813    
5814                            content = DDMXMLUtil.formatXML(document);
5815                    }
5816                    catch (DocumentException de) {
5817                            _log.error(de, de);
5818                    }
5819    
5820                    content = HtmlUtil.replaceMsWordCharacters(content);
5821    
5822                    return content;
5823            }
5824    
5825            protected void formatImage(
5826                            long groupId, String articleId, double version,
5827                            boolean incrementVersion, Element el, String elInstanceId,
5828                            String elName, Map<String, byte[]> images)
5829                    throws PortalException, SystemException {
5830    
5831                    List<Element> imageContents = el.elements("dynamic-content");
5832    
5833                    for (Element dynamicContent : imageContents) {
5834                            String elLanguage = dynamicContent.attributeValue(
5835                                    "language-id", StringPool.BLANK);
5836    
5837                            if (!elLanguage.equals(StringPool.BLANK)) {
5838                                    elLanguage = "_" + elLanguage;
5839                            }
5840    
5841                            long imageId = journalArticleImageLocalService.getArticleImageId(
5842                                    groupId, articleId, version, elInstanceId, elName, elLanguage);
5843    
5844                            if (dynamicContent.getText().equals("delete") ||
5845                                    Validator.isNull(dynamicContent.getText())) {
5846    
5847                                    dynamicContent.setText(StringPool.BLANK);
5848    
5849                                    imageLocalService.deleteImage(imageId);
5850    
5851                                    String defaultElLanguage = "";
5852    
5853                                    if (Validator.isNull(elLanguage)) {
5854                                            defaultElLanguage =
5855                                                    "_" +
5856                                                            LocaleUtil.toLanguageId(
5857                                                                    LocaleUtil.getSiteDefault());
5858                                    }
5859    
5860                                    long defaultImageId =
5861                                            journalArticleImageLocalService.getArticleImageId(
5862                                                    groupId, articleId, version, elInstanceId, elName,
5863                                                    defaultElLanguage);
5864    
5865                                    imageLocalService.deleteImage(defaultImageId);
5866    
5867                                    continue;
5868                            }
5869    
5870                            String elContent =
5871                                    "/image/journal/article?img_id=" + imageId + "&t=" +
5872                                            WebServerServletTokenUtil.getToken(imageId);
5873    
5874                            byte[] bytes = images.get(elInstanceId + "_" + elName + elLanguage);
5875    
5876                            if (ArrayUtil.isNotEmpty(bytes)) {
5877                                    dynamicContent.setText(elContent);
5878                                    dynamicContent.addAttribute("id", String.valueOf(imageId));
5879    
5880                                    imageLocalService.updateImage(imageId, bytes);
5881    
5882                                    continue;
5883                            }
5884    
5885                            if ((version > JournalArticleConstants.VERSION_DEFAULT) &&
5886                                    incrementVersion) {
5887    
5888                                    double oldVersion = MathUtil.format(version - 0.1, 1, 1);
5889    
5890                                    long oldImageId = 0;
5891    
5892                                    if ((oldVersion >= 1) && incrementVersion) {
5893                                            oldImageId =
5894                                                    journalArticleImageLocalService.getArticleImageId(
5895                                                            groupId, articleId, oldVersion, elInstanceId,
5896                                                            elName, elLanguage);
5897                                    }
5898    
5899                                    Image oldImage = null;
5900    
5901                                    if (oldImageId > 0) {
5902                                            oldImage = imageLocalService.getImage(oldImageId);
5903                                    }
5904    
5905                                    if (oldImage != null) {
5906                                            dynamicContent.setText(elContent);
5907                                            dynamicContent.addAttribute("id", String.valueOf(imageId));
5908    
5909                                            bytes = oldImage.getTextObj();
5910    
5911                                            imageLocalService.updateImage(imageId, bytes);
5912                                    }
5913                                    else if (dynamicContent.getText().equals("update")) {
5914                                            dynamicContent.setText(StringPool.BLANK);
5915                                    }
5916    
5917                                    continue;
5918                            }
5919    
5920                            Image image = imageLocalService.getImage(imageId);
5921    
5922                            if (image != null) {
5923                                    dynamicContent.setText(elContent);
5924                                    dynamicContent.addAttribute("id", String.valueOf(imageId));
5925    
5926                                    continue;
5927                            }
5928                            else if (dynamicContent.getText().equals("update")) {
5929                                    dynamicContent.setText(StringPool.BLANK);
5930    
5931                                    continue;
5932                            }
5933    
5934                            long contentImageId = GetterUtil.getLong(
5935                                    HttpUtil.getParameter(dynamicContent.getText(), "img_id"));
5936    
5937                            if (contentImageId <= 0) {
5938                                    contentImageId = GetterUtil.getLong(
5939                                            HttpUtil.getParameter(
5940                                                    dynamicContent.getText(), "img_id", false));
5941                            }
5942    
5943                            if (contentImageId > 0) {
5944                                    image = imageLocalService.getImage(contentImageId);
5945    
5946                                    if (image != null) {
5947                                            dynamicContent.addAttribute(
5948                                                    "id", String.valueOf(contentImageId));
5949    
5950                                            continue;
5951                                    }
5952                            }
5953    
5954                            String defaultElLanguage = "";
5955    
5956                            if (Validator.isNull(elLanguage)) {
5957                                    defaultElLanguage =
5958                                            "_" + LocaleUtil.toLanguageId(LocaleUtil.getSiteDefault());
5959                            }
5960    
5961                            long defaultImageId =
5962                                    journalArticleImageLocalService.getArticleImageId(
5963                                            groupId, articleId, version, elInstanceId, elName,
5964                                            defaultElLanguage);
5965    
5966                            Image defaultImage = imageLocalService.getImage(defaultImageId);
5967    
5968                            if (defaultImage != null) {
5969                                    dynamicContent.setText(elContent);
5970                                    dynamicContent.addAttribute(
5971                                            "id", String.valueOf(defaultImageId));
5972    
5973                                    bytes = defaultImage.getTextObj();
5974    
5975                                    imageLocalService.updateImage(defaultImageId, bytes);
5976    
5977                                    continue;
5978                            }
5979    
5980                            if (Validator.isNotNull(elLanguage)) {
5981                                    dynamicContent.setText(StringPool.BLANK);
5982                            }
5983                    }
5984            }
5985    
5986            protected List<ObjectValuePair<Long, Integer>> getArticleVersionStatuses(
5987                    List<JournalArticle> articles) {
5988    
5989                    List<ObjectValuePair<Long, Integer>> dlArticleVersionStatusOVPs =
5990                            new ArrayList<ObjectValuePair<Long, Integer>>(articles.size());
5991    
5992                    for (JournalArticle article : articles) {
5993                            int status = article.getStatus();
5994    
5995                            if (status == WorkflowConstants.STATUS_PENDING) {
5996                                    status = WorkflowConstants.STATUS_DRAFT;
5997                            }
5998    
5999                            ObjectValuePair<Long, Integer> dlFileVersionStatusOVP =
6000                                    new ObjectValuePair<Long, Integer>(article.getId(), status);
6001    
6002                            dlArticleVersionStatusOVPs.add(dlFileVersionStatusOVP);
6003                    }
6004    
6005                    return dlArticleVersionStatusOVPs;
6006            }
6007    
6008            protected long getClassTypeId(JournalArticle article) {
6009                    long classTypeId = 0;
6010    
6011                    try {
6012                            long classNameId = PortalUtil.getClassNameId(JournalArticle.class);
6013    
6014                            DDMStructure ddmStructure = ddmStructurePersistence.fetchByG_C_S(
6015                                    article.getGroupId(), classNameId, article.getStructureId());
6016    
6017                            if (ddmStructure == null) {
6018                                    Group companyGroup = groupLocalService.getCompanyGroup(
6019                                            article.getCompanyId());
6020    
6021                                    ddmStructure = ddmStructurePersistence.fetchByG_C_S(
6022                                            companyGroup.getGroupId(), classNameId,
6023                                            article.getStructureId());
6024                            }
6025    
6026                            if (ddmStructure != null) {
6027                                    classTypeId = ddmStructure.getStructureId();
6028                            }
6029                    }
6030                    catch (Exception e) {
6031                            _log.error(e, e);
6032                    }
6033    
6034                    return classTypeId;
6035            }
6036    
6037            protected Date[] getDateInterval(
6038                            long groupId, String articleId, Date earliestDisplayDate,
6039                            Date latestExpirationDate)
6040                    throws SystemException {
6041    
6042                    Date[] dateInterval = new Date[2];
6043    
6044                    List<JournalArticle> articles = journalArticlePersistence.findByG_A_ST(
6045                            groupId, articleId, WorkflowConstants.STATUS_APPROVED);
6046    
6047                    boolean expiringArticle = true;
6048    
6049                    if (latestExpirationDate == null) {
6050                            expiringArticle = false;
6051                    }
6052    
6053                    for (JournalArticle article : articles) {
6054                            if ((earliestDisplayDate == null) ||
6055                                    ((article.getDisplayDate() != null) &&
6056                                     earliestDisplayDate.after(article.getDisplayDate()))) {
6057    
6058                                    earliestDisplayDate = article.getDisplayDate();
6059                            }
6060    
6061                            if (expiringArticle &&
6062                                    ((latestExpirationDate == null) ||
6063                                     ((article.getExpirationDate() != null) &&
6064                                      latestExpirationDate.before(article.getExpirationDate())))) {
6065    
6066                                    latestExpirationDate = article.getExpirationDate();
6067                            }
6068    
6069                            if (expiringArticle && (article.getExpirationDate() == null) &&
6070                                    (latestExpirationDate != null)) {
6071    
6072                                    expiringArticle = false;
6073                            }
6074                    }
6075    
6076                    dateInterval[0] = earliestDisplayDate;
6077                    dateInterval[1] = latestExpirationDate;
6078    
6079                    return dateInterval;
6080            }
6081    
6082            protected String getUniqueUrlTitle(
6083                            long id, long groupId, String articleId, String title)
6084                    throws PortalException, SystemException {
6085    
6086                    String urlTitle = JournalUtil.getUrlTitle(id, title);
6087    
6088                    return getUniqueUrlTitle(groupId, articleId, urlTitle);
6089            }
6090    
6091            protected String getUniqueUrlTitle(
6092                            long id, String articleId, String title, String oldUrlTitle,
6093                            ServiceContext serviceContext)
6094                    throws PortalException, SystemException {
6095    
6096                    String serviceContextUrlTitle = ParamUtil.getString(
6097                            serviceContext, "urlTitle");
6098    
6099                    String urlTitle = null;
6100    
6101                    if (Validator.isNotNull(serviceContextUrlTitle)) {
6102                            urlTitle = JournalUtil.getUrlTitle(id, serviceContextUrlTitle);
6103                    }
6104                    else if (Validator.isNotNull(oldUrlTitle)) {
6105                            return oldUrlTitle;
6106                    }
6107                    else {
6108                            urlTitle = getUniqueUrlTitle(
6109                                    id, serviceContext.getScopeGroupId(), articleId, title);
6110                    }
6111    
6112                    JournalArticle urlTitleArticle = null;
6113    
6114                    try {
6115                            urlTitleArticle = getArticleByUrlTitle(
6116                                    serviceContext.getScopeGroupId(), urlTitle);
6117                    }
6118                    catch (NoSuchArticleException nsae) {
6119                    }
6120    
6121                    if ((urlTitleArticle != null) &&
6122                            !Validator.equals(
6123                                    urlTitleArticle.getArticleId(), articleId)) {
6124    
6125                            urlTitle = getUniqueUrlTitle(
6126                                    id, serviceContext.getScopeGroupId(), articleId, urlTitle);
6127                    }
6128    
6129                    return urlTitle;
6130            }
6131    
6132            protected boolean hasModifiedLatestApprovedVersion(
6133                            long groupId, String articleId, double version)
6134                    throws PortalException, SystemException {
6135    
6136                    double latestApprovedVersion;
6137    
6138                    try {
6139                            latestApprovedVersion = getLatestVersion(
6140                                    groupId, articleId, WorkflowConstants.STATUS_APPROVED);
6141    
6142                            if (version >= latestApprovedVersion) {
6143                                    return true;
6144                            }
6145                            else {
6146                                    return false;
6147                            }
6148                    }
6149                    catch (NoSuchArticleException nsae) {
6150                            return true;
6151                    }
6152            }
6153    
6154            protected void notifySubscribers(
6155                            JournalArticle article, ServiceContext serviceContext)
6156                    throws PortalException, SystemException {
6157    
6158                    if (!article.isApproved()) {
6159                            return;
6160                    }
6161    
6162                    String articleURL = PortalUtil.getControlPanelFullURL(
6163                            serviceContext.getScopeGroupId(), PortletKeys.JOURNAL, null);
6164    
6165                    if (Validator.isNull(articleURL)) {
6166                            return;
6167                    }
6168    
6169                    PortletPreferences preferences =
6170                            ServiceContextUtil.getPortletPreferences(serviceContext);
6171    
6172                    if (preferences == null) {
6173                            long ownerId = article.getGroupId();
6174                            int ownerType = PortletKeys.PREFS_OWNER_TYPE_GROUP;
6175                            long plid = PortletKeys.PREFS_PLID_SHARED;
6176                            String portletId = PortletKeys.JOURNAL;
6177                            String defaultPreferences = null;
6178    
6179                            preferences = portletPreferencesLocalService.getPreferences(
6180                                    article.getCompanyId(), ownerId, ownerType, plid, portletId,
6181                                    defaultPreferences);
6182                    }
6183    
6184                    if ((article.getVersion() == 1.0) &&
6185                            JournalUtil.getEmailArticleAddedEnabled(preferences)) {
6186                    }
6187                    else if ((article.getVersion() != 1.0) &&
6188                                     JournalUtil.getEmailArticleUpdatedEnabled(preferences)) {
6189                    }
6190                    else {
6191                            return;
6192                    }
6193    
6194                    String fromName = JournalUtil.getEmailFromName(
6195                            preferences, article.getCompanyId());
6196                    String fromAddress = JournalUtil.getEmailFromAddress(
6197                            preferences, article.getCompanyId());
6198    
6199                    String subject = null;
6200                    String body = null;
6201    
6202                    if (article.getVersion() == 1.0) {
6203                            subject = JournalUtil.getEmailArticleAddedSubject(preferences);
6204                            body = JournalUtil.getEmailArticleAddedBody(preferences);
6205                    }
6206                    else {
6207                            subject = JournalUtil.getEmailArticleUpdatedSubject(preferences);
6208                            body = JournalUtil.getEmailArticleUpdatedBody(preferences);
6209                    }
6210    
6211                    SubscriptionSender subscriptionSender = new SubscriptionSender();
6212    
6213                    subscriptionSender.setBody(body);
6214                    subscriptionSender.setCompanyId(article.getCompanyId());
6215                    subscriptionSender.setContextAttributes(
6216                            "[$ARTICLE_ID$]", article.getArticleId(), "[$ARTICLE_TITLE$]",
6217                            article.getTitle(serviceContext.getLanguageId()), "[$ARTICLE_URL$]",
6218                            articleURL, "[$ARTICLE_VERSION$]", article.getVersion());
6219                    subscriptionSender.setContextUserPrefix("ARTICLE");
6220                    subscriptionSender.setFrom(fromAddress, fromName);
6221                    subscriptionSender.setHtmlFormat(true);
6222                    subscriptionSender.setMailId("journal_article", article.getId());
6223                    subscriptionSender.setPortletId(PortletKeys.JOURNAL);
6224                    subscriptionSender.setReplyToAddress(fromAddress);
6225                    subscriptionSender.setScopeGroupId(article.getGroupId());
6226                    subscriptionSender.setServiceContext(serviceContext);
6227                    subscriptionSender.setSubject(subject);
6228                    subscriptionSender.setUserId(article.getUserId());
6229    
6230                    subscriptionSender.addPersistedSubscribers(
6231                            JournalArticle.class.getName(), article.getResourcePrimKey());
6232    
6233                    JournalFolder folder = article.getFolder();
6234    
6235                    List<Long> folderIds = new ArrayList<Long>();
6236    
6237                    if (folder != null) {
6238                            folderIds.add(folder.getFolderId());
6239    
6240                            folderIds.addAll(folder.getAncestorFolderIds());
6241                    }
6242    
6243                    for (long curFolderId : folderIds) {
6244                            subscriptionSender.addPersistedSubscribers(
6245                                    JournalFolder.class.getName(), curFolderId);
6246                    }
6247    
6248                    subscriptionSender.addPersistedSubscribers(
6249                            JournalFolder.class.getName(), article.getGroupId());
6250    
6251                    subscriptionSender.flushNotificationsAsync();
6252            }
6253    
6254            protected void saveImages(
6255                            boolean smallImage, long smallImageId, File smallImageFile,
6256                            byte[] smallImageBytes)
6257                    throws PortalException, SystemException {
6258    
6259                    if (smallImage) {
6260                            if ((smallImageFile != null) && (smallImageBytes != null)) {
6261                                    imageLocalService.updateImage(smallImageId, smallImageBytes);
6262                            }
6263                    }
6264                    else {
6265                            imageLocalService.deleteImage(smallImageId);
6266                    }
6267            }
6268    
6269            protected void sendEmail(
6270                            JournalArticle article, String articleURL,
6271                            PortletPreferences preferences, String emailType,
6272                            ServiceContext serviceContext)
6273                    throws PortalException, SystemException {
6274    
6275                    if (preferences == null) {
6276                            return;
6277                    }
6278                    else if (emailType.equals("denied") &&
6279                                     JournalUtil.getEmailArticleApprovalDeniedEnabled(
6280                                             preferences)) {
6281                    }
6282                    else if (emailType.equals("granted") &&
6283                                     JournalUtil.getEmailArticleApprovalGrantedEnabled(
6284                                             preferences)) {
6285                    }
6286                    else if (emailType.equals("requested") &&
6287                                     JournalUtil.getEmailArticleApprovalRequestedEnabled(
6288                                             preferences)) {
6289                    }
6290                    else if (emailType.equals("review") &&
6291                                     JournalUtil.getEmailArticleReviewEnabled(preferences)) {
6292                    }
6293                    else {
6294                            return;
6295                    }
6296    
6297                    Company company = companyPersistence.findByPrimaryKey(
6298                            article.getCompanyId());
6299    
6300                    User user = userPersistence.findByPrimaryKey(article.getUserId());
6301    
6302                    articleURL +=
6303                            "&groupId=" + article.getGroupId() + "&articleId=" +
6304                                    article.getArticleId() + "&version=" + article.getVersion();
6305    
6306                    String fromName = JournalUtil.getEmailFromName(
6307                            preferences, article.getCompanyId());
6308                    String fromAddress = JournalUtil.getEmailFromAddress(
6309                            preferences, article.getCompanyId());
6310    
6311                    String toName = user.getFullName();
6312                    String toAddress = user.getEmailAddress();
6313    
6314                    if (emailType.equals("requested") || emailType.equals("review")) {
6315                            String tempToName = fromName;
6316                            String tempToAddress = fromAddress;
6317    
6318                            fromName = toName;
6319                            fromAddress = toAddress;
6320    
6321                            toName = tempToName;
6322                            toAddress = tempToAddress;
6323                    }
6324    
6325                    String subject = null;
6326                    String body = null;
6327    
6328                    if (emailType.equals("denied")) {
6329                            subject = JournalUtil.getEmailArticleApprovalDeniedSubject(
6330                                    preferences);
6331                            body = JournalUtil.getEmailArticleApprovalDeniedBody(preferences);
6332                    }
6333                    else if (emailType.equals("granted")) {
6334                            subject = JournalUtil.getEmailArticleApprovalGrantedSubject(
6335                                    preferences);
6336                            body = JournalUtil.getEmailArticleApprovalGrantedBody(preferences);
6337                    }
6338                    else if (emailType.equals("requested")) {
6339                            subject = JournalUtil.getEmailArticleApprovalRequestedSubject(
6340                                    preferences);
6341                            body = JournalUtil.getEmailArticleApprovalRequestedBody(
6342                                    preferences);
6343                    }
6344                    else if (emailType.equals("review")) {
6345                            subject = JournalUtil.getEmailArticleReviewSubject(preferences);
6346                            body = JournalUtil.getEmailArticleReviewBody(preferences);
6347                    }
6348    
6349                    SubscriptionSender subscriptionSender = new SubscriptionSender();
6350    
6351                    subscriptionSender.setBody(body);
6352                    subscriptionSender.setCompanyId(company.getCompanyId());
6353                    subscriptionSender.setContextAttributes(
6354                            "[$ARTICLE_ID$]", article.getArticleId(), "[$ARTICLE_TITLE$]",
6355                            article.getTitle(serviceContext.getLanguageId()), "[$ARTICLE_URL$]",
6356                            articleURL, "[$ARTICLE_USER_NAME$]", article.getUserName(),
6357                            "[$ARTICLE_VERSION$]", article.getVersion());
6358                    subscriptionSender.setContextUserPrefix("ARTICLE");
6359                    subscriptionSender.setFrom(fromAddress, fromName);
6360                    subscriptionSender.setHtmlFormat(true);
6361                    subscriptionSender.setMailId("journal_article", article.getId());
6362                    subscriptionSender.setPortletId(PortletKeys.JOURNAL);
6363                    subscriptionSender.setScopeGroupId(article.getGroupId());
6364                    subscriptionSender.setServiceContext(serviceContext);
6365                    subscriptionSender.setSubject(subject);
6366                    subscriptionSender.setUserId(article.getUserId());
6367    
6368                    subscriptionSender.addRuntimeSubscribers(toAddress, toName);
6369    
6370                    subscriptionSender.flushNotificationsAsync();
6371            }
6372    
6373            protected void updateDDMStructureXSD(
6374                            long ddmStructureId, String content, ServiceContext serviceContext)
6375                    throws PortalException, SystemException {
6376    
6377                    try {
6378                            Document document = SAXReaderUtil.read(content);
6379    
6380                            Element rootElement = document.getRootElement();
6381    
6382                            List<Element> elements = rootElement.elements();
6383    
6384                            for (Element element : elements) {
6385                                    String fieldName = element.attributeValue(
6386                                            "name", StringPool.BLANK);
6387    
6388                                    List<Element> dynamicContentElements = element.elements(
6389                                            "dynamic-content");
6390    
6391                                    for (Element dynamicContentElement : dynamicContentElements) {
6392                                            String value = dynamicContentElement.getText();
6393    
6394                                            ddmStructureLocalService.updateXSDFieldMetadata(
6395                                                    ddmStructureId, fieldName,
6396                                                    FieldConstants.PREDEFINED_VALUE, value, serviceContext);
6397                                    }
6398                            }
6399                    }
6400                    catch (DocumentException de) {
6401                            throw new SystemException(de);
6402                    }
6403            }
6404    
6405            protected void updatePreviousApprovedArticle(JournalArticle article)
6406                    throws PortalException, SystemException {
6407    
6408                    List<JournalArticle> approvedArticles =
6409                            journalArticlePersistence.findByG_A_ST(
6410                                    article.getGroupId(), article.getArticleId(),
6411                                    WorkflowConstants.STATUS_APPROVED, 0, 2);
6412    
6413                    if (approvedArticles.isEmpty() ||
6414                            ((approvedArticles.size() == 1) &&
6415                             (article.getStatus() == WorkflowConstants.STATUS_APPROVED))) {
6416    
6417                            assetEntryLocalService.updateVisible(
6418                                    JournalArticle.class.getName(), article.getResourcePrimKey(),
6419                                    false);
6420                    }
6421                    else {
6422                            JournalArticle previousApprovedArticle = approvedArticles.get(0);
6423    
6424                            if (article.getStatus() == WorkflowConstants.STATUS_APPROVED) {
6425                                    previousApprovedArticle = approvedArticles.get(1);
6426                            }
6427    
6428                            Date[] dateInterval = getDateInterval(
6429                                    previousApprovedArticle.getGroupId(),
6430                                    previousApprovedArticle.getArticleId(),
6431                                    previousApprovedArticle.getDisplayDate(),
6432                                    previousApprovedArticle.getExpirationDate());
6433    
6434                            Date displayDate = dateInterval[0];
6435                            Date expirationDate = dateInterval[1];
6436    
6437                            AssetEntry assetEntry = assetEntryLocalService.updateEntry(
6438                                    JournalArticle.class.getName(), article.getResourcePrimKey(),
6439                                    displayDate, expirationDate, true);
6440    
6441                            assetEntry.setModifiedDate(
6442                                    previousApprovedArticle.getModifiedDate());
6443    
6444                            assetEntryPersistence.update(assetEntry);
6445                    }
6446            }
6447    
6448            protected void updateUrlTitles(
6449                            long groupId, String articleId, String urlTitle)
6450                    throws SystemException {
6451    
6452                    List<JournalArticle> articles = journalArticlePersistence.findByG_A(
6453                            groupId, articleId);
6454    
6455                    for (JournalArticle article : articles) {
6456                            if (!article.getUrlTitle().equals(urlTitle)) {
6457                                    article.setUrlTitle(urlTitle);
6458    
6459                                    journalArticlePersistence.update(article);
6460                            }
6461                    }
6462            }
6463    
6464            protected void validate(
6465                            long companyId, long groupId, long classNameId,
6466                            Map<Locale, String> titleMap, String content, String type,
6467                            String ddmStructureKey, String ddmTemplateKey, Date expirationDate,
6468                            boolean smallImage, String smallImageURL, File smallImageFile,
6469                            byte[] smallImageBytes)
6470                    throws PortalException, SystemException {
6471    
6472                    Locale articleDefaultLocale = LocaleUtil.fromLanguageId(
6473                            LocalizationUtil.getDefaultLanguageId(content));
6474    
6475                    Locale[] availableLocales = LanguageUtil.getAvailableLocales(groupId);
6476    
6477                    if (!ArrayUtil.contains(availableLocales, articleDefaultLocale)) {
6478                            LocaleException le = new LocaleException(
6479                                    LocaleException.TYPE_CONTENT,
6480                                    "The locale " + articleDefaultLocale +
6481                                            " is not available in site with groupId" + groupId);
6482    
6483                            Locale[] sourceAvailableLocales = {articleDefaultLocale};
6484    
6485                            le.setSourceAvailableLocales(sourceAvailableLocales);
6486                            le.setTargetAvailableLocales(availableLocales);
6487    
6488                            throw le;
6489                    }
6490    
6491                    if ((classNameId == JournalArticleConstants.CLASSNAME_ID_DEFAULT) &&
6492                            (titleMap.isEmpty() ||
6493                             Validator.isNull(titleMap.get(articleDefaultLocale)))) {
6494    
6495                            throw new ArticleTitleException();
6496                    }
6497                    else if (Validator.isNull(type)) {
6498                            throw new ArticleTypeException();
6499                    }
6500    
6501                    validateContent(content);
6502    
6503                    if (Validator.isNotNull(ddmStructureKey)) {
6504                            Group companyGroup = groupLocalService.getCompanyGroup(companyId);
6505    
6506                            DDMStructure ddmStructure = null;
6507    
6508                            try {
6509                                    ddmStructure = ddmStructurePersistence.findByG_C_S(
6510                                            groupId, PortalUtil.getClassNameId(JournalArticle.class),
6511                                            ddmStructureKey);
6512                            }
6513                            catch (NoSuchStructureException nsse) {
6514                                    ddmStructure = ddmStructurePersistence.findByG_C_S(
6515                                            companyGroup.getGroupId(),
6516                                            PortalUtil.getClassNameId(JournalArticle.class),
6517                                            ddmStructureKey);
6518                            }
6519    
6520                            DDMTemplate ddmTemplate = null;
6521    
6522                            if (Validator.isNotNull(ddmTemplateKey)) {
6523                                    try {
6524                                            ddmTemplate = ddmTemplatePersistence.findByG_C_T(
6525                                                    groupId, PortalUtil.getClassNameId(DDMStructure.class),
6526                                                    ddmTemplateKey);
6527                                    }
6528                                    catch (NoSuchTemplateException nste) {
6529                                            ddmTemplate = ddmTemplatePersistence.findByG_C_T(
6530                                                    companyGroup.getGroupId(),
6531                                                    PortalUtil.getClassNameId(DDMStructure.class),
6532                                                    ddmTemplateKey);
6533                                    }
6534    
6535                                    if (ddmTemplate.getClassPK() != ddmStructure.getStructureId()) {
6536                                            throw new NoSuchTemplateException();
6537                                    }
6538                            }
6539                            else if (classNameId ==
6540                                                    JournalArticleConstants.CLASSNAME_ID_DEFAULT) {
6541    
6542                                    throw new NoSuchTemplateException();
6543                            }
6544                    }
6545    
6546                    if ((expirationDate != null) && expirationDate.before(new Date()) &&
6547                            !ExportImportThreadLocal.isImportInProcess()) {
6548    
6549                            throw new ArticleExpirationDateException();
6550                    }
6551    
6552                    String[] imageExtensions = PrefsPropsUtil.getStringArray(
6553                            PropsKeys.JOURNAL_IMAGE_EXTENSIONS, StringPool.COMMA);
6554    
6555                    if (!smallImage || Validator.isNotNull(smallImageURL) ||
6556                            (smallImageFile == null) || (smallImageBytes == null)) {
6557    
6558                            return;
6559                    }
6560    
6561                    String smallImageName = smallImageFile.getName();
6562    
6563                    if (smallImageName != null) {
6564                            boolean validSmallImageExtension = false;
6565    
6566                            for (String _imageExtension : imageExtensions) {
6567                                    if (StringPool.STAR.equals(_imageExtension) ||
6568                                            StringUtil.endsWith(smallImageName, _imageExtension)) {
6569    
6570                                            validSmallImageExtension = true;
6571    
6572                                            break;
6573                                    }
6574                            }
6575    
6576                            if (!validSmallImageExtension) {
6577                                    throw new ArticleSmallImageNameException(smallImageName);
6578                            }
6579                    }
6580    
6581                    long smallImageMaxSize = PrefsPropsUtil.getLong(
6582                            PropsKeys.JOURNAL_IMAGE_SMALL_MAX_SIZE);
6583    
6584                    if ((smallImageMaxSize > 0) &&
6585                            ((smallImageBytes == null) ||
6586                             (smallImageBytes.length > smallImageMaxSize))) {
6587    
6588                            throw new ArticleSmallImageSizeException();
6589                    }
6590            }
6591    
6592            protected void validate(
6593                            long companyId, long groupId, long classNameId, String articleId,
6594                            boolean autoArticleId, double version, Map<Locale, String> titleMap,
6595                            String content, String type, String ddmStructureKey,
6596                            String ddmTemplateKey, Date expirationDate, boolean smallImage,
6597                            String smallImageURL, File smallImageFile, byte[] smallImageBytes)
6598                    throws PortalException, SystemException {
6599    
6600                    if (!autoArticleId) {
6601                            validate(articleId);
6602                    }
6603    
6604                    JournalArticle article = journalArticlePersistence.fetchByG_A_V(
6605                            groupId, articleId, version);
6606    
6607                    if (article != null) {
6608                            throw new DuplicateArticleIdException();
6609                    }
6610    
6611                    validate(
6612                            companyId, groupId, classNameId, titleMap, content, type,
6613                            ddmStructureKey, ddmTemplateKey, expirationDate, smallImage,
6614                            smallImageURL, smallImageFile, smallImageBytes);
6615            }
6616    
6617            protected void validate(String articleId) throws PortalException {
6618                    if (Validator.isNull(articleId) ||
6619                            (articleId.indexOf(CharPool.SPACE) != -1)) {
6620    
6621                            throw new ArticleIdException();
6622                    }
6623            }
6624    
6625            protected void validateContent(String content) throws PortalException {
6626                    if (Validator.isNull(content)) {
6627                            throw new ArticleContentException("Content is null");
6628                    }
6629    
6630                    try {
6631                            SAXReaderUtil.read(content);
6632                    }
6633                    catch (DocumentException de) {
6634                            if (_log.isDebugEnabled()) {
6635                                    _log.debug("Invalid content:\n" + content);
6636                            }
6637    
6638                            throw new ArticleContentException(
6639                                    "Unable to read content with an XML parser", de);
6640                    }
6641            }
6642    
6643            private static final long _JOURNAL_ARTICLE_CHECK_INTERVAL =
6644                    PropsValues.JOURNAL_ARTICLE_CHECK_INTERVAL * Time.MINUTE;
6645    
6646            private static Log _log = LogFactoryUtil.getLog(
6647                    JournalArticleLocalServiceImpl.class);
6648    
6649            private Date _previousCheckDate;
6650    
6651    }