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